Skip to content

Commit 3dd51b9

Browse files
authored
Fix constraint validation problems (#103)
* Fix Pattern facet and return all constraints of a given type - Before, if a model would contain a value that is not cohering to the defined pattern facet, the ObjectMapper could not encode the diagnostic properly because it ignored private fields. This is now fixed. - Ensure Deserializer sets severity properly. - Now the validation/constraints endpoint returns all of the constraints for each type, as well as the parent types. - Features with empty constraints are not send to the client anymore. - Add test cases. Signed-off-by: Simon Graband <sgraband@eclipsesource.com>
1 parent 979b76c commit 3dd51b9

29 files changed

Lines changed: 3333 additions & 92 deletions

bundles/org.eclipse.emfcloud.modelserver.emf/src/org/eclipse/emfcloud/modelserver/emf/common/DefaultModelValidator.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.eclipse.emfcloud.modelserver.emf.configuration.FacetConfig;
3030
import org.eclipse.emfcloud.modelserver.jsonschema.Json;
3131

32+
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
33+
import com.fasterxml.jackson.annotation.PropertyAccessor;
3234
import com.fasterxml.jackson.databind.JsonNode;
3335
import com.fasterxml.jackson.databind.ObjectMapper;
3436
import com.google.inject.Inject;
@@ -58,6 +60,7 @@ public JsonNode validate(final String modeluri) {
5860
}
5961
ObjectMapper mapper = mapperProvider.get();
6062
mapper.registerModule(new ValidationMapperModule(res.get()));
63+
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.PROTECTED_AND_PUBLIC);
6164
BasicDiagnostic diagnostics = DIAGNOSTICIAN.createDefaultDiagnostic(model.get());
6265
DIAGNOSTICIAN.validate(model.get(), diagnostics, DIAGNOSTICIAN.createDefaultContext());
6366
return mapper.valueToTree(diagnostics);
@@ -75,16 +78,7 @@ public JsonNode getValidationConstraints(final String modeluri) {
7578
for (EClassifier classifier : ePackage.getEClassifiers()) {
7679
if (classifier instanceof EClass) {
7780
// Map Feature -> ExtendedMetaData
78-
Map<String, JsonNode> featureMap = new HashMap<>();
79-
for (EStructuralFeature feature : ((EClass) classifier).getEStructuralFeatures()) {
80-
if (feature instanceof EAttribute) {
81-
EDataType dataType = ((EAttribute) feature).getEAttributeType();
82-
// Map facet -> Value
83-
Map<String, Object> constraints = getConstraints(dataType);
84-
EMFFacetConstraints emfFacetConstraints = new EMFFacetConstraints(constraints);
85-
featureMap.put(feature.getName(), mapper.valueToTree(emfFacetConstraints));
86-
}
87-
}
81+
Map<String, JsonNode> featureMap = getFeatures((EClass) classifier, mapper);
8882
// Map Class -> Features
8983
if (!featureMap.isEmpty()) {
9084
jsonResult.put(EcoreUtil.getURI(classifier).toString(), featureMap);
@@ -94,6 +88,26 @@ public JsonNode getValidationConstraints(final String modeluri) {
9488
return mapper.valueToTree(jsonResult);
9589
}
9690

91+
protected Map<String, JsonNode> getFeatures(final EClass eClass, final ObjectMapper mapper) {
92+
Map<String, JsonNode> featureMap = new HashMap<>();
93+
for (EStructuralFeature feature : eClass.getEStructuralFeatures()) {
94+
if (feature instanceof EAttribute) {
95+
EDataType dataType = ((EAttribute) feature).getEAttributeType();
96+
// Map facet -> Value
97+
Map<String, Object> constraints = getConstraints(dataType);
98+
EMFFacetConstraints emfFacetConstraints = new EMFFacetConstraints(constraints);
99+
if (emfFacetConstraints.hasConstraints()) {
100+
featureMap.put(feature.getName(), mapper.valueToTree(emfFacetConstraints));
101+
}
102+
}
103+
}
104+
for (EClass parent : eClass.getESuperTypes()) {
105+
Map<String, JsonNode> parentMap = getFeatures(parent, mapper);
106+
featureMap.putAll(parentMap);
107+
}
108+
return featureMap;
109+
}
110+
97111
@SuppressWarnings("checkstyle:CyclomaticComplexity")
98112
protected Map<String, Object> getConstraints(final EDataType dataType) {
99113
Map<String, Object> result = new HashMap<>();

bundles/org.eclipse.emfcloud.modelserver.emf/src/org/eclipse/emfcloud/modelserver/emf/common/EMFFacetConstraints.java

Lines changed: 126 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212

1313
import java.util.List;
1414
import java.util.Map;
15+
import java.util.Objects;
16+
17+
import org.eclipse.emf.ecore.EcorePackage;
18+
import org.eclipse.emf.ecore.util.ExtendedMetaData;
1519

1620
import com.fasterxml.jackson.annotation.JsonInclude;
1721
import com.fasterxml.jackson.annotation.JsonInclude.Include;
1822

1923
@JsonInclude(Include.NON_NULL)
2024
public class EMFFacetConstraints {
21-
2225
public static final String WHITESPACE = "whiteSpace";
2326
public static final String ENUMERATION = "enumeration";
2427
public static final String PATTERN = "pattern";
@@ -32,25 +35,54 @@ public class EMFFacetConstraints {
3235
public static final String MININCLUSIVE = "minInclusive";
3336
public static final String MAXINCLUSIVE = "maxInclusive";
3437

38+
public static final int WHITESPACE_DEFAULT = ExtendedMetaData.INSTANCE
39+
.getWhiteSpaceFacet(EcorePackage.Literals.EBOOLEAN);
40+
public static final List<String> ENUMERATION_DEFAULT = ExtendedMetaData.INSTANCE
41+
.getEnumerationFacet(EcorePackage.Literals.EBOOLEAN);
42+
public static final List<String> PATTERN_DEFAULT = ExtendedMetaData.INSTANCE
43+
.getPatternFacet(EcorePackage.Literals.EBOOLEAN);
44+
public static final int TOTALDIGITS_DEFAULT = ExtendedMetaData.INSTANCE
45+
.getTotalDigitsFacet(EcorePackage.Literals.EBOOLEAN);
46+
public static final int FRACTIONDIGITS_DEFAULT = ExtendedMetaData.INSTANCE
47+
.getFractionDigitsFacet(EcorePackage.Literals.EBOOLEAN);
48+
public static final int LENGTH_DEFAULT = ExtendedMetaData.INSTANCE
49+
.getLengthFacet(EcorePackage.Literals.EBOOLEAN);
50+
public static final int MINLENGTH_DEFAULT = ExtendedMetaData.INSTANCE
51+
.getMinLengthFacet(EcorePackage.Literals.EBOOLEAN);
52+
public static final int MAXLENGTH_DEFAULT = ExtendedMetaData.INSTANCE
53+
.getMaxLengthFacet(EcorePackage.Literals.EBOOLEAN);
54+
public static final String MINEXCLUSIVE_DEFAULT = ExtendedMetaData.INSTANCE
55+
.getMinExclusiveFacet(EcorePackage.Literals.EBOOLEAN);
56+
public static final String MAXEXCLUSIVE_DEFAULT = ExtendedMetaData.INSTANCE
57+
.getMaxExclusiveFacet(EcorePackage.Literals.EBOOLEAN);
58+
public static final String MININCLUSIVE_DEFAULT = ExtendedMetaData.INSTANCE
59+
.getMinInclusiveFacet(EcorePackage.Literals.EBOOLEAN);
60+
public static final String MAXINCLUSIVE_DEFAULT = ExtendedMetaData.INSTANCE
61+
.getMaxInclusiveFacet(EcorePackage.Literals.EBOOLEAN);
62+
3563
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = WhiteSpaceFilter.class)
3664
private Integer whiteSpace;
37-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = StringListFilter.class)
65+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = EnumerationFilter.class)
3866
private List<String> enumeration;
39-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = StringListFilter.class)
67+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = PatternFilter.class)
4068
private List<String> pattern;
41-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = IntegerFilter.class)
69+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = TotalDigitsFilter.class)
4270
private Integer totalDigits;
43-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = IntegerFilter.class)
71+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = FractionDigitsFilter.class)
4472
private Integer fractionDigits;
45-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = IntegerFilter.class)
73+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = LengthFilter.class)
4674
private Integer length;
47-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = IntegerFilter.class)
75+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MinLengthFilter.class)
4876
private Integer minLength;
49-
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = IntegerFilter.class)
77+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MaxLengthFilter.class)
5078
private Integer maxLength;
79+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MinExclusiveFilter.class)
5180
private String minExclusive;
81+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MaxExclusiveFilter.class)
5282
private String maxExclusive;
83+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MinInclusiveFilter.class)
5384
private String minInclusive;
85+
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MaxInclusiveFilter.class)
5486
private String maxInclusive;
5587

