Skip to content

Repository files navigation

Eclipse EMF.cloud Model Server build-status-server

For more information, please visit the EMF.cloud Website. If you have questions, contact us on our discussions page and have a look at our communication and support options.

Important

The EMF.cloud Model Server is a development-time component intended to run locally in a trusted environment, driven by a trusted client. Its default configuration binds all network interfaces, enables permissive CORS, and uses no authentication. Do not expose it to untrusted networks. Before any networked or production deployment, read Intended Use, Deployment & Security Considerations.

Prerequisites

The following libraries/frameworks need to be installed on your system:

VersionRemark
Java>= 11We support the two latest LTS versions (11 and 17)
Maven>=3.8.6

Remark: There are build errors with the Maven version 3.8.5, which causes a build error for the p2 build.

Build

To build the model server as standalone JAR and execute all component tests execute the following maven goal in the root directory:

mvn clean install

Maven Repositories build-status-server

P2 Update Sites build-status-server

Code Coverage

The latest code coverage can be found here: org.eclipse.emfcloud.modelserver.codecoverage/jacoco/index.html.

The code coverage report is generated with JaCoCo and is integrated in the Maven build. In the package com.eclipsesource.modelserver.codecoverage all code coverages are aggregated into one report.

When executing the Maven build locally, the detailed results are computed and can be investigated in more detail.

Run

Execute from IDE

To run the example model server within an IDE, run the main method of ExampleServerLauncher.java as a Java Application, located in the module org.eclipse.emfcloud.modelserver.example.

Execute Standalone JAR

To run the model server standalone JAR, run this command in your terminal:

cd examples/org.eclipse.emfcloud.modelserver.example/target/
java -jar org.eclipse.emfcloud.modelserver.example-X.X.X-SNAPSHOT-standalone.jar

Warning

This starts an unauthenticated server bound to all network interfaces with permissive CORS, suitable for local development only. Anyone who can reach the port can read and write models and files accessible to the server process. Do not run it on an exposed or shared host without the protections described in Intended Use, Deployment & Security Considerations.

Usage

usage: java -jar org.eclipse.emfcloud.modelserver.example-X.X.X-SNAPSHOT-standalone.jar
[-e] [-h] [-l <arg>] [-p <arg>] [-r <arg>] [-u <arg>]
options:
-e,--enableDevLogging Enable Javalin dev logging (extensive request/response logging
meant for development)
-h,--help Display usage information about ModelServer
-l,--logConfig <arg> Set path to Log4j configuration file (*.xml)
-p,--port <arg> Set server port, otherwise a default port is used
-r,--root <arg> Set workspace root
-u,--uiSchemaUri <arg> Set UI schema folder uri

Logging

The default logging configuration for the ExampleServerLauncher is console output and the LogLevel is set to INFO.

To set the path to a custom configuration configuration file you can use the CLI argument -l,--logConfig.

The example offers such a configuration file which is used by the launch configuration ExampleServerLauncher.launch (-l=log4j2.xml).

This example configuration configures the logging behavior as follows: The default log level is set to INFO for the Log4j 2 logger itself and to DEBUG for the Model Server application logging.

  • Console logging: Level.FATAL and Level.ERROR write to stderr, all levels above write to stdout.
  • Rolling File Logger: Is configured to log on Level.DEBUG and outputs to the log file logs/emfcloud-modelserver.log.

Another example for an external log configuration can be found in the emfcloud-modelserver-theia repository.

For more information on the Log4j 2 configuration please visit the Log4j 2 manual.

Remark: There is a known issue with Log4j2 used in bundled JARs. This shows in not properly logging INFO lines in the (rolling) log file (if used). This can be solved by adding the following property to the ManifestResourceTransformer in the packaging phase within the maven-shade-plugin. See for example also in our example application org.eclipse.emfcloud.modelserver.example/pom.xml

<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>

Development logging

To enable extensive development logging in Javalin for http and websocket requests, you can use the CLI argument -e,--enableDevLogging.

It will output details for each request/response similar to the following snippet:

