-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.json
More file actions
1 lines (1 loc) · 46.6 KB
/
index.json
File metadata and controls
1 lines (1 loc) · 46.6 KB
1
[{"categories":null,"contents":"At the center of EMF Cloud, there is a model management component, which provides consistent model access, as well as an interface for manipulating models and listening to model changes across various editors, views, and components interacting with a model. This model management component is called Model Hub (Typescript).\n The Model Hub is an extensible central model management component, which offers a common API to all clients for accessing and interacting with models. As such, the model hub provides plenty of generic functionalities, such as dirty state handling of models, undo/redo, notification mechanisms for various events, etc. It supports clients living in the frontend (browser), as well as clients that are running in a backend (e.g. node.js).\nFor each modeling language or format, you can register a modeling language contribution, which defines how certain model-specific capabilities are implemented, such as persisting models, model-specific operations or APIs that can be invoked for your model, how cross-references are to be resolved within a workspace, validation rules, etc. This gives you full control and customizability in all relevant aspects of the model management for your modeling language, format, or data source, whether it is a JSON file, custom file format, database, or REST service that you want to make available to your clients by integrating them with the Model Hub.\nTo make your life easier, you don\u0026rsquo;t have to implement all of those Model Hub capabilities from scratch for every modeling language. Instead EMF Cloud provides reusable libraries and integration code for third-party components to cover the most common choices and formats, such as JSON files. Also, EMF Cloud contains libraries that make it easy to connect and interact with the Model Hub.\n➡️ Best to get started with one of our example applications: Model Hub examples for model management or CrossModel for DSL support.\nNote on EMF: If you need to support EMF-based models, there is a dedicated Java-based model management component for EMF models. For more information, please head over to the EMF support documentation. For all other use cases, we recommend to use the Model Hub as it provides a more homogeneous developer experience based on Typescript throughout the client and the server.\n","permalink":"https://www.eclipse.dev/emfcloud/documentation/overview/","tags":null,"title":"Overview"},{"categories":null,"contents":"Getting Started EMF Cloud provides two approaches to best accommodate your project\u0026rsquo;s needs. Choose the path that aligns best with your requirements.\nModel Hub Approach Designed for projects that require robust, command-based model management with features like undo/redo, versioning, and persistence. This approach is best suited for users who need a tightly controlled model manipulation environment.\n→ Explore the Model Hub Demo for a practical example.\nLangium-Based DSL Approach Tailored for projects that benefit from advanced textual DSL support with integrated Language Server Protocol (LSP) features, scaleable cross-reference and workspace indexing support. This approach excels in delivering a modeling solution for large interconnected models that are spread across many files, with full control over the underlying textual format.\n→ Check out the CrossModel Demo to see this approach in action.\n For a detailed comparison of the two approaches and guidance on when to use which, please refer to our EMF Cloud Approaches Documentation.\nGet started by selecting the demo that best matches your project’s requirements.\n","permalink":"https://www.eclipse.dev/emfcloud/documentation/gettingstarted/","tags":null,"title":"Getting Started"},{"categories":null,"contents":"EMF Cloud provides two complementary approaches for implementing the model management. Each approach is tailored to specific requirements of modeling solutions and is integrated as a core part of the framework. On this page we provide an overview of both approaches and give recommendations which approach is the right for your modeling tool.\n Model Hub Approach This approach focuses on a model-first, command-based model management. It is built around a central Model Hub that offers robust capabilities such as command-based editing, state management with undo/redo, and persistence. This is ideal for scenarios where fine-grained change control and model-oriented clients are most vital for your modeling tool.\nFor more details, see the Model Hub Approach section.\nLangium-Based DSL Approach This approach is designed for modeling tools that are built around textual domain-specific languages. Leveraging Langium, it provides advanced textual language features including parsing, reference resolution, workspace indexing, and language server integration, but extends it with a modeling-tool-oriented API to simplify accessing the models via an API for non-textual editors.\nFor more details, see the Langium-Based DSL Approach section.\nSelecting the Right Approach Most modeling tools can be built with either of the two approaches, however, many modeling tools have rather a text-file-oriented model management perspective or a model-oriented model management perspective, at least they tend in one or the other direction. Tending in the one or the other direction makes certain implementation strategies a more natural fit.\nInstead of enforcing one approach for all modeling tools, EMF Cloud embraces these differences and provides a set of different components for both implementation strategies.\nSummary of Approaches The table below highlights the key differences between the two approaches. For further guidance on integrating and customizing these approaches, please consult the dedicated sections in our documentation.\n Feature Category Model Hub Langium-Based DSL Core Model Management ✓ Comprehensive management with command-based editing and undo/redo ✓ Includes DSL-specific model management Model API Advanced and flexible Basic integration for DSL Command-based Editing ✓ Built-in support for complex model edits - Multi-Client Support ✓ Real-time updates and coordination ✓ Supports multi-client scenarios Validation Framework Comprehensive model validation DSL-based text validation Persistence Layer Customizable strategies DSL-driven approaches Language Infrastructure (LSP) - ✓ Full Language Server Protocol support Reference Resolution Basic built-in resolution Advanced symbol and reference management ","permalink":"https://www.eclipse.dev/emfcloud/documentation/approaches/","tags":null,"title":"Modeling Tool Approaches"},{"categories":null,"contents":"The Langium-Based DSL Approach offers a sophisticated foundation for building modeling tools centered around textual domain-specific languages. This approach harnesses the advanced capabilities of Langium — providing professional-grade language features such as parsing, reference resolution, workspace indexing, and language server integration — while extending it with a purpose-built API designed specifically for modeling tools. The result is a seamless experience for developers and users working with models through both textual and visual interfaces.\n Core Features and Benefits Complete Language Server Protocol Integration: Enjoy a rich development experience with syntax highlighting, intelligent autocompletion, and real-time validation that guides users toward correct model definitions Sophisticated Symbol and Reference Management: Navigate complex model relationships effortlessly with cross-file reference resolution that maintains model integrity Purpose-Built Model Management: Access models programmatically through APIs specifically designed for DSL interaction High-Performance Workspace Indexing: Handle enterprise-scale models spanning hundreds of files with efficient indexing and search capabilities Seamless Multi-Client Collaboration: Enable coordinated model editing across multiple editor instances with automatic synchronization When to Choose This Approach The Langium-Based DSL approach delivers exceptional value for:\n Text-Centric Modeling Projects: When your primary interface for model creation and editing is textual Large, Interconnected Model Ecosystems: When managing extensive models distributed across numerous files Custom Format Requirements: When you need complete control over the textual representation of your models Developer Experience Focus: When advanced language features like autocompletion, validation, and navigation are critical to user productivity Creating your DSL with Langium Langium is a powerful open-source language engineering tool that transforms an EBNF-like grammar into a complete TypeScript-based language server with features like syntax highlighting, auto-completion, cross-references, and validation.\nGrammar Definition In Langium, you define your grammar in a dedicated .langium file. Here\u0026rsquo;s an example of a simple grammar with JSON-like syntax:\ngrammar CustomLanguage // grammar name entry CustomModelRoot: // entry rule for the parser, i.e., document root Machine | WorkflowConfig; // sequence of valid tokens → abstract syntax fragment IdentifiableFragment: // re-usable fragment \u0026#39;\u0026#34;id\u0026#34;\u0026#39; \u0026#39;:\u0026#39; id=STRING; TYPE_MACHINE returns string: \u0026#39;\u0026#34;Machine\u0026#34;\u0026#39;; // we use a type to ease distinction for parsing Machine: \u0026#39;{\u0026#39; IdentifiableFragment \u0026#39;,\u0026#39; \u0026#39;\u0026#34;name\u0026#34;\u0026#39; \u0026#39;:\u0026#39; name=STRING // keywords as inline terminals → conrecte syntax \u0026#39;,\u0026#39; \u0026#39;\u0026#34;type\u0026#34;\u0026#39; \u0026#39;:\u0026#39; type=TYPE_MACHINE (\u0026#39;,\u0026#39; \u0026#39;\u0026#34;workflows\u0026#34;\u0026#39; \u0026#39;:\u0026#39; \u0026#39;[\u0026#39; ((workflows+=Workflow) (\u0026#39;,\u0026#39; workflows+=Workflow)*)? \u0026#39;]\u0026#39;)? // optional workflow children \u0026#39;}\u0026#39;; TYPE_WORKFLOW returns string: \u0026#39;\u0026#34;Workflow\u0026#34;\u0026#39;; Workflow: \u0026#39;{\u0026#39; IdentifiableFragment \u0026#39;,\u0026#39; \u0026#39;\u0026#34;name\u0026#34;\u0026#39; \u0026#39;:\u0026#39; name=STRING \u0026#39;,\u0026#39; \u0026#39;\u0026#34;type\u0026#34;\u0026#39; \u0026#39;:\u0026#39; type=TYPE_WORKFLOW \u0026#39;}\u0026#39;; TYPE_WORKFLOW_CONFIG returns string: \u0026#39;\u0026#34;WorkflowConfig\u0026#34;\u0026#39;; WorkflowConfig: \u0026#39;{\u0026#39; \u0026#39;\u0026#34;machine\u0026#34;\u0026#39; \u0026#39;:\u0026#39; machine=[Machine:STRING] // reference a machine defined somewhere else \u0026#39;,\u0026#39; \u0026#39;\u0026#34;workflow\u0026#34;\u0026#39; \u0026#39;:\u0026#39; workflow=[Workflow:STRING]; \u0026#39;,\u0026#39; \u0026#39;\u0026#34;type\u0026#34;\u0026#39; \u0026#39;:\u0026#39; type=TYPE_WORKFLOW_CONFIG \u0026#39;}\u0026#39;; hidden terminal WS: /\\s+/; // Ignore whitespaces during parsing terminal STRING: /\u0026#34;[^\u0026#34;]*\u0026#34;/; // JSON only supports double quoted strings This grammar would parse JSON content like:\n{ \u0026#34;id\u0026#34;: \u0026#34;my_machine\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Wonderful Machine\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;Machine\u0026#34;, \u0026#34;workflows\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;my_workflow\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Best Workflow\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;Workflow\u0026#34; } ] } and\n{ \u0026#34;machine\u0026#34;: \u0026#34;Wonderful Machine\u0026#34;, \u0026#34;workflow\u0026#34;: \u0026#34;Best Workflow\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;WorkflowConfig\u0026#34; } Customizing Language Behavior Langium\u0026rsquo;s flexibility allows you to customize its behavior through a dependency injection framework. For example, if you want references to use the id property instead of name:\nexport interface ModelServicesExtension { references: { /** override */ NameProvider: NameProvider; } } export type ModelLanguageServices = LangiumServices \u0026amp; ModelServicesExtension; export function createMyLangModule(context: { shared: ModelLanguagesSharedServices; }): Module\u0026lt;ModelLanguageServices, ModelServicesExtension\u0026gt; { return { references: { NameProvider: () =\u0026gt; new QualifiedIdProvider() } }; } // creating the services from the modules const shared = inject(createDefaultSharedModule(context), …); const myLanguage = inject(createDefaultModule({ shared }), createMyLangModule({ shared }), …); // usage: our NameProvider will be lazily created on access myLanguage.references.NameProvider.getName() The Model Hub offers pre-built modules to support JSON-based languages and provides shared services to simplify integration into the overall architecture. Future enhancements will include support for generating JSON-based grammars from TypeScript interfaces, making the creation of modeling languages even more efficient.\nLangium Integration: The Engine Behind Your Models To extend the Model Hub with your specific language, you need to provide a ModelServiceContribution that integrates your Langium-based DSL with the Model Hub ecosystem. This contribution can deliver a specialized Model Service API while enhancing the persistence and validation capabilities of the Model Hub.\nLeveraging Existing Infrastructure A core principle of the Model Hub architecture is efficient reuse of existing components. This approach is evident in how we utilize Langium\u0026rsquo;s infrastructure:\n Unified Process Architecture: Since the language server containing all language modules already runs in a dedicated process, we start our Model Hub server in the same process for seamless integration.\n Shared Dependency Injection: We reuse Langium\u0026rsquo;s dependency injection framework to bind Model Hub-specific services, including the core model hub implementation, model manager, command stack, model subscriptions, and validation services.\n The EMF Cloud-Langium Bridge The connection between the Model Hub ecosystem and Langium is our comprehensive EMF Cloud Model Hub Langium integration library, which consists of two primary components:\n AST Server: Acts as a facade to access and update semantic models from the Langium language server as a non-LSP client, providing a straightforward document lifecycle management system (open-request-update-save/close).\n Model Converter: Transforms between the Langium-based AST model and the client-facing JSON model, resolving cycles and handling cross-references to ensure complete bidirectional transformation.\n By using these components, implementing a language-specific ModelServiceContribution becomes straightforward — simply connect your Langium services with the Model Hub services and expose any additional functionality through your Model Service API.\nModel Persistence Implementation A model persistence contribution provides language-specific methods to load and store models. Here\u0026rsquo;s an example implementation:\nclass CustomPersistence implements ModelPersistenceContribution\u0026lt;string, CustomModelRoot\u0026gt; { modelHub: ModelHub; modelManager: ModelManager\u0026lt;string\u0026gt;; constructor(private modelServer: CustomModelServer) { } async canHandle(modelId: string): Promise\u0026lt;boolean\u0026gt; { return modelId.endsWith(\u0026#39;.custom\u0026#39;); } async loadModel(modelId: string): Promise\u0026lt;CustomModelRoot\u0026gt; { const model = await this.modelServer.getModel(modelId); if (model === undefined) { throw new Error(\u0026#39;Failed to load model: \u0026#39; + modelId); } this.modelServer.onUpdate(modelId, async newModel =\u0026gt; { try { // update model hub model const currentModel = await this.modelHub.getModel(modelId); const diff = compare(currentModel, newModel); if (diff.length === 0) { return; } const commandStack = this.modelManager.getCommandStack(modelId); const updateCommand = new PatchCommand(\u0026#39;Update Derived Values\u0026#39;, currentModel, diff); commandStack.execute(updateCommand); } catch (error) { console.error(\u0026#39;Failed to synchronize model from CustomLanguageService\u0026#39;, error); } }); return model; } async saveModel(modelId: string, model: CustomModelRoot): Promise\u0026lt;boolean\u0026gt; { try { await this.modelServer.save(modelId, model); } catch (error) { console.error(\u0026#39;Failed to save model\u0026#39; + modelId, error); return false; } return true; } } Model Validation A model validation contribution provides validators that work on the semantic model, returning hierarchical diagnostic objects that capture information, warnings, and errors. The translation from Langium\u0026rsquo;s DiagnosticInfo to the Model Hub\u0026rsquo;s Diagnostic format is the main task in this contribution.\nCross References The ModelHub handles cross-references through the AstLanguageModelConverter, converting Langium Reference objects to serializable ReferenceInfo objects:\nexport class DefaultAstLanguageModelConverter implements AstLanguageModelConverter { ... protected replacer(_source: AstNode, key: string, value: unknown): unknown { ... if (isReference(value)) { return this.replaceReference(value); } return value; } protected replaceReference(value: Reference\u0026lt;AstNode\u0026gt;): client.ReferenceInfo { return value.$nodeDescription \u0026amp;\u0026amp; value.ref ? { $documentUri: getDocument(value.ref).uri.toString(), $name: value.$nodeDescription.name, $path: value.$nodeDescription.path, $type: value.$nodeDescription.type } : { $refText: value.$refText, $error: value.error?.message ?? \u0026#39;Could not resolve reference: \u0026#39; + value.$refText }; } protected reviveNodeReference(container: AstNode, reference: client.NodeReferenceInfo): Reference { const node = this.resolveClientReference(container, reference); return { $refText: reference.$name, $nodeDescription: { documentUri: URI.parse(reference.$documentUri), name: reference.$name, path: reference.$path, type: reference.$type }, $refNode: node?.$cstNode }; } protected resolveClientReference(container: AstNode, reference: client.NodeReferenceInfo): AstNode | undefined { const uri = URI.parse(reference.$documentUri); const root = uri ? this.documents.getOrCreateDocument(uri).parseResult.value : container; return this.getAstNodeLocator(root.$document?.uri)?.getAstNode(root, reference.$path); } } Diagram Editor: Visualizing Models with GLSP Powerful Model Visualization and Editing The integration of diagram editors with Langium-based DSLs combines the intuitive nature of graphical modeling with the precision and expressiveness of textual DSLs. This powerful combination leverages the Eclipse GLSP framework for sophisticated diagram editing while using Langium to manage the underlying model representation and provide comprehensive language services.\nSeamless Integration Architecture A well-designed integration between GLSP diagram editors and Langium includes these key components:\n Unified Language Server: The Langium language server provides the authoritative model data and manages modifications to the underlying textual representation Bidirectional Transformation: Specialized components translate between the textual DSL structure (AST) and the graphical model representation Real-time Synchronization: Changes in either the diagram or text editor are instantly reflected across all views of the model Implementation Guidelines with Code Examples Establishing the Connection Connect your GLSP diagram editor with a Langium-based DSL using dependency injection:\n// Configure language services integration @inject(LangiumServices) protected languageServices: LangiumServices; // Setup document access @inject(LangiumDocumentFactory) protected documentFactory: LangiumDocumentFactory; Loading and Presenting Models Transform Langium\u0026rsquo;s textual representation into a visual diagram:\nasync loadSourceModel(action: RequestModelAction): Promise\u0026lt;void\u0026gt; { const modelUri = this.getUri(action); const model = await this.modelAPI.get(modelUri); // Store the model in GLSP\u0026#39;s model state this.modelState.setSemanticRoot(modelUri, model); this.modelAPI.subscribe(modelUri, () =\u0026gt; { this.actionDispatcher.dispatchAll(actions); }); } For a complete implementation of this integration pattern, refer to the CrossModel project:\nhttps://github.com/CrossBreezeNL/crossmodel\nReal-World Implementation For a comprehensive example of this architecture in action, explore the CrossModel tool:\nhttps://github.com/CrossBreezeNL/crossmodel\nTake the Next Step with Langium-Based DSLs The Langium-Based DSL Approach combines the precision of textual languages with the power of visual modeling tools, delivering a comprehensive solution for domain-specific modeling. By integrating directly with the Model Hub, your DSLs become part of a larger modeling ecosystem while maintaining their unique capabilities.\nReady to build your modeling language? Explore our examples and documentation to get started with this powerful approach.\n","permalink":"https://www.eclipse.dev/emfcloud/documentation/langiumapproach/","tags":null,"title":"Langium-Based DSL Approach"},{"categories":null,"contents":"The Model Hub Approach delivers a powerful, model-first solution for enterprise-grade modeling applications. Built around a centralized Model Hub, it provides advanced capabilities including command-based editing, comprehensive state management with undo/redo functionality, and robust persistence — making it the ideal foundation for scenarios demanding precise control and model-oriented workflow.\n Core Features \u0026amp; Benefits Command-based editing: Implement complex model operations through a structured command system, ensuring data integrity Centralized state management: Maintain consistent model state with powerful undo/redo capabilities Robust persistence layer: Deploy flexible storage strategies adaptable to your business requirements Real-time multi-client support: Enable seamless collaboration with synchronized updates across all clients Comprehensive model validation: Catch errors early with the built-in validation framework When to Choose This Approach The Model Hub approach delivers exceptional value for:\n Modeling tools requiring granular control over model changes Applications with complex editing operations needing comprehensive undo/redo capabilities Collaborative solutions where multiple users must work simultaneously Projects where structured model data (rather than plain text files) is your primary focus ModelHub: The Central Intelligence for Model Management The EMF Cloud Model Hub serves as the central coordination hub for model management, orchestrating multiple clients (like different editors) and their interactions with models. Beyond providing a generic model access API, the Model Hub is highly extensible to support various modeling languages.\nInteracting with models Applications can contain multiple model hubs, each with a unique context identifier. This allows for flexible organization based on application requirements.\nAccess the model hub for a specific context using the ModelHubProvider:\nimport { ModelHubProvider } from \u0026#39;@eclipse-emfcloud/model-service-theia/lib/node/model-hub-provider\u0026#39;; import { ModelHub } from \u0026#39;@eclipse-emfcloud/model-service\u0026#39;; @injectable() class ModelHubExample { @inject(ModelHubProvider) modelHubProvider: ModelHubProvider modelHub: ModelHub; async initializeModelHub() { this.modelHub = await modelHubProvider(\u0026#39;my-application-context\u0026#39;); } } Loading and saving models Load models using the model hub\u0026rsquo;s asynchronous API:\nconst modelId = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const model: object = await modelHub.getModel(modelId); With type safety:\nconst modelId = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const model: CustomModelRoot = await modelHub.getModel\u0026lt;CustomModelRoot\u0026gt;(modelId); Save models after changes:\n// Using modelId as the commandStackId for single-model operations const modelId = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const commandStackId = modelId; modelHub.save(commandStackId); Or save all modified models:\nmodelHub.save(); Resolving references ModelHub provides sophisticated handling of cross-references between model elements, representing them as structured information objects:\nexport interface NodeInfo { /** URI to the document containing the referenced element. */ $documentUri: string; /** Navigation path inside the document */ $path: string; /** `$type` property value */ $type: string; /** Name of element */ $name: string; /** Generic object properties */ [x: string]: unknown; } export interface ReferenceError { $refText: string; $error: string; } export type ReferenceInfo = NodeReferenceInfo | ReferenceError; On the client side, reference resolution is simplified with utility functions:\nexport type Reference\u0026lt;T\u0026gt; = Partial\u0026lt;NodeReferenceInfo\u0026gt; \u0026amp; Partial\u0026lt;ReferenceError\u0026gt; \u0026amp; { element(): Promise\u0026lt;T | undefined\u0026gt;; error(): string | undefined; }; export type ReferenceFactory\u0026lt;T\u0026gt; = (info: ReferenceInfo) =\u0026gt; Reference\u0026lt;T\u0026gt;; export function reviveReferences\u0026lt;T extends object\u0026gt;(obj: T, referenceFactory: ReferenceFactory\u0026lt;T\u0026gt;): T { for (const [key, value] of Object.entries(obj)) { if (isReferenceInfo(value)) { (obj as any)[key] = referenceFactory(value); } else if (value \u0026amp;\u0026amp; typeof value === \u0026#39;object\u0026#39;) { reviveReferences(value, referenceFactory); } } return obj; } Changing models Models are modified through specialized Model Services that implement domain-specific editing operations as Commands on a CommandStack:\nexport interface CustomModelService { getCustomModel(modelUri: string): Promise\u0026lt;CustomModelRoot | undefined\u0026gt;; unload(modelUri: string): Promise\u0026lt;void\u0026gt;; edit(modelUri: string, patch: Operation[]): Promise\u0026lt;PatchResult\u0026gt;; createNode(modelUri: string, parent: string | Workflow, args: CreateNodeArgs): Promise\u0026lt;PatchResult\u0026gt;; } export const CUSTOM_SERVICE_KEY = \u0026#39;customModelService\u0026#39;; // Access and use the model service const modelService: CustomModelService = modelHub.getModelService\u0026lt;CustomModelService\u0026gt;(CUSTOM_SERVICE_KEY); await modelService.createNode(modelId, \u0026#39;/workflows/0\u0026#39;, { type: \u0026#39;AutomaticTask\u0026#39; }); Validating models Model validation ensures data integrity through registered Validators:\nconst modelId = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const diagnostic = await modelHub.validateModels(modelId); Validate multiple models simultaneously:\nconst modelId1 = \u0026#39;file:///custom-editor/examples/workspace/foo.custom\u0026#39;; const modelId2 = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const diagnostic = await modelHub.validateModels(modelId1, modelId2); Or validate all currently loaded models:\nconst diagnostic = await modelHub.validateModels(); For accessing the latest known validation results without triggering revalidation:\nconst modelId = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const currentDiagnostic = modelHub.getValidationState(modelId); Contributing modeling languages Extend the ModelHub with custom modeling languages by implementing the ModelServiceContribution interface, which can handle:\n Persistence (Save/Load) Editing (Via Model Services and Commands) Validation Triggers Here\u0026rsquo;s a minimal example:\nexport const CUSTOM_SERVICE_KEY = \u0026#39;customModelService\u0026#39;; @injectable() export class CustomModelServiceContribution extends AbstractModelServiceContribution { private modelService: CustomModelService; @postConstruct() protected init(): void { this.initialize({ id: CUSTOM_SERVICE_KEY }) } getModelService\u0026lt;S\u0026gt;(): S { if (! this.modelService){ this.modelService = new CustomModelServiceImpl(); } return this.modelService as unknown as S; } } A more comprehensive implementation with persistence capabilities:\nexport const CUSTOM_SERVICE_KEY = \u0026#39;customModelService\u0026#39;; @injectable() export class CustomModelServiceContribution extends AbstractModelServiceContribution { private modelService: CustomLanguageModelService; constructor(@inject(CustomLanguageModelService) private languageService: CustomLanguageModelService){ // Empty constructor } @postConstruct() protected init(): void { this.initialize({ id: CUSTOM_SERVICE_KEY, persistenceContribution: new CustomPersistenceContribution(this.languageService) }); } getModelService\u0026lt;S\u0026gt;(): S { return this.modelService as unknown as S; } setModelManager(modelManager: ModelManager): void { super.setModelManager(modelManager); // Forward the model manager to our model service this.modelService = new CustomModelServiceImpl(modelManager, this.languageService); } } class CustomPersistenceContribution implements ModelPersistenceContribution { constructor(private languageService: CustomLanguageModelService) { // Empty } canHandle(modelId: string): Promise\u0026lt;boolean\u0026gt; { // Handle file URIs with \u0026#39;.custom\u0026#39; extension return Promise.resolve(modelId.startsWith(\u0026#39;file:/\u0026#39;) \u0026amp;\u0026amp; modelId.endsWith(\u0026#39;.custom\u0026#39;)); } async loadModel(modelId: string): Promise\u0026lt;object\u0026gt; { // Load model from file... } async saveModel(modelId: string, model: object): Promise\u0026lt;boolean\u0026gt; { // Save model to file... } } Persistence Register a ModelPersistenceContribution to handle model loading and saving:\n@postConstruct() protected init(): void { this.initialize({ id: CUSTOM_SERVICE_KEY, persistenceContribution: new CustomPersistenceContribution(this.languageService) }); } The contribution must implement three key methods:\nclass CustomPersistenceContribution implements ModelPersistenceContribution { constructor(private languageService: CustomLanguageModelService) { // Empty } canHandle(modelId: string): Promise\u0026lt;boolean\u0026gt; { // Handle file URIs with \u0026#39;.custom\u0026#39; extension return Promise.resolve(modelId.startsWith(\u0026#39;file:/\u0026#39;) \u0026amp;\u0026amp; modelId.endsWith(\u0026#39;.custom\u0026#39;)); } async loadModel(modelId: string): Promise\u0026lt;object\u0026gt; { // Load model from file... } async saveModel(modelId: string, model: object): Promise\u0026lt;boolean\u0026gt; { // Save model to file... } } Editing Domain Model editing is handled through the ModelManager, which is accessed via Model Services that execute Commands on CommandStacks:\nModel Service implementation The ModelService uses the ModelManager to execute Commands that modify models:\nexport class CustomModelServiceImpl implements CustomModelService { constructor(private modelManager: ModelManager\u0026lt;string\u0026gt;) {} async getCustomModel(modelUri: string): Promise\u0026lt;CustomModelRoot | undefined\u0026gt; { const key = getModelKey(modelUri); return this.modelManager.getModel\u0026lt;CustomModelRoot\u0026gt;(key); } async createNode(modelUri: string, parent: string | Workflow, args: CreateNodeArgs): Promise\u0026lt;PatchResult\u0026gt; { const model = await this.getCustomModel(modelUri); if (model === undefined) { return { success: false, error: `Failed to edit ${modelUri.toString()}: Model not found` }; } // Resolve parent element const parentPath = this.getParentPath(model, parent); const parentElement = getValueByPointer(model, parentPath); if (!isWorkflow(parentElement)) { throw new Error(`Parent element is not a Workflow: ${parentPath}`); } // Create new node const newNode = createNode(args.type, parentElement, args); // Create JSON patch const patch: Operation[] = [ { op: \u0026#39;add\u0026#39;, path: `${parentPath}/nodes/-`, value: newNode } ]; // Get command stack and execute command const stackId = getStackId(modelUri); const stack = this.modelManager.getCommandStack(stackId); const command = new PatchCommand(\u0026#39;Create Node\u0026#39;, modelUri, patch); const result = await stack.execute(command); // Return result const patchResult = result?.get(command); if (patchResult === undefined) { return { success: false, error: `Failed to edit ${modelUri.toString()}: Model edition failed` }; } return { success: true, patch: patchResult }; } } Alternative approaches for creating patch commands include using JSON Patch libraries:\n// Using fast-json-patch to generate a diff const updatedModel = deepClone(model) as CustomModelRoot; const updatedWorkflow = getValueByPointer(updatedModel, parentPath); updatedWorkflow.nodes.push(newNode); const patch: Operation[] = compare(model, updatedModel); const command = new PatchCommand(\u0026#39;Create Node\u0026#39;, modelUri, patch); Or using the built-in Model Updater for direct editing:\n// Using Model Updater for direct modification const command = new PatchCommand(\u0026#39;Create Node\u0026#39;, modelUri, model =\u0026gt; { const workflow = getValueByPointer(model, parentPath); const newNode = createNode(args.type, parentElement, args); workflow.nodes.push(newNode); }); Command Stack IDs Command Stack IDs define the scope of undo/redo operations. Typical scenarios include:\n One command stack per editor (for independent editing) Shared command stacks for interrelated models (for consistent state) Model Hub Context A Context defines the scope of a Model Hub instance, with completely independent contributions, model managers, and command stacks. Applications can define contexts based on:\n Project ID (for project isolation) Language ID (for language isolation) Or a single constant context for simpler applications Validators Register validators through a ValidationContribution:\n@postConstruct() protected init(): void { this.initialize({ id: CUSTOM_SERVICE_KEY, validationContribution: new CustomValidationContribution(this.languageService) }); } The ValidationContribution provides a list of Validators:\nclass CustomValidationContribution implements ModelValidationContribution { constructor(private languageService: CustomLanguageModelService){ // Empty } getValidators(): Validator[] { return [ new WorkflowValidator(), new TaskValidator() ]; } } class WorkflowValidator implements Validator\u0026lt;string\u0026gt; { async validate(modelId: string, model: object): Promise\u0026lt;Diagnostic\u0026gt; { // Type guard to ensure this validator only handles relevant models if (isWorkflow(modelId, model)){ // Validate workflow structure } else { return ok(); } } } class TaskValidator implements Validator\u0026lt;string\u0026gt; { async validate(modelId: string, model: object): Promise\u0026lt;Diagnostic\u0026gt; { if (isWorkflow(modelId, model)){ const tasks = model.nodes.filter( node =\u0026gt; node.type === \u0026#39;AutomaticTask\u0026#39; || node.type === \u0026#39;ManualTask\u0026#39;); for (const task of tasks){ // Validate each task } } else { return ok(); } } } Custom APIs Model Service Contributions expose public APIs for interaction with their models:\n// Model Service definition export interface CustomModelService { getCustomModel(modelUri: string): Promise\u0026lt;CustomModelRoot | undefined\u0026gt;; unload(modelUri: string): Promise\u0026lt;void\u0026gt;; edit(modelUri: string, patch: Operation[]): Promise\u0026lt;PatchResult\u0026gt;; createNode(modelUri: string, parent: string | Workflow, args: CreateNodeArgs): Promise\u0026lt;PatchResult\u0026gt;; } export const CUSTOM_SERVICE_KEY = \u0026#39;customModelService\u0026#39;; The API can be accessed by any model hub client:\nconst customModelService = modelHub.getModelService\u0026lt;CustomModelService\u0026gt;(CUSTOM_SERVICE_KEY); const modelUri = \u0026#39;file:///custom-editor/examples/workspace/my.custom\u0026#39;; const customModel = await customModelService.getModel(modelUri); const createArgs = { type: \u0026#39;AutomaticTask\u0026#39;, name: \u0026#39;new task\u0026#39; } await customModelService.createNode(modelUri, customModel.workflows[0], createArgs); Form Implementation Create sophisticated form based editors in Theia by leveraging JSON Forms and ModelHub. This integration provides a robust foundation for creating customized form editors with seamless data management through the Frontend Model Hub.\nOverview The Theia Tree Editor framework provides extensive base classes and service definitions that can be extended and implemented to create tailored editors for specific data requirements. For comprehensive details, see the official Theia Tree Editor documentation.\nIntegrating ModelHub Creating a Form Editor involves two key aspects:\n Data provisioning: Use the FrontendModelHub to fetch and manage models Change synchronization: Implement a ModelService to propagate editor changes back to the ModelHub Achieve this integration by injecting the FrontendModelHub into your constructor and using it within the init method to monitor model changes and update the form accordingly. Implement the handleFormUpdate method to capture and apply data changes to the ModelHub-tracked model.\nExample Your widget should integrate the ModelHub by adding a listener within the init method:\nthis.modelHub .subscribe(this.getResourceUri()?.toString() ?? \u0026#39;\u0026#39;) .then((subscription) =\u0026gt; { subscription.onModelChanged = (_modelId, model, _delta) =\u0026gt; { this.updateForm(model); }; }); This listener ensures your form editor stays synchronized with any model changes in real-time.\nAdditionally, implement a handleFormUpdate method to propagate editor changes to the model:\nprotected override async handleFormUpdate(data: any): Promise\u0026lt;void\u0026gt; { const result = await this.modelService.edit(this.getResourceUri()?.toString() ?? \u0026#39;\u0026#39;, data); this.updateForm(result.data); } This method ensures that any editor changes are immediately reflected in the model managed by the ModelHub.\nThe ModelService is a custom service with access to the ModelManager, see the Custom APIs section.\nDiagram Editor Implementation Introduction Create powerful, interactive diagram editors using the GLSP framework integrated with the ModelHub. This combination provides a seamless foundation for sophisticated diagramming tools with robust data management. The GLSP diagram editor leverages the ModelHub and ModelService interfaces for optimal performance and maintainability.\nModelHub Integration SourceModelStorage The source model storage handles persistence of source models — loading from and saving to the model state. A source model is the underlying data model from which the diagram\u0026rsquo;s graphical representation (graph model) is generated.\nWith ModelHub integration, these responsibilities are elegantly handled by the ModelHub itself, which provides:\n Model loading Subscription to model changes Dirty state tracking Model saving functionality GModelFactory The graph model factory transforms the source model (from the model state) into a graphical representation (GModelRoot). For complex transformations like creating edges, the CustomModelService provides reference resolution capabilities.\nModel Operations Operations on the model are forwarded to the CustomModelService, which provides specialized functions for model manipulation — such as dedicated create/delete functions that incorporate diagram-specific data like canvas positions.\nExample WorkflowModelStorage The WorkflowModelStorage demonstrates how to load and save source models via the ModelHub:\nLoad the source model Fetch the CustomModelRoot from the modelHub:\nthis.modelHub.getModel\u0026lt;CustomModelRoot\u0026gt;(modelUri); Detailed implementation async loadSourceModel(action: RequestModelAction): Promise\u0026lt;void\u0026gt; { const modelUri = this.getUri(action); const customModel = await this.modelHub.getModel\u0026lt;CustomModelRoot\u0026gt;(modelUri); if (!customModel \u0026amp;\u0026amp; customModel.workflows.length \u0026lt; 1)) { throw new GLSPServerError(\u0026#39;Expected Model with at least one workflow\u0026#39;); } this.modelState.setSemanticRoot(modelUri, customModel); this.subscribeToChanges(modelUri); } Subscribe to model changes Maintain real-time synchronization with model changes:\nthis.subscription = this.modelHub.subscribe(modelUri); this.subscription.onModelChanged = async (modelId: string, newModel: object) =\u0026gt; { // handle model update } Detailed implementation private subscribeToChanges(modelUri: string): void { this.subscription = this.modelHub.subscribe(modelUri); this.subscription.onModelChanged = async (modelId: string, newModel: object) =\u0026gt; { if (!newModel || newModel.workflows.length \u0026lt; 1) { throw new GLSPServerError(\u0026#39;Expected Model with at least one workflow\u0026#39;); } this.modelState.setSemanticRoot(modelId, newModel); const actions = await this.submissionHandler.submitModel(); const dirtyStateAction = actions.find(action =\u0026gt; action.kind === SetDirtyStateAction.KIND); if (dirtyStateAction \u0026amp;\u0026amp; SetDirtyStateAction.is(dirtyStateAction)) { dirtyStateAction.isDirty = this.modelHub.isDirty(this.modelState.semanticUri); } this.actionDispatcher.dispatchAll(actions); }; } Save model Trigger model saving through the ModelHub:\nthis.modelHub.save(modelUri) Detailed implementation async saveSourceModel(action: SaveModelAction): Promise\u0026lt;void\u0026gt; { const modelUri = action.fileUri ?? this.modelState.semanticUri; if (modelUri) { await this.modelHub.save(modelUri); } } CustomGModelFactory Resolve references using the CustomModelServer to properly translate the source model into a GModelRoot:\n@inject(CustomModelServer) protected modelServer: CustomModelServer; ... protected async createEdge(edge: Flow): Promise\u0026lt;GEdge\u0026gt; { const [sourceNode, targetNode] = await Promise.all([ this.modelServer.resolve\u0026lt;Node\u0026gt;(edge.source as Required\u0026lt;NodeReferenceInfo\u0026gt;), this.modelServer.resolve\u0026lt;Node\u0026gt;(edge.target as Required\u0026lt;NodeReferenceInfo\u0026gt;) ]); return GEdge.builder() .id(edge.id) .sourceId(sourceNode.id) .targetId(targetNode.id) .build(); } Model Operations via the CustomModelService Forward model operations to the CustomModelService for clean, maintainable code:\noverride createCommand(operation: CreateNodeOperation): MaybePromise\u0026lt;Command | undefined\u0026gt; { return new CustomModelCommand( this.modelState, this.serializer, () =\u0026gt; this.createWorkflowNode(operation) ); } async createWorkflowNode(operation: CreateNodeOperation): Promise\u0026lt;void\u0026gt; { const container = this.modelState.semanticRoot; const modelService = this.modelHub.getModelService\u0026lt;CustomModelService\u0026gt;(CUSTOM_SERVICE_KEY); const modelId = this.modelState.semanticUri; await modelService?.createNode(modelId, container.workflows[0], { posX: this.getLocation(operation)?.x ?? Point.ORIGIN.x, posY: this.getLocation(operation)?.y ?? Point.ORIGIN.y, type: this.getNodeType(operation) }); } This approach works for all diagram editor operations including deleting elements, resizing, repositioning, and editing labels.\nExample Implementation Explore detailed examples of the Model Hub approach in action:\nhttps://github.com/eclipse-emfcloud/modelhub/tree/main/examples/theia\nBe aware that the example does not cover all cases described above.\n","permalink":"https://www.eclipse.dev/emfcloud/documentation/modelhubapproach/","tags":null,"title":"Model Hub Approach"},{"categories":null,"contents":"EMF Model Server If you are not creating your modeling tool from scratch, but need to migrate an existing EMF-based model to a modern web-based modeling tool, EMF Cloud provides a dedicated model management component, called EMF Model Server. The EMF Model Server is written in Java and provides access to your EMF models, including manipulation, state management, undo and redo, via a generic REST API, as well as a JSON-RPC channel. This not only opens up accessing EMF models from web-based frontends and other components that aren\u0026rsquo;t written in Java, but also encapsulates your EMF dependency for future migrations.\nAlongside the EMF Model Server, there are also several components that simplify interacting with an EMF Model Server:\n Java-based EMF Model Server Client Typescript-based EMF Model Server Client Eclipse Theia integration of the EMF Model Server Eclipse GLSP Integration EMF Coffee Editor The EMF Coffee Editor provides a comprehensive example modeling tool that combines the EMF Model Server as well as all components mentioned above. The sources of the Coffee Editor are available under an open-source license and thus makes a great blueprint and starting point for your modeling tool project.\n This example provides several features:\n A custom Theia application frame Tree/form-based property editor Diagram editor Textual DSL Model analysis and visualization Code generation Go ahead and try out the coffee editor online!\nGetting Started To get you started quickly, we also provide project templates for the most popular choices including EMF Cloud and GLSP components.\nPlease see the following project-template and follow its README file.\n💾 Model Server ● 🖥️ Java ● 🗂️ EMF ● 🖼️ Theia \u0026ndash; modelserver-glspjava-emf-theia\nIf you need help, please raise a question in the Github discussions or look at our support options.\n","permalink":"https://www.eclipse.dev/emfcloud/documentation/emf/","tags":null,"title":"EMF Support"},{"categories":null,"contents":"Articles The EMF Cloud Model Server How to build a tree editor in Eclipse Theia A web-based modeling tool based on Eclipse Theia, EMF Cloud and GLSP Introducing EMF Cloud Web-based diagram editor features in Eclipse GLSP GLSP: Diagrams in VS Code, Theia, Eclipse and plain HTML Videos EclipseCon 2023: Building cloud-native (modeling) tools EclipseCon 2021: Model validation, diffing and more with EMF Cloud Eclipse Cloud Tool Time June 2021: Web-based tools - built with Eclipse (only) Eclipse Cloud Tool Time March 2021: Web-based modeling tools with EMF Cloud EclipseCon 2020: Ecore tools in the cloud - behind the scenes EclipseCon 2020: Diagram editors in the web with Eclipse GLSP EclipseCon 2019: Lifting the greatness of EMF into the cloud with EMF Cloud ","permalink":"https://www.eclipse.dev/emfcloud/documentation/additionals/","tags":null,"title":"More Articles \u0026 Videos"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/emfcloud/categories/","tags":null,"title":"Categories"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/emfcloud/contact/","tags":null,"title":"Contact"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/emfcloud/documentation/","tags":null,"title":"Documentation"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/emfcloud/","tags":null,"title":"EMF Cloud"},{"categories":null,"contents":"Support for EMF Cloud, to create new EMF Cloud components and for projects adopting EMF Cloud is provided by ","permalink":"https://www.eclipse.dev/emfcloud/support/","tags":null,"title":"Support"},{"categories":null,"contents":null,"permalink":"https://www.eclipse.dev/emfcloud/tags/","tags":null,"title":"Tags"}]