5688
public Integer getWhiteSpace() { return whiteSpace; }
@@ -117,19 +149,35 @@ public EMFFacetConstraints(final Map<String, Object> facetMap) {
117149
this.maxInclusive = (String) facetMap.getOrDefault(MAXINCLUSIVE, null);
118150
}
119151

120-
public EMFFacetConstraints() {
121-
152+
public EMFFacetConstraints() {}
153+
154+
@SuppressWarnings({ "CyclomaticComplexity", "BooleanExpressionComplexity" })
155+
public boolean hasConstraints() {
156+
return !(Objects.equals(this.whiteSpace, WHITESPACE_DEFAULT)
157+
&& Objects.equals(this.enumeration, ENUMERATION_DEFAULT)
158+
&& Objects.equals(this.pattern, PATTERN_DEFAULT)
159+
&& Objects.equals(this.totalDigits, TOTALDIGITS_DEFAULT)
160+
&& Objects.equals(this.fractionDigits, FRACTIONDIGITS_DEFAULT)
161+
&& Objects.equals(this.length, LENGTH_DEFAULT)
162+
&& Objects.equals(this.minLength, MINLENGTH_DEFAULT)
163+
&& Objects.equals(this.maxLength, MAXLENGTH_DEFAULT)
164+
&& Objects.equals(this.minExclusive, MINEXCLUSIVE_DEFAULT)
165+
&& Objects.equals(this.maxExclusive, MAXEXCLUSIVE_DEFAULT)
166+
&& Objects.equals(this.minInclusive, MININCLUSIVE_DEFAULT)
167+
&& Objects.equals(this.maxInclusive, MAXINCLUSIVE_DEFAULT));
122168
}
123-
124169
}
125170