2022-01-01 00:00:00,000000000 [JettyServerThreadPool-25] INFO Javalin - JAVALIN REQUEST DEBUG LOG:
Request: GET [/api/v1/models]
Matching endpoint-handlers: [BEFORE=*, BEFORE=/api/v1/*, GET=/api/v1/models]
Headers: {Accept=*/*, User-Agent=PostmanRuntime/7.28.4, Connection=keep-alive, Postman-Token=..., Host=localhost:8081, Accept-Encoding=gzip, deflate, br}
Cookies: {}
Body:
QueryString: modeluri=SuperBrewer3000.coffee
QueryParams: {modeluri=[SuperBrewer3000.coffee]}
FormParams: {}
Response: [200], execution took 5.84 ms
Headers: {Date=Fri, 01 Jan 2022 00:00:00 GMT, Content-Type=application/json}
Body is 1015 bytes (starts on next line):
{
"type" : "success",
"data" : {
"eClass" : "http://www.eclipsesource.com/modelserver/example/coffeemodel#//Machine",
"children" : [ {
...
} ],
"name" : "Super Brewer 3000",
"workflows" : [ {
"name" : "Simple Workflow",
...
} ]
}
}

Model Server API

Parameters

  • The query parameter ?modeluri= accepts files in the loaded workspace as well as absolute file paths.
  • Parameters in brackets [] are optional.
    • If no format is specified, the default format is JSON.
    • [WebSocket] The parameter livevalidation defaults to false. If set to true the websocket will recieve validation results automatically on model changes.

HTTP Endpoints

The Model Server supports two versions of the API (v1 and v2). If the model server is up and running, you can access the model server v1 API via http://localhost:8081/api/v1/*, and the v2 API via http://localhost:8081/api/v2/*

HTTP Endpoints - V1

HTTP Endpoints - V1

The following table shows the current HTTP endpoints (v1):

CategoryDescriptionHTTP methodPathInput
ModelsGet all available models in the workspaceGET/modelsquery parameter: [?format=...]
Get modelGET/modelsquery parameter: ?modeluri=...[&format=...]
Create new modelPOST/modelsquery parameter: ?modeluri=...[&format=...]
application/json
Update modelPATCH/modelsquery parameter: ?modeluri=...[&format=...]
application/json
Delete modelDELETE/modelsquery parameter: ?modeluri=...
Close modelPOST/closequery parameter: ?modeluri=...
SaveGET/savequery parameter: ?modeluri=...
SaveAllGET/saveall-
UndoGET/undoquery parameter: ?modeluri=...
RedoGET/redoquery parameter: ?modeluri=...
Execute commandsPATCH/editquery parameter: ?modeluri=...
Get all available model URIs in the workspaceGET/modeluris-
Get model element by idGET/modelelementquery parameter: ?modeluri=...&elementid=...[&format=...]
Get model element by name
(Returns the first element that matches the given elementname)
GET/modelelementquery parameter: ?modeluri=...&elementname=...[&format=...]
JSON schemaGet the type schema of a model as a JSON schemaGET/typeschemaquery parameter: ?modeluri=...
Get the UI schema of a certain view elementGET/uischemaquery parameter: ?schemaname=...
Server actionsPing serverGET/server/ping-
Update server configurationPUT/server/configureapplication/json
Model ValidationValidate ModelGET/validationquery parameter: ?modeluri=...
Get list of constraintsGET/validation/constraintsquery parameter: ?modeluri=...

HTTP Endpoints - V2

The following table shows the current HTTP endpoints (v2):

CategoryDescriptionHTTP methodPathInput
ModelsGet all available models in the workspaceGET/modelsquery parameter: [?format=...]
Get modelGET/modelsquery parameter: ?modeluri=...[&format=...]
Create new modelPOST/modelsquery parameter: ?modeluri=...[&format=...]
application/json
Execute commandsPATCH/modelsquery parameter: ?modeluri=...[&format=...]
application/json
Replace modelPUT/modelsquery parameter: ?modeluri=...[&format=...]
application/json
Delete modelDELETE/modelsquery parameter: ?modeluri=...
Close modelPOST/closequery parameter: ?modeluri=...
SaveGET/savequery parameter: ?modeluri=...
SaveAllGET/saveall-
UndoGET/undoquery parameter: ?modeluri=...
RedoGET/redoquery parameter: ?modeluri=...
Get all available model URIs in the workspaceGET/modeluris-
Get model element by idGET/modelelementquery parameter: ?modeluri=...&elementid=...[&format=...]
Get model element by name
(Returns the first element that matches the given elementname)
GET/modelelementquery parameter: ?modeluri=...&elementname=...[&format=...]
JSON schemaGet the type schema of a model as a JSON schemaGET/typeschemaquery parameter: ?modeluri=...
Get the UI schema of a certain view elementGET/uischemaquery parameter: ?schemaname=...
Server actionsPing serverGET/server/ping-
Update server configurationPUT/server/configureapplication/json
Model ValidationValidate ModelGET/validationquery parameter: ?modeluri=...
Get list of constraintsGET/validation/constraintsquery parameter: ?modeluri=...
InternalCreate a transaction for auto-composing edits on a model with intermediate results. Returns the ID of the transaction for a websocket (see below)POST/transactionquery parameter: ?modeluri=...
No body content required

