Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collab advanced #241

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import org.eclipse.glsp.server.model.GModelState;

public interface EMFModelState extends GModelState {
void setEditingDomain(EditingDomain editingDomain);
void setEditingDomain(EditingDomain editingDomain, String subclientId);

EditingDomain getEditingDomain();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.glsp.graph.GModelIndex;
import org.eclipse.glsp.graph.GModelRoot;
import org.eclipse.glsp.server.command.CommandStackManager;
import org.eclipse.glsp.server.model.DefaultGModelState;
import org.eclipse.glsp.server.session.ClientSession;
import org.eclipse.glsp.server.session.ClientSessionListener;
Expand All @@ -50,6 +52,9 @@ public class EMFModelStateImpl extends DefaultGModelState implements EMFModelSta
@Inject
protected ClientSessionManager clientSessionManager;

@Inject
protected CommandStackManager commandStackManager;

@Inject
protected EMFIdGenerator idGenerator;

Expand All @@ -62,9 +67,9 @@ public void init() {
}

@Override
public void setEditingDomain(final EditingDomain editingDomain) {
public void setEditingDomain(final EditingDomain editingDomain, final String subclientId) {
this.editingDomain = editingDomain;
setCommandStack(this.editingDomain.getCommandStack());
commandStackManager.setCommandStack(this.editingDomain.getCommandStack(), subclientId);
}

@Override
Expand Down Expand Up @@ -107,8 +112,12 @@ protected void closeResourceSet() {
}
}
if (result) {
commandStack.flush();
saveIsDone();
commandStackManager.getAllCommandStacks().forEach(commandStack -> {
commandStack.flush();
if (commandStack instanceof BasicCommandStack) {
((BasicCommandStack) commandStack).saveIsDone();
}
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ public void loadSourceModel(final RequestModelAction action) {
.orElseThrow(() -> new GLSPServerException("No source URI given to load model!"));
URI resourceURI = URI.createFileURI(sourceURI);

EditingDomain editingDomain = getOrCreateEditingDomain();
EditingDomain editingDomain = getOrCreateEditingDomain(action.getSubclientId());
doLoadSourceModel(editingDomain.getResourceSet(), resourceURI, action);
}

protected EditingDomain getOrCreateEditingDomain() {
protected EditingDomain getOrCreateEditingDomain(final String subclientId) {
if (modelState.getEditingDomain() != null) {
return modelState.getEditingDomain();
}
EditingDomain editingDomain = editingDomainFactory.createEditingDomain();
setupResourceSet(editingDomain.getResourceSet());
modelState.setEditingDomain(editingDomain);
modelState.setEditingDomain(editingDomain, subclientId);
return editingDomain;
}

Expand Down
1 change: 1 addition & 0 deletions plugins/org.eclipse.glsp.server/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Export-Package: org.eclipse.glsp.server.actions,
org.eclipse.glsp.server.features.validation,
org.eclipse.glsp.server.gmodel,
org.eclipse.glsp.server.gson,
org.eclipse.glsp.server.command,
org.eclipse.glsp.server.internal.actions;x-internal:=true,
org.eclipse.glsp.server.internal.di.scope;x-internal:=true,
org.eclipse.glsp.server.internal.diagram;x-internal:=true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,20 @@ public abstract class Action {
*/
private boolean receivedFromClient;

public Action(final String kind) {
/**
* Unique identifier specifying the initiator of the action in a collaboration session.
* This value is initialized on the initating client.
*/
private String subclientId;

public Action(final String kind, final String subclientId) {
super();
this.kind = kind;
this.subclientId = subclientId;
}

public Action(final String kind) {
this(kind, null);
}

public String getKind() { return kind; }
Expand All @@ -51,6 +62,8 @@ public Action(final String kind) {

public void setReceivedFromClient(final boolean receivedFromClient) { this.receivedFromClient = receivedFromClient; }

public String getSubclientId() { return subclientId; }

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
Expand All @@ -60,4 +73,11 @@ public String toString() {
return builder.toString();
}

public static Action addSubclientId(final Action initialAction, final Action extendedAction) {
if (initialAction.getSubclientId() != null) {
extendedAction.subclientId = initialAction.subclientId;
}
return extendedAction;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public List<Action> executeAction(final SaveModelAction action) {
} finally {
modelSourceWatcher.ifPresent(watcher -> watcher.continueWatching());
}
return listOf(new SetDirtyStateAction(modelState.isDirty(), SetDirtyStateAction.Reason.SAVE));
return listOf(
new SetDirtyStateAction(modelState.isDirty(), SetDirtyStateAction.Reason.SAVE));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.eclipse.glsp.server.command;

Check warning on line 1 in plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/command/CommandStackFactory.java

View check run for this annotation

Jenkins - GLSP / CheckStyle

RegexpHeaderCheck

NORMAL: Line does not match expected header line of '^\/\*{2,80}$'.
Raw output
<p>Since Checkstyle 6.9</p><p> Checks the header of a source file against a header that contains a <a href="https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">regular expression</a> for each line of the source header. </p><p> Rationale: In some projects <a href="#Header">checking against a fixed header</a> is not sufficient, e.g. the header might require a copyright line where the year information is not static. </p><p> For example, consider the following header: </p><pre><code> line 1: ^/{71}$ line 2: ^// checkstyle:$ line 3: ^// Checks Java source code for adherence to a set of rules\.$ line 4: ^// Copyright \(C\) \d\d\d\d Oliver Burn$ line 5: ^// Last modification by \$Author.*\$$ line 6: ^/{71}$ line 7: line 8: ^package line 9: line 10: ^import line 11: line 12: ^/\*\* line 13: ^ \*([^/]|$) line 14: ^ \*/ </code></pre><p> Lines 1 and 6 demonstrate a more compact notation for 71 '/' characters. Line 4 enforces that the copyright notice includes a four digit year. Line 5 is an example how to enforce revision control keywords in a file header. Lines 12-14 is a template for javadoc (line 13 is so complicated to remove conflict with and of javadoc comment). Lines 7, 9 and 11 will be treated as '^$' and will forcefully expect the line to be empty. </p><p> Different programming languages have different comment syntax rules, but all of them start a comment with a non-word character. Hence you can often use the non-word character class to abstract away the concrete comment syntax and allow checking the header for different languages with a single header definition. For example, consider the following header specification (note that this is not the full Apache license header): </p><pre><code> line 1: ^#! line 2: ^&lt;\?xml.*&gt;$ line 3: ^\W*$ line 4: ^\W*Copyright 2006 The Apache Software Foundation or its licensors, as applicable\.$ line 5: ^\W*Licensed under the Apache License, Version 2\.0 \(the "License"\);$ line 6: ^\W*$ </code></pre><p> Lines 1 and 2 leave room for technical header lines, e.g. the "#!/bin/sh" line in Unix shell scripts, or the XML file header of XML files. Set the multiline property to "1, 2" so these lines can be ignored for file types where they do no apply. Lines 3 through 6 define the actual header content. Note how lines 2, 4 and 5 use escapes for characters that have special regexp semantics. </p>

import org.eclipse.emf.common.command.CommandStack;

public interface CommandStackFactory {
CommandStack createCommandStack();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.eclipse.glsp.server.command;

Check warning on line 1 in plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/command/CommandStackManager.java

View check run for this annotation

Jenkins - GLSP / CheckStyle

RegexpHeaderCheck

NORMAL: Line does not match expected header line of '^\/\*{2,80}$'.
Raw output
<p>Since Checkstyle 6.9</p><p> Checks the header of a source file against a header that contains a <a href="https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">regular expression</a> for each line of the source header. </p><p> Rationale: In some projects <a href="#Header">checking against a fixed header</a> is not sufficient, e.g. the header might require a copyright line where the year information is not static. </p><p> For example, consider the following header: </p><pre><code> line 1: ^/{71}$ line 2: ^// checkstyle:$ line 3: ^// Checks Java source code for adherence to a set of rules\.$ line 4: ^// Copyright \(C\) \d\d\d\d Oliver Burn$ line 5: ^// Last modification by \$Author.*\$$ line 6: ^/{71}$ line 7: line 8: ^package line 9: line 10: ^import line 11: line 12: ^/\*\* line 13: ^ \*([^/]|$) line 14: ^ \*/ </code></pre><p> Lines 1 and 6 demonstrate a more compact notation for 71 '/' characters. Line 4 enforces that the copyright notice includes a four digit year. Line 5 is an example how to enforce revision control keywords in a file header. Lines 12-14 is a template for javadoc (line 13 is so complicated to remove conflict with and of javadoc comment). Lines 7, 9 and 11 will be treated as '^$' and will forcefully expect the line to be empty. </p><p> Different programming languages have different comment syntax rules, but all of them start a comment with a non-word character. Hence you can often use the non-word character class to abstract away the concrete comment syntax and allow checking the header for different languages with a single header definition. For example, consider the following header specification (note that this is not the full Apache license header): </p><pre><code> line 1: ^#! line 2: ^&lt;\?xml.*&gt;$ line 3: ^\W*$ line 4: ^\W*Copyright 2006 The Apache Software Foundation or its licensors, as applicable\.$ line 5: ^\W*Licensed under the Apache License, Version 2\.0 \(the "License"\);$ line 6: ^\W*$ </code></pre><p> Lines 1 and 2 leave room for technical header lines, e.g. the "#!/bin/sh" line in Unix shell scripts, or the XML file header of XML files. Set the multiline property to "1, 2" so these lines can be ignored for file types where they do no apply. Lines 3 through 6 define the actual header content. Note how lines 2, 4 and 5 use escapes for characters that have special regexp semantics. </p>

import org.eclipse.emf.common.command.CommandStack;

import java.util.List;

public interface CommandStackManager {
CommandStack getOrCreateCommandStack(String subclientId);

List<CommandStack> getAllCommandStacks();

void setCommandStack(CommandStack commandStack, String subclientId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.eclipse.glsp.server.command;

Check warning on line 1 in plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/command/DefaultCommandStackManager.java

View check run for this annotation

Jenkins - GLSP / CheckStyle

RegexpHeaderCheck

NORMAL: Line does not match expected header line of '^\/\*{2,80}$'.
Raw output
<p>Since Checkstyle 6.9</p><p> Checks the header of a source file against a header that contains a <a href="https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">regular expression</a> for each line of the source header. </p><p> Rationale: In some projects <a href="#Header">checking against a fixed header</a> is not sufficient, e.g. the header might require a copyright line where the year information is not static. </p><p> For example, consider the following header: </p><pre><code> line 1: ^/{71}$ line 2: ^// checkstyle:$ line 3: ^// Checks Java source code for adherence to a set of rules\.$ line 4: ^// Copyright \(C\) \d\d\d\d Oliver Burn$ line 5: ^// Last modification by \$Author.*\$$ line 6: ^/{71}$ line 7: line 8: ^package line 9: line 10: ^import line 11: line 12: ^/\*\* line 13: ^ \*([^/]|$) line 14: ^ \*/ </code></pre><p> Lines 1 and 6 demonstrate a more compact notation for 71 '/' characters. Line 4 enforces that the copyright notice includes a four digit year. Line 5 is an example how to enforce revision control keywords in a file header. Lines 12-14 is a template for javadoc (line 13 is so complicated to remove conflict with and of javadoc comment). Lines 7, 9 and 11 will be treated as '^$' and will forcefully expect the line to be empty. </p><p> Different programming languages have different comment syntax rules, but all of them start a comment with a non-word character. Hence you can often use the non-word character class to abstract away the concrete comment syntax and allow checking the header for different languages with a single header definition. For example, consider the following header specification (note that this is not the full Apache license header): </p><pre><code> line 1: ^#! line 2: ^&lt;\?xml.*&gt;$ line 3: ^\W*$ line 4: ^\W*Copyright 2006 The Apache Software Foundation or its licensors, as applicable\.$ line 5: ^\W*Licensed under the Apache License, Version 2\.0 \(the "License"\);$ line 6: ^\W*$ </code></pre><p> Lines 1 and 2 leave room for technical header lines, e.g. the "#!/bin/sh" line in Unix shell scripts, or the XML file header of XML files. Set the multiline property to "1, 2" so these lines can be ignored for file types where they do no apply. Lines 3 through 6 define the actual header content. Note how lines 2, 4 and 5 use escapes for characters that have special regexp semantics. </p>

import com.google.inject.Inject;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.glsp.server.utils.CollaborationUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DefaultCommandStackManager implements CommandStackManager {

@Inject
CommandStackFactory factory;

Check warning on line 15 in plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/command/DefaultCommandStackManager.java

View check run for this annotation

Jenkins - GLSP / CheckStyle

VisibilityModifierCheck

NORMAL: Variable 'factory' must be private and have accessor methods.
Raw output
<p>Since Checkstyle 3.0</p><p> Checks visibility of class members. Only static final, immutable or annotated by specified annotation members may be public; other class members must be private unless the property <code>protectedAllowed</code> or <code>packageAllowed</code> is set. </p><p> Public members are not flagged if the name matches the public member regular expression (contains <code>"^serialVersionUID$"</code> by default). </p><p>Note that Checkstyle 2 used to include <code>"^f[A-Z][a-zA-Z0-9]*$"</code> in the default pattern to allow names used in container-managed persistence for Enterprise JavaBeans (EJB) 1.1 with the default settings. With EJB 2.0 it is no longer necessary to have public access for persistent fields, so the default has been changed. </p><p> Rationale: Enforce encapsulation. </p><p> Check also has options making it less strict: </p><p><b>ignoreAnnotationCanonicalNames</b> - the list of annotations which ignore variables in consideration. If user will provide short annotation name that type will match to any named the same type without consideration of package </p><p><b>allowPublicFinalFields</b> - which allows public final fields. Default value is <b>false</b></p><p><b>allowPublicImmutableFields</b> - which allows immutable fields to be declared as public if defined in final class. Default value is <b>false</b></p><p> Field is known to be immutable if: - It's declared as final - Has either a primitive type or instance of class user defined to be immutable (such as String, ImmutableCollection from Guava and etc) </p><p> Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their <b>canonical</b> names. </p><p> Rationale: Forcing all fields of class to have private modified by default is good in most cases, but in some cases it drawbacks in too much boilerplate get/set code. One of such cases are immutable classes. </p><p><b>Restriction</b>: Check doesn't check if class is immutable, there's no checking if accessory methods are missing and all fields are immutable, we only check <b>if current field is immutable or final</b>. Under the flag <b>allowPublicImmutableFields</b>, the enclosing class must also be final, to encourage immutability. Under the flag <b>allowPublicFinalFields</b>, the final modifier on the enclosing class is optional. </p><p> Star imports are out of scope of this Check. So if one of type imported via <b>star import</b> collides with user specified one by its short name - there won't be Check's violation. </p>

// subclientId, CommandStack
protected Map<String, CommandStack> commandStackMap = new HashMap<>();

@Override
public CommandStack getOrCreateCommandStack(final String subclientId) {
String subclientIdOrFallback = getSubclientIdOrFallback(subclientId);
if (commandStackMap.containsKey(subclientIdOrFallback)) {
return commandStackMap.get(subclientIdOrFallback);
}

CommandStack commandStack = factory.createCommandStack();
commandStackMap.put(subclientIdOrFallback, commandStack);
return commandStack;
}

@Override
public List<CommandStack> getAllCommandStacks() {
return new ArrayList<>(commandStackMap.values());
}

@Override
public void setCommandStack(final CommandStack commandStack, final String subclientId) {
String subclientIdOrFallback = getSubclientIdOrFallback(subclientId);
if (commandStackMap.containsKey(subclientIdOrFallback)) {
commandStackMap.get(subclientIdOrFallback).flush();
}
commandStackMap.put(subclientIdOrFallback, commandStack);
}

private String getSubclientIdOrFallback(final String subclientId) {
if (subclientId != null) {
return subclientId;
}
return CollaborationUtil.FALLBACK_SUBCLIENT_ID;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@
import org.eclipse.glsp.server.gson.GraphGsonConfigurationFactory;
import org.eclipse.glsp.server.internal.actions.DefaultActionDispatcher;
import org.eclipse.glsp.server.internal.actions.DefaultActionHandlerRegistry;
import org.eclipse.glsp.server.command.CommandStackFactory;
import org.eclipse.glsp.server.command.CommandStackManager;
import org.eclipse.glsp.server.command.DefaultCommandStackManager;
import org.eclipse.glsp.server.internal.diagram.DefaultServerConfigurationContribution;
import org.eclipse.glsp.server.internal.featues.directediting.DefaultContextEditValidatorRegistry;
import org.eclipse.glsp.server.internal.featues.navigation.DefaultNavigationTargetProviderRegistry;
import org.eclipse.glsp.server.internal.features.contextactions.DefaultContextActionsProviderRegistry;
import org.eclipse.glsp.server.internal.gmodel.commandstack.GModelCommandStackFactory;
import org.eclipse.glsp.server.internal.gson.DefaultGraphGsonConfigurationFactory;
import org.eclipse.glsp.server.internal.operations.DefaultOperationHandlerRegistry;
import org.eclipse.glsp.server.internal.toolpalette.DefaultToolPaletteItemProvider;
Expand Down Expand Up @@ -189,6 +193,10 @@ protected void configureBase() {
bindOptionally(LayoutEngine.class, bindLayoutEngine());
bindOptionally(GraphExtension.class, bindGraphExtension());
bindOptionally(EdgeCreationChecker.class, bindEdgeCreationChecker());

// Command Stack
bind(CommandStackFactory.class).to(bindCommandStackFactory()).in(Singleton.class);
bind(CommandStackManager.class).to(bindCommandStackManager()).in(Singleton.class);
}

protected void bindDiagramType() {
Expand Down Expand Up @@ -328,5 +336,13 @@ protected Class<? extends EdgeCreationChecker> bindEdgeCreationChecker() {
return null;
}

protected Class<? extends CommandStackFactory> bindCommandStackFactory() {
return GModelCommandStackFactory.class;
}

protected Class<? extends CommandStackManager> bindCommandStackManager() {
return DefaultCommandStackManager.class;
}

public abstract String getDiagramType();
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public List<Action> submitInitialModel(final RequestModelAction requestAction) {
* Therefore we temporarily store the action later retrival
*/
this.requestModelAction = Optional.of(requestAction);
return submitModel();
return submitModel(requestAction.getSubclientId());

}

Expand Down Expand Up @@ -126,7 +126,7 @@ public List<Action> submitModel() {
* Returns a list of actions to directly update the client-side model without any server- or client-side layouting.
* <p>
* Typically {@link ActionHandler action handlers} don't invoke this method but use
* {@link #submitModel(String)}
* {@link #submitModel(String, String)}
* instead, as this is only used to eventually submit the model on the client directly after all layouting is already
* performed before. The only foreseen caller of this method is {@link ComputedBoundsActionHandler}.
* </p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,23 @@ public class RequestModelActionHandler extends AbstractActionHandler<RequestMode

@Override
public List<Action> executeAction(final RequestModelAction action) {
modelState.setClientOptions(action.getOptions());

boolean isReconnecting = ClientOptionsUtil.isReconnecting(action.getOptions());

ProgressMonitor monitor = notifyStartLoading();
if (isReconnecting) {
handleReconnect(action);
} else {
sourceModelStorage.loadSourceModel(action);
}
notifyFinishedLoading(monitor);

if (!isReconnecting) {
sourceModelWatcher.ifPresent(watcher -> watcher.startWatching());
// only reload if not initialized
if (!ClientOptionsUtil.disableReloadIsTrue(action.getOptions()) || modelState.getRoot() == null) {
modelState.setClientOptions(action.getOptions());

boolean isReconnecting = ClientOptionsUtil.isReconnecting(action.getOptions());

ProgressMonitor monitor = notifyStartLoading();
if (isReconnecting) {
handleReconnect(action);
} else {
sourceModelStorage.loadSourceModel(action);
}
notifyFinishedLoading(monitor);

if (!isReconnecting) {
sourceModelWatcher.ifPresent(watcher -> watcher.startWatching());
}
}

return modelSubmissionHandler.submitInitialModel(action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.glsp.graph.GModelElement;
import org.eclipse.glsp.graph.GNode;
import org.eclipse.glsp.graph.GPoint;
import org.eclipse.glsp.server.actions.Action;
import org.eclipse.glsp.server.actions.ActionDispatcher;
import org.eclipse.glsp.server.actions.SelectAction;
import org.eclipse.glsp.server.operations.AbstractCreateOperationHandler;
Expand Down Expand Up @@ -59,7 +60,10 @@ public void executeOperation(final CreateNodeOperation operation) {
.map(location -> LayoutUtil.getRelativeLocation(location, container));
GModelElement element = createNode(relativeLocation, operation.getArgs());
container.getChildren().add(element);
actionDispatcher.dispatchAfterNextUpdate(new SelectAction(), new SelectAction(List.of(element.getId())));
actionDispatcher.dispatchAfterNextUpdate(
Action.addSubclientId(operation, new SelectAction()),
Action.addSubclientId(operation, new SelectAction(List.of(element.getId())))
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.glsp.graph.GModelElement;
import org.eclipse.glsp.graph.GNode;
import org.eclipse.glsp.graph.GPoint;
import org.eclipse.glsp.server.actions.Action;
import org.eclipse.glsp.server.actions.ActionDispatcher;
import org.eclipse.glsp.server.actions.GhostElement;
import org.eclipse.glsp.server.actions.SelectAction;
Expand Down Expand Up @@ -63,7 +64,12 @@ public void executeCreation(final CreateNodeOperation operation) {
Optional<GPoint> relativeLocation = getRelativeLocation(container, absoluteLocation);
GModelElement element = createNode(relativeLocation, operation.getArgs());
container.getChildren().add(element);
actionDispatcher.dispatchAfterNextUpdate(SelectAction.addSelection(List.of(element.getId())));
actionDispatcher.dispatchAfterNextUpdate(
Action.addSubclientId(
operation,
SelectAction.addSelection(List.of(element.getId()))
)
);
}

protected Optional<GPoint> getLocation(final CreateNodeOperation operation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.eclipse.glsp.server.di.ClientId;
import org.eclipse.glsp.server.disposable.Disposable;
import org.eclipse.glsp.server.features.core.model.UpdateModelAction;
import org.eclipse.glsp.server.model.GModelState;
import org.eclipse.glsp.server.protocol.GLSPClient;
import org.eclipse.glsp.server.utils.FutureUtil;

Expand Down Expand Up @@ -85,6 +86,10 @@ public class DefaultActionDispatcher extends Disposable implements ActionDispatc
@Inject
protected Provider<GLSPClient> client;

// use modelstate here to set subclient id
@Inject
protected GModelState modelState;

public DefaultActionDispatcher() {
this.name = getClass().getSimpleName() + " " + COUNT.incrementAndGet();
this.thread = new Thread(this::runThread);
Expand Down Expand Up @@ -193,10 +198,13 @@ protected List<CompletableFuture<Void>> runAction(final Action action) {
throw new IllegalArgumentException("No handler registered for action: " + action);
}

this.modelState.setParticipationID(action.getSubclientId());

List<CompletableFuture<Void>> results = new ArrayList<>();
for (final ActionHandler actionHandler : actionHandlers) {
final List<Action> responses = actionHandler.execute(action).stream()
.map(response -> ResponseAction.respond(action, response))
.map(response -> Action.addSubclientId(action, response))
.collect(Collectors.toList());
results.addAll(dispatchAll(responses));
}
Expand Down
Loading
Loading