126-
class WhiteSpaceFilter {
171+
class DefaultValueFilter {
172+
protected Object defaultValue;
173+
174+
DefaultValueFilter(final Object defaultValue) {
175+
this.defaultValue = defaultValue;
176+
}
177+
127178
@Override
128179
public boolean equals(final Object obj) {
129-
if (obj instanceof Integer) {
130-
return obj == Integer.valueOf(0);
131-
}
132-
return true;
180+
return Objects.equals(defaultValue, obj);
133181
}
134182

135183
@Override
@@ -138,32 +186,74 @@ public int hashCode() {
138186
}
139187
}
140188

141-
class IntegerFilter {
142-
@Override
143-
public boolean equals(final Object obj) {
144-
if (obj instanceof Integer) {
145-
return obj == Integer.valueOf(-1);
146-
}
147-
return true;
189+
class WhiteSpaceFilter extends DefaultValueFilter {
190+
WhiteSpaceFilter() {
191+
super(EMFFacetConstraints.WHITESPACE_DEFAULT);
148192
}
193+
}
149194

150-
@Override
151-
public int hashCode() {
152-
return super.hashCode();
195+
class EnumerationFilter extends DefaultValueFilter {
196+
EnumerationFilter() {
197+
super(EMFFacetConstraints.ENUMERATION_DEFAULT);
153198
}
154199
}
155200

156-
class StringListFilter {
157-
@Override
158-
public boolean equals(final Object obj) {
159-
if (obj instanceof List) {
160-
return ((List<?>) obj).isEmpty();
161-
}
162-
return true;
201+
class PatternFilter extends DefaultValueFilter {
202+
PatternFilter() {
203+
super(EMFFacetConstraints.PATTERN_DEFAULT);
163204
}
205+
}
164206

165-
@Override
166-
public int hashCode() {
167-
return super.hashCode();
207+
class TotalDigitsFilter extends DefaultValueFilter {
208+
TotalDigitsFilter() {
209+
super(EMFFacetConstraints.TOTALDIGITS_DEFAULT);
210+
}
211+
}
212+
213+
class FractionDigitsFilter extends DefaultValueFilter {
214+
FractionDigitsFilter() {
215+
super(EMFFacetConstraints.FRACTIONDIGITS_DEFAULT);
216+
}
217+
}
218+
219+
class LengthFilter extends DefaultValueFilter {
220+
LengthFilter() {
221+
super(EMFFacetConstraints.LENGTH_DEFAULT);
222+
}
223+
}
224+
225+
class MinLengthFilter extends DefaultValueFilter {
226+
MinLengthFilter() {
227+
super(EMFFacetConstraints.MINLENGTH_DEFAULT);
228+
}
229+
}
230+
231+
class MaxLengthFilter extends DefaultValueFilter {
232+
MaxLengthFilter() {
233+
super(EMFFacetConstraints.MAXLENGTH_DEFAULT);
234+
}
235+
}
236+
237+
class MinExclusiveFilter extends DefaultValueFilter {
238+
MinExclusiveFilter() {
239+
super(EMFFacetConstraints.MINEXCLUSIVE_DEFAULT);
240+
}
241+
}
242+
243+
class MaxExclusiveFilter extends DefaultValueFilter {
244+
MaxExclusiveFilter() {
245+
super(EMFFacetConstraints.MAXEXCLUSIVE_DEFAULT);
246+
}
247+
}
248+
249+
class MinInclusiveFilter extends DefaultValueFilter {
250+
MinInclusiveFilter() {
251+
super(EMFFacetConstraints.MININCLUSIVE_DEFAULT);
252+
}
253+
}
254+
255+
class MaxInclusiveFilter extends DefaultValueFilter {
256+
MaxInclusiveFilter() {
257+
super(EMFFacetConstraints.MAXINCLUSIVE_DEFAULT);
168258
}
169259
}

bundles/org.eclipse.emfcloud.modelserver.emf/src/org/eclipse/emfcloud/modelserver/emf/common/ValidationMapperModule.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ public BasicDiagnostic deserialize(final JsonParser p, final DeserializationCont
9393
int code = node.get(ValidationMapperModule.CODE).asInt();
9494
String message = node.get(ValidationMapperModule.MESSAGE).asText();
9595
Object[] data = p.getCodec().treeToValue(node.get(ValidationMapperModule.DATA), Object[].class);
96+
97+
if (node.get(ValidationMapperModule.CHILDREN).isEmpty()) {
98+
int severity = node.get(ValidationMapperModule.SEVERITY).asInt();
99+
return new BasicDiagnostic(severity, source, code, message, data);
100+
}
101+
96102
List<BasicDiagnostic> children = new ArrayList<>();
97103
for (JsonNode child : node.get(ValidationMapperModule.CHILDREN)) {
98104
children.add(p.getCodec().treeToValue(child, BasicDiagnostic.class));

tests/org.eclipse.emfcloud.modelserver.emf.tests/.classpath

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@
1111
<attribute name="test" value="true"/>
1212
</attributes>
1313
</classpathentry>
14+
<classpathentry kind="src" path="src-gen">
15+
<attributes>
16+
<attribute name="test" value="true"/>
17+
</attributes>
18+
</classpathentry>
1419
<classpathentry kind="output" path="bin"/>
1520
</classpath>

tests/org.eclipse.emfcloud.modelserver.emf.tests/build.properties

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
source.. = src/
1+
#
2+
3+
bin.includes = .,\
4+
META-INF/,\
5+
plugin.properties
6+
jars.compile.order = .
7+
source.. = src/,\
8+
src-gen/
29
output.. = bin/
3-
bin.includes = META-INF/,\
4-
.,\
5-
resources/,\
6-
ui-schemas/

0 commit comments

Comments
 (0)