Server Configuration

Per default, updating the server configuration (/server/configure) with a new workspaceRoot, enables queueing of further incoming requests until configuration is completed. Please see ModelServerRouting for details.

WebSocket Endpoints

Subscriptions are implemented via websockets. For v1, ws://localhost:8081/api/v1/*. For v2, ws://localhost:8081/api/v2/*.

The following table shows the current WS endpoints common to both v1 and v2 APIs:

DescriptionPathInputReturns
Subscribe to model changes/subscribequery parameter: ?modeluri=...[&format=...][&timeout=...][&livevalidation=-...]sessionId

The following table shows messages accepted from a client on valid WS /subscribe connection:

TypeDescriptionExample message
keepAliveKeep WS connection alive if timeout is defined{ type: 'keepAlive', data: '' }

Websocket Endpoints — v2

The following table shows the WS endpoints added in the v2 API:

DescriptionPathInput
[Internal] Incremental edits with intermediate results/transaction/:idid returned from POST request on /transaction
query parameter: ?modeluri=...[&format=...]

The following table shows messages accepted from a client on the transaction/:id endpoint in the v2 API:

TypeDescriptionExample message
executeExecute an edit on the model, via EMF command or JSON Patch{ "type": "execute", "data": { "type": "modelserver.patch" ...}" }
closeClose the transaction, putting summary of all changes on the undo stack{ "type": "close" }
roll-backCancel the transaction, discarding all changes{ "type": "roll-back", "message": "Unexpected opening date for course enrollment." }

And messages from the server that clients will receive on the transaction/:id endpoint:

TypeDescriptionExample message
successSuccessful execution of command or JSON Patch{ "type": "success", "data": { "message": "Model successfully updated.", "patch": [ { "op": "add", ... }, ... ] } }

Java Client API

The model server project features a Java-based client API that eases integration with the model server. The interface declaration is as defined below. Please note that the Model class is a POJO with a model uri and content.

v1 Client API
publicinterfaceModelServerClientApiV1<A> {
CompletableFuture<Response<String>> get(StringmodelUri);
CompletableFuture<Response<A>> get(StringmodelUri, Stringformat);
CompletableFuture<Response<List<Model<String>>>> getAll();
CompletableFuture<Response<List<Model<A>>>> getAll(Stringformat);
CompletableFuture<Response<List<String>>> getModelUris();
CompletableFuture<Response<String>> getModelElementById(StringmodelUri, Stringelementid);
CompletableFuture<Response<A>> getModelElementById(StringmodelUri, Stringelementid, Stringformat);
CompletableFuture<Response<String>> getModelElementByName(StringmodelUri, Stringelementname);
CompletableFuture<Response<A>> getModelElementByName(StringmodelUri, Stringelementname, Stringformat);
CompletableFuture<Response<Boolean>> delete(StringmodelUri);
CompletableFuture<Response<Boolean>> close(StringmodelUri);
CompletableFuture<Response<String>> create(StringmodelUri, StringcreatedModelAsJsonText);
CompletableFuture<Response<A>> create(StringmodelUri, AcreatedModel, Stringformat);
CompletableFuture<Response<String>> update(StringmodelUri, StringupdatedModelAsJsonText);
CompletableFuture<Response<A>> update(StringmodelUri, AupdatedModel, Stringformat);
CompletableFuture<Response<Boolean>> save(StringmodelUri);
CompletableFuture<Response<Boolean>> saveAll();
CompletableFuture<Response<String>> validate(StringmodelUri);
CompletableFuture<Response<String>> getValidationConstraints(StringmodelUri);
CompletableFuture<Response<String>> getTypeSchema(StringmodelUri);
CompletableFuture<Response<String>> getUiSchema(StringschemaName);
CompletableFuture<Response<Boolean>> configure(ServerConfigurationconfiguration);
CompletableFuture<Response<Boolean>> ping();
CompletableFuture<Response<Boolean>> edit(StringmodelUri, CCommandcommand, Stringformat);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, longtimeout);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat, longtimeout);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener, longtimeout);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat,
longtimeout);
booleansend(StringmodelUri, Stringmessage);
booleanunsubscribe(StringmodelUri);
EditingContextedit();
booleanclose(EditingContexteditingContext);
CompletableFuture<Response<Boolean>> undo(StringmodelUri);
CompletableFuture<Response<Boolean>> redo(StringmodelUri);
}

