Skip to content

Commit ff9ceb9

Browse files
authored
Add validation reason and distinguish between live and batch validation (#200)
eclipse-glsp/glsp#980
1 parent 96a02e0 commit ff9ceb9

8 files changed

Lines changed: 205 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### Changes
66

77
- [graph] Updated the Graph model and add Layoutable interface [#175](https://github.com/eclipse-glsp/glsp-server/pull/175) - Contributed on behalf of STMicroelectronics
8-
- [layout] Extend `ComputedBoundsAction` to also provide route data for client-side routed edges and store source/target point in the `args` map [#181](https://github.com/eclipse-glsp/glsp-server/pull/181)
8+
- [layout] Extend `ComputedBoundsAction` to also provide route data for client-side routed edges and store source/target point in the `args` map [#181](https://github.com/eclipse-glsp/glsp-server/pull/181)
99
- [websocket] Remove listing on `stdin` from `WebsocketServerLauncher` [#189](https://github.com/eclipse-glsp/glsp-server/pull/189)
1010
- [diagram] Fixed a bug that prevented stable ids within one session when using the `IdKeeperAdapter` [#192](https://github.com/eclipse-glsp/glsp-server/pull/192) - Contributed on behalf of STMicroelectronics
1111

@@ -23,6 +23,7 @@
2323
- EMFModelState and EMFNotationModelState are now interfaces instead of classes
2424
- EMFModelStateImpl and EMFNotationModelStateImpl classes have been added
2525
- Related Modules have been updated to inject these GModelState sub-types as a Singleton
26+
- [validation] Add explicit support and API for live and batch validation [#200](https://github.com/eclipse-glsp/glsp-server/pull/200)
2627

2728
## [v1.0.0 - 30/06/2022](https://github.com/eclipse-glsp/glsp-server/releases/tag/v1.0.0)
2829

examples/org.eclipse.glsp.example.workflow/src/org/eclipse/glsp/example/workflow/marker/WorkflowModelValidator.java

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2019-2022 EclipseSource and others.
2+
* Copyright (c) 2019-2023 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -39,48 +39,47 @@ public class WorkflowModelValidator implements ModelValidator {
3939
protected GModelState modelState;
4040

4141
@Override
42-
public List<Marker> validate(final GModelElement... elements) {
42+
public List<Marker> doLiveValidation(final GModelElement element) {
4343
List<Marker> markers = new ArrayList<>();
44-
45-
for (GModelElement element : elements) {
46-
if (element instanceof TaskNode) {
47-
markers.addAll(validateTaskNode(modelState, element));
48-
} else if (element instanceof ActivityNode) {
49-
ActivityNode activityNode = (ActivityNode) element;
50-
if ("decisionNode".equals(activityNode.getNodeType())) {
51-
markers.addAll(validateDecisionNode(modelState, element));
52-
} else if ("mergeNode".equals(activityNode.getNodeType())) {
53-
markers.addAll(validateMergeNode(modelState, element));
54-
}
55-
}
56-
if (element.getChildren() != null) {
57-
markers.addAll(validate(element.getChildren().toArray(new GModelElement[element.getChildren().size()])));
44+
if (element instanceof ActivityNode) {
45+
ActivityNode activityNode = (ActivityNode) element;
46+
if ("decisionNode".equals(activityNode.getNodeType())) {
47+
markers.addAll(validateDecisionNode(element));
48+
} else if ("mergeNode".equals(activityNode.getNodeType())) {
49+
markers.addAll(validateMergeNode(element));
5850
}
5951
}
6052
return markers;
6153
}
6254

63-
private static List<Marker> validateTaskNode(final GModelState modelState, final GModelElement taskNode) {
55+
@Override
56+
public List<Marker> doBatchValidation(final GModelElement element) {
57+
List<Marker> markers = new ArrayList<>();
58+
if (element instanceof TaskNode) {
59+
markers.addAll(validateTaskNode(element));
60+
}
61+
return markers;
62+
}
63+
64+
private List<Marker> validateTaskNode(final GModelElement taskNode) {
6465
List<Marker> markers = new ArrayList<>();
65-
validateTaskNode_isAutomated(modelState, taskNode).ifPresent(m -> markers.add(m));
66-
validateTaskNode_labelStartsUpperCase(modelState, taskNode).ifPresent(m -> markers.add(m));
66+
validateTaskNode_isAutomated(taskNode).ifPresent(m -> markers.add(m));
67+
validateTaskNode_labelStartsUpperCase(taskNode).ifPresent(m -> markers.add(m));
6768
return markers;
6869
}
6970

7071
@SuppressWarnings("checkstyle:MethodName")
71-
private static Optional<Marker> validateTaskNode_isAutomated(final GModelState modelState,
72-
final GModelElement element) {
72+
private Optional<Marker> validateTaskNode_isAutomated(final GModelElement element) {
7373
TaskNode taskNode = (TaskNode) element;
7474
if ("automated".equals(taskNode.getTaskType())) {
75-
return Optional
76-
.of(new Marker("Automated task", "This is an automated task", element.getId(), MarkerKind.INFO));
75+
String id = element.getId();
76+
return Optional.of(new Marker("Automated task", "This is an automated task", id, MarkerKind.INFO));
7777
}
7878
return Optional.empty();
7979
}
8080

8181
@SuppressWarnings("checkstyle:MethodName")
82-
private static Optional<Marker> validateTaskNode_labelStartsUpperCase(final GModelState modelState,
83-
final GModelElement element) {
82+
private Optional<Marker> validateTaskNode_labelStartsUpperCase(final GModelElement element) {
8483
TaskNode taskNode = (TaskNode) element;
8584

8685
boolean hasLowerCaseLabel = taskNode.getChildren().stream()
@@ -97,16 +96,14 @@ private static Optional<Marker> validateTaskNode_labelStartsUpperCase(final GMod
9796
return Optional.empty();
9897
}
9998

100-
private static List<Marker> validateDecisionNode(final GModelState modelState,
101-
final GModelElement decisionNode) {
99+
private List<Marker> validateDecisionNode(final GModelElement decisionNode) {
102100
List<Marker> markers = new ArrayList<>();
103-
validateDecisionNode_hasOneIncomingEdge(modelState, decisionNode).ifPresent(m -> markers.add(m));
101+
validateDecisionNode_hasOneIncomingEdge(decisionNode).ifPresent(m -> markers.add(m));
104102
return markers;
105103
}
106104

107105
@SuppressWarnings("checkstyle:MethodName")
108-
private static Optional<Marker> validateDecisionNode_hasOneIncomingEdge(final GModelState modelState,
109-
final GModelElement decisionNode) {
106+
private Optional<Marker> validateDecisionNode_hasOneIncomingEdge(final GModelElement decisionNode) {
110107
Collection<GEdge> incomingEdges = modelState.getIndex().getIncomingEdges(decisionNode);
111108
if (incomingEdges.size() > 1) {
112109
return Optional.of(new Marker("Too many incoming edges", "Decision node may only have one incoming edge.",
@@ -118,15 +115,14 @@ private static Optional<Marker> validateDecisionNode_hasOneIncomingEdge(final GM
118115
return Optional.empty();
119116
}
120117

121-
private static List<Marker> validateMergeNode(final GModelState modelState, final GModelElement mergeNode) {
118+
private List<Marker> validateMergeNode(final GModelElement mergeNode) {
122119
List<Marker> markers = new ArrayList<>();
123-
validateMergeNode_hasOneOutgoingEdge(modelState, mergeNode).ifPresent(m -> markers.add(m));
120+
validateMergeNode_hasOneOutgoingEdge(mergeNode).ifPresent(m -> markers.add(m));
124121
return markers;
125122
}
126123

127124
@SuppressWarnings("checkstyle:MethodName")
128-
private static Optional<Marker> validateMergeNode_hasOneOutgoingEdge(final GModelState modelState,
129-
final GModelElement mergeNode) {
125+
private Optional<Marker> validateMergeNode_hasOneOutgoingEdge(final GModelElement mergeNode) {
130126
Collection<GEdge> outgoingEdges = modelState.getIndex().getOutgoingEdges(mergeNode);
131127
if (outgoingEdges.size() > 1) {
132128
return Optional.of(new Marker("Too many outgoing edges", "Merge node may only have one outgoing edge.",

plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/features/core/model/ModelSubmissionHandler.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2019-2022 EclipseSource and others.
2+
* Copyright (c) 2019-2023 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -26,6 +26,10 @@
2626
import org.eclipse.glsp.server.actions.ActionHandler;
2727
import org.eclipse.glsp.server.actions.SetDirtyStateAction;
2828
import org.eclipse.glsp.server.diagram.DiagramConfiguration;
29+
import org.eclipse.glsp.server.features.validation.Marker;
30+
import org.eclipse.glsp.server.features.validation.ModelValidator;
31+
import org.eclipse.glsp.server.features.validation.MarkersReason;
32+
import org.eclipse.glsp.server.features.validation.SetMarkersAction;
2933
import org.eclipse.glsp.server.layout.LayoutEngine;
3034
import org.eclipse.glsp.server.layout.ServerLayoutKind;
3135
import org.eclipse.glsp.server.model.GModelState;
@@ -48,6 +52,9 @@ public class ModelSubmissionHandler {
4852
@Inject
4953
protected GModelState modelState;
5054

55+
@Inject
56+
protected Optional<ModelValidator> validator;
57+
5158
private final Object modelLock = new Object();
5259

5360
/**
@@ -108,6 +115,11 @@ public List<Action> submitModelDirectly(final String reason) {
108115
if (!diagramConfiguration.needsClientLayout()) {
109116
result.add(new SetDirtyStateAction(modelState.isDirty(), reason));
110117
}
118+
if (validator.isPresent()) {
119+
List<Marker> markers = validator.get() //
120+
.validate(Arrays.asList(modelState.getRoot()), MarkersReason.LIVE);
121+
result.add(new SetMarkersAction(markers, MarkersReason.LIVE));
122+
}
111123
return result;
112124
}
113125
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
package org.eclipse.glsp.server.features.validation;
17+
18+
/**
19+
* The default reasons for a request for markers.
20+
*/
21+
public final class MarkersReason {
22+
23+
/**
24+
* Batch validation executed on demand by the client.
25+
*/
26+
public static final String BATCH = "batch";
27+
/**
28+
* Live validation executed on start and after model update.
29+
*/
30+
public static final String LIVE = "live";
31+
32+
private MarkersReason() {
33+
// prevent instantiation for class only holding constants.
34+
}
35+
36+
}

plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/features/validation/ModelValidator.java

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2019-2021 EclipseSource and others.
2+
* Copyright (c) 2019-2023 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -15,6 +15,8 @@
1515
********************************************************************************/
1616
package org.eclipse.glsp.server.features.validation;
1717

18+
import java.util.ArrayList;
19+
import java.util.Collections;
1820
import java.util.List;
1921

2022
import org.eclipse.glsp.graph.GModelElement;
@@ -23,14 +25,104 @@
2325
* Validates a list of {@link GModelElement}s based on a set of validation rules and returns corresponding issue
2426
* {@link Marker}s.
2527
* An issue marker is a serializable description of the validation violation that can be visualised by the GLSP client.
28+
*
29+
* There are two default reasons for a validation:
30+
* <ol>
31+
* <li><i>live</i> validation is executed on open and after each operation. These validation rules should be rather
32+
* fast. Overwrite {@link #doLiveValidation(GModelElement)} to implement the live validation rules.</li>
33+
* <li><i>batch</i> validation is executed on demand by the client. These validation rules can be more expensive.
34+
* Overwrite {@link #doBatchValidation(GModelElement)} to implement the batch validation rules.</li>
35+
* </ol>
2636
*/
2737
public interface ModelValidator {
2838

2939
/**
3040
* Validates the given list of {@link GModelElement}s and returns a list of {@link Marker}s.
3141
*
3242
* @param elements The list of {@link GModelElement} to validate.
43+
* @param reason The reason for a validation request, such as "batch" or "live" validation.
3344
* @return A list of {@link Marker}s for the validated {@link GModelElement}s.
3445
*/
35-
List<Marker> validate(GModelElement... elements);
46+
default List<Marker> validate(final List<GModelElement> elements, final String reason) {
47+
List<Marker> markers = new ArrayList<>();
48+
49+
for (GModelElement element : elements) {
50+
if (MarkersReason.LIVE.equals(reason)) {
51+
markers.addAll(doLiveValidation(element));
52+
} else if (MarkersReason.BATCH.equals(reason)) {
53+
markers.addAll(doBatchValidation(element));
54+
} else {
55+
markers.addAll(doValidationForCustomReason(element, reason));
56+
}
57+
if (!element.getChildren().isEmpty()) {
58+
markers.addAll(validate(element.getChildren(), reason));
59+
}
60+
}
61+
62+
return markers;
63+
}
64+
65+
/**
66+
* Runs a <code>batch</code> validation with the given list of {@link GModelElement}s and returns a list of
67+
* {@link Marker}s.
68+
*
69+
* @param elements The list of {@link GModelElement} to validate.
70+
* @return A list of {@link Marker}s for the validated {@link GModelElement}s.
71+
*/
72+
default List<Marker> validate(final GModelElement... elements) {
73+
return validate(List.of(elements), MarkersReason.BATCH);
74+
}
75+
76+
/**
77+
* Perform the live validation rules for the given <code>element</code>.
78+
*
79+
* <p>
80+
* This will be invoked on start and after each operation for all elements.
81+
* Thus, the validation should be rather inexpensive.
82+
* There is no need to traverse through the children in this method as {@link #validate(List, String)} will invoke
83+
* this method for all children anyway.
84+
* </p>
85+
*
86+
* @param element The element to validate.
87+
* @return A list of {@link Marker}s for the validated {@link GModelElement}.
88+
*/
89+
default List<Marker> doLiveValidation(final GModelElement element) {
90+
return Collections.emptyList();
91+
}
92+
93+
/**
94+
* Perform the batch validation rules for the given <code>element</code>.
95+
*
96+
* <p>
97+
* This will be invoked on demand by the client.
98+
* Thus, the validation can include more expensive validation rules.
99+
* There is no need to traverse through the children in this method as {@link #validate(List, String)} will invoke
100+
* this method for all children anyway.
101+
* </p>
102+
*
103+
* @param element The element to validate.
104+
* @return A list of {@link Marker}s for the validated {@link GModelElement}.
105+
*/
106+
default List<Marker> doBatchValidation(final GModelElement element) {
107+
return Collections.emptyList();
108+
}
109+
110+
/**
111+
* Perform a validation for a custom <code>reason</code> with the given <code>element</code>.
112+
*
113+
* <p>
114+
* GLSP editors may add custom reasons for triggering a validation, other than <code>live</code> and
115+
* <code>batch</code>.
116+
* Validation requests that are not live or batch validations will be handled by this method.
117+
* There is no need to traverse through the children in this method as {@link #validate(List, String)} will invoke
118+
* this method for all children anyway.
119+
* </p>
120+
*
121+
* @param element The element to validate.
122+
* @return A list of {@link Marker}s for the validated {@link GModelElement}.
123+
*/
124+
default List<Marker> doValidationForCustomReason(final GModelElement element, final String reason) {
125+
return Collections.emptyList();
126+
}
127+
36128
}

plugins/org.eclipse.glsp.server/src/org/eclipse/glsp/server/features/validation/RequestMarkersAction.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,28 @@ public class RequestMarkersAction extends RequestAction<SetMarkersAction> {
2525
public static final String KIND = "requestMarkers";
2626

2727
private List<String> elementsIDs;
28+
private String reason;
2829

2930
public RequestMarkersAction() {
3031
this(new ArrayList<>());
3132
}
3233

3334
public RequestMarkersAction(final List<String> elementsIDs) {
35+
this(elementsIDs, MarkersReason.BATCH);
36+
}
37+
38+
public RequestMarkersAction(final List<String> elementsIDs, final String reason) {
3439
super(KIND);
3540
this.elementsIDs = elementsIDs;
41+
this.reason = reason;
3642
}
3743

3844
public List<String> getElementsIDs() { return elementsIDs; }
3945

4046
public void setElementsIDs(final List<String> elementsIDs) { this.elementsIDs = elementsIDs; }
4147

48+
public String getReason() { return reason; }
49+
50+
public void setReason(final String reason) { this.reason = reason; }
51+
4252
}

0 commit comments

Comments
 (0)