Skip to content

Commit a98817d

Browse files
author
Vincent HEMERY
authored
#216 Json and schema are inconsistent when using an EMap reference (#227)
Generate schema for Map object. closes #216
1 parent 680d163 commit a98817d

2 files changed

Lines changed: 69 additions & 11 deletions

File tree

bundles/org.eclipse.emfcloud.modelserver.common/src/org/eclipse/emfcloud/modelserver/jsonschema/DefaultJsonSchemaConverter.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2019 EclipseSource and others.
2+
* Copyright (c) 2019-2022 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
@@ -10,8 +10,10 @@
1010
********************************************************************************/
1111
package org.eclipse.emfcloud.modelserver.jsonschema;
1212

13+
import java.text.MessageFormat;
1314
import java.util.Collection;
1415
import java.util.List;
16+
import java.util.Map;
1517
import java.util.Optional;
1618
import java.util.stream.Collectors;
1719

@@ -126,20 +128,46 @@ protected JsonNode createJsonSchemaFromEStructuralFeature(final EStructuralFeatu
126128
}
127129

128130
protected JsonNode createJsonSchema(final EReference eReference, final boolean featureAsAttribute) {
131+
EClass eReferenceType = eReference.getEReferenceType();
129132
final ObjectNode objectNode = Json.object();
130-
if (eReference.getUpperBound() > 1 || eReference.getUpperBound() == -1) {
131-
objectNode.set("type", Json.text("array"));
132-
JsonNode feature = Json.object();
133-
if (!featureAsAttribute) {
134-
feature = createJsonSchemaFromEClass(eReference.getEReferenceType());
133+
if (eReference.isMany()) {
134+
if (Map.Entry.class.equals(eReferenceType.getInstanceClass()) && eReference.isContainment()) {
135+
// eReference points to a Map$Entry EClass and should be translated as a Map Object
136+
EStructuralFeature keyFeature = eReferenceType.getEStructuralFeature("key");
137+
if (!Optional.ofNullable(keyFeature).filter(EAttribute.class::isInstance).filter(f -> !f.isMany())
138+
.isPresent()) {
139+
// there is no key attribute in the Map$Entry EClass. The metamodel is incorrect and cannot be supported.
140+
String msg = MessageFormat.format(
141+
"The EClass {0} has instance type name 'java.util.Map$Entry', but no 'key' monovalued attribute. It is incorrect, and schema cannot be generated for referencing property {1}::{2}.",
142+
eReferenceType.getName(), eReference.getEContainingClass().getName(), eReference.getName());
143+
throw new IllegalArgumentException(msg);
144+
}
145+
EStructuralFeature valueFeature = eReferenceType.getEStructuralFeature("value");
146+
if (!Optional.ofNullable(valueFeature).filter(f -> !f.isMany()).isPresent()) {
147+
// there is no value feature in the Map$Entry EClass. The metamodel is incorrect and cannot be supported.
148+
String msg = MessageFormat.format(
149+
"The EClass {0} has instance type name 'java.util.Map$Entry', but no 'value' monovalued property. It is incorrect, and schema cannot be generated for referencing property {1}::{2}.",
150+
eReferenceType.getName(), eReference.getEContainingClass().getName(), eReference.getName());
151+
throw new IllegalArgumentException(msg);
152+
}
153+
objectNode.set("type", Json.text("object"));
154+
JsonNode valueType = createJsonSchemaFromEStructuralFeature(valueFeature);
155+
objectNode.set("additionalProperties", valueType);
135156
} else {
136-
feature = Json.object().set("$ref",
137-
TextNode.valueOf("#/definitions/" + eReference.getEType().getName().trim().toLowerCase()));
157+
// eReference is multivalued and should be translated as an array
158+
objectNode.set("type", Json.text("array"));
159+
JsonNode feature = Json.object();
160+
if (!featureAsAttribute) {
161+
feature = createJsonSchemaFromEClass(eReferenceType);
162+
} else {
163+
feature = Json.object().set("$ref",
164+
TextNode.valueOf("#/definitions/" + eReference.getEType().getName().trim().toLowerCase()));
165+
}
166+
objectNode.set("items", feature);
138167
}
139-
objectNode.set("items", feature);
140168
return objectNode;
141169
}
142-
return createJsonSchema(eReference.getEReferenceType(), true);
170+
return createJsonSchema(eReferenceType, true);
143171
}
144172

145173
protected ObjectNode createJsonSchema(final EAttribute eAttribute) {

tests/org.eclipse.emfcloud.modelserver.common.tests/src/org/eclipse/emfcloud/modelserver/common/tests/jsonschema/DefaultJsonSchemaConverterEClassTest.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2019 EclipseSource and others.
2+
* Copyright (c) 2019-2022 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
@@ -14,6 +14,7 @@
1414
import static org.junit.Assert.assertEquals;
1515

1616
import java.util.Collections;
17+
import java.util.Map;
1718

1819
import org.eclipse.emf.ecore.EAttribute;
1920
import org.eclipse.emf.ecore.EClass;
@@ -37,11 +38,14 @@ public class DefaultJsonSchemaConverterEClassTest extends DefaultJsonSchemaConve
3738

3839
private static final String ECLASS_NAME = "TestEClass";
3940
private static final String REFECLASS_NAME = "TestEClass2";
41+
private static final String MAPENTRYECLASS_NAME = "StringToStringMapEntry";
4042
private static final String REFERENCE_NAME = "testReference";
4143
private static final String ATTRIBUTE_NAME = "testAttribute";
4244

4345
private EClass eClass;
4446
private EClass refClass;
47+
/** An EClass used for correspondence with EMap entries. */
48+
private EClass mapEntryClass;
4549
private EReference eReference;
4650
private EReference eReference2;
4751

@@ -56,6 +60,12 @@ public void before() {
5660

5761
refClass = EcoreFactory.eINSTANCE.createEClass();
5862
refClass.setName(REFECLASS_NAME);
63+
64+
mapEntryClass = EcoreFactory.eINSTANCE.createEClass();
65+
mapEntryClass.setName(MAPENTRYECLASS_NAME);
66+
mapEntryClass.setInstanceClass(Map.Entry.class);
67+
mapEntryClass.getEAttributes().add(EcoreTestUtil.stringEAttribute("key", 0, 1));
68+
mapEntryClass.getEAttributes().add(EcoreTestUtil.stringEAttribute("value", 0, 1));
5969
}
6070

6171
@Test
@@ -220,4 +230,24 @@ public void createJsonSchemaFromEClassWithMandatoryStringAttribute() throws Json
220230
assertEquals(expected, actual);
221231
}
222232

233+
@Test
234+
public void createJsonSchemaFromEClassWithEMapReference() {
235+
eReference = EcoreTestUtil.eReference(REFERENCE_NAME, 0, -1, mapEntryClass);
236+
eReference.setContainment(true);
237+
eClass.getEStructuralFeatures().add(eReference);
238+
239+
final JsonNode actual = jsonSchemaCreator.from(eClass);
240+
final ObjectNode expected = Json.object(
241+
prop("$id", Json.text(getIdHelper(eClass))),
242+
prop("title", Json.text(ECLASS_NAME)),
243+
prop("type", Json.text("object")),
244+
prop("properties", Json.object(
245+
prop(REFERENCE_NAME, Json.object(
246+
prop("type", Json.text("object")),
247+
prop("additionalProperties", Json.object(prop("type", Json.text("string")))))))),
248+
prop("additionalProperties", Json.bool(false)));
249+
250+
assertEquals(expected, actual);
251+
}
252+
223253
}

0 commit comments

Comments
 (0)