v2 Client API:

publicinterfaceModelServerClientApiV2<A> {
CompletableFuture<Response<String>> get(StringmodelUri);
CompletableFuture<Response<A>> get(StringmodelUri, Stringformat);
CompletableFuture<Response<List<Model<String>>>> getAll();
CompletableFuture<Response<List<Model<A>>>> getAll(Stringformat);
CompletableFuture<Response<List<String>>> getModelUris();
CompletableFuture<Response<String>> getModelElementById(StringmodelUri, Stringelementid);
CompletableFuture<Response<A>> getModelElementById(StringmodelUri, Stringelementid, Stringformat);
CompletableFuture<Response<String>> getModelElementByName(StringmodelUri, Stringelementname);
CompletableFuture<Response<A>> getModelElementByName(StringmodelUri, Stringelementname, Stringformat);
CompletableFuture<Response<Boolean>> delete(StringmodelUri);
CompletableFuture<Response<Boolean>> close(StringmodelUri);
CompletableFuture<Response<String>> create(StringmodelUri, StringcreatedModelAsJsonText);
CompletableFuture<Response<A>> create(StringmodelUri, AcreatedModel, Stringformat);
CompletableFuture<Response<String>> update(StringmodelUri, StringupdatedModelAsJsonText);
CompletableFuture<Response<A>> update(StringmodelUri, AupdatedModel, Stringformat);
CompletableFuture<Response<Boolean>> save(StringmodelUri);
CompletableFuture<Response<Boolean>> saveAll();
CompletableFuture<Response<String>> validate(StringmodelUri);
CompletableFuture<Response<String>> getValidationConstraints(StringmodelUri);
CompletableFuture<Response<String>> getTypeSchema(StringmodelUri);
CompletableFuture<Response<String>> getUiSchema(StringschemaName);
CompletableFuture<Response<Boolean>> configure(ServerConfigurationconfiguration);
CompletableFuture<Response<Boolean>> ping();
CompletableFuture<Response<String>> edit(StringmodelUri, CCommandcommand, Stringformat);
CompletableFuture<Response<String>> edit(StringmodelUri, ArrayNodejsonPatch, Stringformat);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, longtimeout);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat, longtimeout);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener, longtimeout);
voidsubscribeWithValidation(StringmodelUri, SubscriptionListenersubscriptionListener, Stringformat,
longtimeout);
voidsubscribe(StringmodelUri, SubscriptionListenersubscriptionListener, SubscriptionOptionsoptions);
booleansend(StringmodelUri, Stringmessage);
booleanunsubscribe(StringmodelUri);
CompletableFuture<Response<String>> undo(StringmodelUri);
CompletableFuture<Response<String>> redo(StringmodelUri);
}
v2 Subscription Options API
publicinterfaceSubscriptionOptionsextendsSerializable {
StringgetFormat();
booleanisLiveValidation();
longgetTimeout();
StringgetPathScheme();
Map<String, String> getAdditionalOptions();
defaultbooleanhasAdditionalOptions() {
return !getAdditionalOptions().isEmpty();
}
staticBuilderbuilder() {
returnnewImpl.Builder();
}
interfaceBuilder {
BuilderwithFormat(Stringformat);
BuilderwithLiveValidation();
BuilderwithLiveValidation(booleanvalidation);
BuilderwithTimeout(finallongtimeout);
BuilderwithTimeout(longtimeout, TimeUnitunit);
BuilderwithPathScheme(StringpathScheme);
BuilderwithOption(Stringkey, Stringvalue);
SubscriptionOptionsbuild();
}
}

REST API Example

// You can customize the underlying okhttp instance by passing it in as a 1st parameterModelServerClientclient = newModelServerClient("http://localhost:8081/api/v2/");
// perform simple GETclient.get("SuperBrewer3000.json")
.thenAccept(response -> System.out.println("GET: " + response.body()));
// perform same GET, but obtain the result as an EObjectclient.get("SuperBrewer3000.json", "json-v2")
.thenAccept(response -> System.out.println("GET: " + response.body()));
// perform GET ALLclient.getAll()
.thenAccept(response -> System.out.println("GET ALL: " + response.body()));
// replace the model content via a PATCH updateEObjectcoffeeMachine = ...;
client.update("SuperBrewer3000.json", coffeeMachine, "json-v2")
.thenAccept(response -> System.out.println(response.body()));

Executing Commands

To perform changes on the model, clients may issue PATCH requests to update the model state incrementally in the server. These updates are broadcast to subscribers as incremental updates (see below).

Consider the following JSON payload for a PATCH request to add change the name of the workflow in the example Super Brewer 3000 model and to add another task to it:

v1 PATCH Commands
{
"eClass": "http://www.eclipse.org/emfcloud/modelserver/command#//CompoundCommand",
"type": "compound",
"commands": [
{
"eClass": "http://www.eclipse.org/emfcloud/modelserver/command#//Command",
"type": "set",
"owner": {
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"$ref": "SuperBrewer3000.json#//@workflows.0"
},
"feature": "name",
"dataValues": ["Auto Brew"]
},
{
"eClass": "http://www.eclipse.org/emfcloud/modelserver/command#//Command",
"type": "add",
"owner": {
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"$ref": "SuperBrewer3000.json#//@workflows.0"
},
"feature": "nodes",
"objectValues": [
{
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"$ref": "//@commands.1/@objectsToAdd.0"
}
],
"objectsToAdd": [
{
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"name": "Brew"
}
],
"indices": [1]
}
]
}

This is a JSON representation of an EMF CompoundCommand containing two commands, a SetCommand that changes the name of the first workflow in the model, and an AddCommand that adds a new AutomaticTask to that workflow. The SetCommand does not require any index because the name feature is single-valued. The AddCommand here explicitly adds an position 1, but this can also be omitted to simply append to the end of the list. Notice how each command indicates the owner object in the model to which the change is applied using a cross-document reference. And in the case of the AddCommand, the object to be added does not yet exist in the model, so it must be included in the payload of the command, itself. Thus it is contained in the objectsToAdd property and indicate via an in-document reference in the objectValues property. Other commands, such as the RemoveCommand, would indicate objects in the objectValues property that already exist in the model (to be removed in that case), and so those would be cross-document references and the objectsToAdd is unused.

To execute this command, issue a PATCH request to the edit endpoint like:

PATCH http://localhost:8081/api/v1/edit?modeluri=SuperBrewer3000.json
Content-type: application/json
{ "data" : <payload> }

The model server project already provides a default set of commands but it is also possible to plug in your custom metamodel-specific commands by providing CommandContributions specified with your model server module.

All commands are executed on a transactional command stack within an EMF transactional editing domain. The use of an EMF transactional editing domain on the server side provides a more reliable way of executing commands through transactions and therefore making a clear separation between the end user's operations. In addition, it enables us to make use of RecordingCommands which record the changes made to objects via the custom metamodel's API and therefore provide automatic undo/redo support for custom commands.

v2 Patch, using an EMF Command:

{
"type": "modelserver.emfcommand",
"data": {
"eClass": "http://www.eclipse.org/emfcloud/modelserver/command#//CompoundCommand",
"type": "compound",
"commands": [
{
"eClass": "http://www.eclipse.org/emfcloud/modelserver/command#//Command",
"type": "set",
"owner": {
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"$ref": "SuperBrewer3000.json#//@workflows.0"
},
"feature": "name",
"dataValues": [
"Auto Brew"
]
},
{
"eClass": "http://www.eclipse.org/emfcloud/modelserver/command#//Command",
"type": "add",
"owner": {
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"$ref": "SuperBrewer3000.json#//@workflows.0"
},
"feature": "nodes",
"objectValues": [
{
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"$ref": "//@commands.1/@objectsToAdd.0"
}
],
"objectsToAdd": [
{
"eClass": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"name": "Brew"
}
],
"indices": [
1
]
}
]
}
}

This is a JSON representation of an EMF CompoundCommand containing two commands, a SetCommand that changes the name of the first workflow in the model, and an AddCommand that adds a new AutomaticTask to that workflow. The SetCommand does not require any index because the name feature is single-valued. The AddCommand here explicitly adds an position 1, but this can also be omitted to simply append to the end of the list. Notice how each command indicates the owner object in the model to which the change is applied using a cross-document reference. And in the case of the AddCommand, the object to be added does not yet exist in the model, so it must be included in the payload of the command, itself. Thus it is contained in the objectsToAdd property and indicate via an in-document reference in the objectValues property. Other commands, such as the RemoveCommand, would indicate objects in the objectValues property that already exist in the model (to be removed in that case), and so those would be cross-document references and the objectsToAdd is unused.

To execute this command, issue a PATCH request to the models endpoint like:

PATCH http://localhost:8081/api/v2/models?modeluri=SuperBrewer3000.json
Content-type: application/json
{ "data" : <payload> }

The model server project already provides a default set of commands but it is also possible to plug in your custom metamodel-specific commands by providing CommandContributions specified with your model server module.

All commands are executed on a transactional command stack within an EMF transactional editing domain. The use of an EMF transactional editing domain on the server side provides a more reliable way of executing commands through transactions and therefore making a clear separation between the end user's operations. In addition, it enables us to make use of RecordingCommands which record the changes made to objects via the custom metamodel's API and therefore provide automatic undo/redo support for custom commands.

v2 Patch, using a Json Patch with EMF-like paths:

 {
"type": "modelserver.patch",
"data": [
{
"op": "replace",
"path": "SuperBrewer3000.json#//@workflows.0/name",
"value": "Auto Brew"
},
{
"op": "add",
"path": "SuperBrewer3000.json#//@workflows.0/nodes/-",
"value": {
"$type": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"name": "Brew"
}
}
]
}

This Json Patch is equivalent to the EMF Command above, and can be used in the same way. This is the recommended format to use for Web clients, as manipulating this Json Patch format is a lot easier than EMF Commands.

In this case, we still use an EMF-like path, which contains the URI of the model to edit, the ID of the Object to edit, and the feature to edit:

modeluri#objectID/featureName or modeluri#objectID/featureName/index (Where the special value - can be used to represent the last element of the list).

v2 Patch, using a Json Patch with standard Json Pointer paths:

{
"type": "modelserver.patch",
"data": [
{
"op": "replace",
"path": "/workflows/0/name",
"value": "Auto Brew"
},
{
"op": "add",
"path": "/workflows/0/nodes/-",
"value": {
"$type": "http://www.eclipsesource.com/modelserver/example/coffeemodel#//AutomaticTask",
"name": "Brew"
}
}
]
}

In this last case, we use standard Json Pointer paths, instead of EMF-like paths. The model URI is no longer part of the path, as this concept doesn't exist with Json Patch/Json Pointers. Instead, the ?modeluri= query parameter will be used. Currently, this format can't be used to edit multiple resources with a single operation.

WebSocket Subscriptions Example

If you want to be notified about any changes happening on a certain model, you can subscribe with a SubscriptionListener and define a format for the responses, which is an EObjectSubscriptionListener for json-v2 format in this example.

Please also see a basic running example in org.eclipse.emfcloud.modelserver.example.client.

ModelServerClientclient = newModelServerClient("http://localhost:8081/api/v2/");
StringsubscriptionId = "SuperBrewer3000.json";
client.subscribe(subscriptionId, newEObjectSubscriptionListener(newJsonCodecV2()) {
@OverridepublicvoidonOpen(finalResponse<String> response) {
System.out.println("Connected: " + response.getMessage());
}
@OverridepublicvoidonSuccess(finalOptional<String> message) {
System.out.println("Success: " + message.get());
}
@OverridepublicvoidonIncrementalUpdate(finalJsonPatchpatch) {
System.out.println("Patch update from model server received: " + patch.toString());
}
@OverridepublicvoidonDirtyChange(finalbooleanisDirty) {
System.out.println("Dirty State: " + isDirty);
}
@OverridepublicvoidonUnknown(finalModelServerNotificationnotification) {
System.out.println("Unknown notification of type " + notification.getType() + ": " + notification.getData());
}
@OverridepublicvoidonFullUpdate(finalEObjectfullUpdate) {
System.out.println("Full <EObject> update from model server received: " + fullUpdate.toString());
}
@OverridepublicvoidonError(finalOptional<String> message) {
System.out.println("Error from model server received: " + message.get());
}
@OverridepublicvoidonFailure(finalThrowablet, finalResponse<String> response) {
System.out.println("Failure: " + response.getMessage());
t.printStackTrace();
}
@OverridepublicvoidonFailure(finalThrowablet) {
System.out.println("Failure: ");
t.printStackTrace();
}
@OverridepublicvoidonClosing(finalintcode, finalStringreason) {
System.out.println("Closing connection to model server, reason: " + reason);
}
@OverridepublicvoidonClosed(finalintcode, finalStringreason) {
System.out.println("Closed connection to model server, reason: " + reason);
}
});
// ...client.unsubscribe(subscriptionId);

The kind of message received depends on the operation. For an update call (PATCH request on the model), the message is the new content of the model (onFullUpdate). For an edit call (incremental update applied by a PATCH request with an edit command or JSON patch — see above), the message is the result of the command that was executed (onIncrementalUpdate). In the case of an API v2 client with json-v2 message format, the incremental update takes the form of a JSON patch describing the changes performed on the server. The patch can be applied to a local copy of the model to synchronize with the server and is modeled in EMF as a JsonPatch object.

Subscriptions support a number of options to tweak their behaviour. For example, to customize the idle timeout interval and receive incremental updates in which the Operations in the JSON Patches use EObject fragment URIs in the path property instead of standard JSON Pointers:

ModelServerClientclient = newModelServerClient("http://localhost:8081/api/v2/");
StringsubscriptionId = "SuperBrewer3000.json";
SubscriptionListenerlistener = /* as above */ ;
client.subscribe(subscriptionId, listener, SubscriptionOptions.builder()
.withTimeout(60, TimeUnit.SECONDS)
.withPathScheme(ModelServerPathParametersV2.PATHS_URI_FRAGMENTS)
.build());

Contributing

All involved code must adhere to the provided codestyle and checkstyle settings.

Eclipse IDE Setup

Requirements

  • Please make sure your Eclipse workspace uses a JRE of Java 11 or higher.
  • Install the Eclipse Checkstyle Plug-in via its update site https://checkstyle.org/eclipse-cs/#!/install.

Configure Checkstyle

This project uses the common checkstyle ruleset from EMF.cloud. Please follow the instructions for usage in Eclipse to configure this ruleset for a new project. To configure Checkstyle for a new project in the same workspace your can also right click on the project, choose Checkstyle > Configure project(s) from blueprint... and select org.eclipse.emfcloud.modelserver.common as blueprint project. Run Checkstyle > Check Code with Checkstyle to make sure Checkstyle is activated correctly.

Import Existing Projects

Import all maven projects via File > Import... > Maven > Existing Maven Projects > Root directory: $REPO_LOCATION. You may skip the parent modules (i.e. org.eclipse.emfcloud.modelserver.*.parent).

Create New Project

When a new project is needed, please stick to the following instructions to guarantee your code will be conform to the existing code conventions.

Project-Specific settings

Upon project creation the settings file org.eclipse.resources.prefs is created automatically and usually needs no further adjustment. Please copy and replace (if applicable) the following preferences files from org.eclipse.emfcloud.modelserver.common before you start coding:

  • org.eclipse.jdt.core.prefs
  • org.eclipse.jdt.launching.prefs
  • org.eclipse.jdt.ui.prefs
  • org.eclipse.m2e.core.prefs

Commit Changes

Please make sure to include the .settings folder as well as the .checkstyle settings file to the repository in your initial commit.

About

Modelserver component

Resources

Code of conduct

Contributing

Security policy

Stars

45 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages