Skip to content
This repository was archived by the owner on Feb 15, 2024. It is now read-only.

Commit 28c1a26

Browse files
committed
Merge branch 'master' into admin-shell/main
# Conflicts: # .gitignore # dataformat-json/src/main/java/io/adminshell/aas/v3/dataformat/json/ReflectionHelper.java # dataformat-json/src/main/java/io/adminshell/aas/v3/dataformat/json/deserialization/EmbeddedDataSpecificationDeserializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/deserialization/DefaultEmbeddedDataSpecificationDeserializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/deserialization/EmbeddedDataSpecificationDeserializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/deserialization/EnumDeserializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/enums/CustomEnumNaming.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/enums/EnumDeserializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/enums/EnumSerializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/enums/ScreamingSnakeCaseEnumNaming.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/enums/UpperCamelCaseEnumNaming.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/enums/UpperSnakeCaseToUpperCamelCaseStrategy.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/serialization/EmbeddedDataSpecificationSerializer.java # dataformat-xml/src/main/java/io/adminshell/aas/v3/dataformat/xml/serialization/EnumSerializer.java # pom.xml # src/main/java/io/adminshell/aas/v3/dataformat/json/JsonDeserializer.java # src/main/java/io/adminshell/aas/v3/dataformat/json/JsonSerializer.java # src/main/java/io/adminshell/aas/v3/dataformat/json/deserialization/DataSpecificationDeserializer.java # src/test/java/io/adminshell/aas/v3/dataformat/json/CustomProperty.java # src/test/resources/test_demo_full_example.json
2 parents 9714a0f + 3e015dc commit 28c1a26

13 files changed

Lines changed: 3618 additions & 50 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,10 @@ local.properties
2727

2828
.classpath
2929
.project
30+
3031
**/.flattened-pom.xml
3132
**/nb-configuration.xml
33+
**.iml
34+
**.flattened-pom.xml
35+
36+
testJsonSerialization.json
Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,79 @@
11
package io.adminshell.aas.v3.dataformat.json;
22

3-
import java.io.IOException;
4-
import java.nio.file.Files;
5-
import java.nio.file.Paths;
6-
import java.util.Set;
7-
83
import com.fasterxml.jackson.core.JsonProcessingException;
94
import com.fasterxml.jackson.databind.JsonNode;
105
import com.fasterxml.jackson.databind.ObjectMapper;
116
import com.networknt.schema.JsonSchema;
127
import com.networknt.schema.JsonSchemaFactory;
138
import com.networknt.schema.SpecVersionDetector;
149
import com.networknt.schema.ValidationMessage;
15-
1610
import io.adminshell.aas.v3.dataformat.SchemaValidator;
11+
12+
import java.io.BufferedReader;
13+
import java.io.IOException;
14+
import java.io.InputStreamReader;
15+
import java.net.URISyntaxException;
16+
import java.nio.file.Files;
17+
import java.nio.file.Paths;
18+
import java.util.Set;
1719
import java.util.stream.Collectors;
1820

21+
/**
22+
* Class for validating a serialized instance of AssetAdministrationShellEnvironment
23+
* against a json-schema.
24+
*/
1925
public class JsonSchemaValidator implements SchemaValidator {
2026

21-
private static final String SCHEMA = "src/main/resources/aas.json";
22-
private ObjectMapper mapper = new ObjectMapper();
23-
24-
protected JsonSchema schema;
27+
private static final String SCHEMA = "/aas.json";
28+
private final ObjectMapper mapper = new ObjectMapper();
2529

26-
public JsonSchemaValidator() throws IOException {
27-
loadSchema();
28-
}
29-
30-
public void loadSchema() throws IOException {
31-
JsonNode schemaRootNode = mapper.readTree(new String(Files.readAllBytes(Paths.get(SCHEMA))));
32-
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(schemaRootNode));
33-
schema = factory.getSchema(schemaRootNode);
34-
}
35-
36-
public Set<ValidationMessage> validateJsonSpecific(String serialized) throws JsonProcessingException {
37-
JsonNode node = mapper.readTree(serialized);
38-
return schema.validate(node);
39-
}
30+
public JsonSchemaValidator() {}
4031

32+
/**
33+
* validates against default schema
34+
*
35+
* @param serialized AssetAdministrationShellEnvironment,
36+
* serialized as json string
37+
* @return Set of messages to display validation results
38+
*/
4139
@Override
4240
public Set<String> validateSchema(String serialized) {
4341
try {
44-
Set<ValidationMessage> validationMessages = validateJsonSpecific(serialized);
42+
return validateSchema(serialized, loadDefaultSchema());
43+
} catch (IOException | URISyntaxException e) {
44+
return Set.of(e.getMessage());
45+
}
46+
}
47+
48+
/**
49+
* validates against custom schema
50+
*
51+
* @param serialized AssetAdministrationShellEnvironment,
52+
* serialized as json string
53+
* @param serializedSchema Custom json-schema serialized as String
54+
* that must extend the default aas-schema
55+
* @return Set of messages to display validation results
56+
*/
57+
public Set<String> validateSchema(String serialized, String serializedSchema) {
58+
try {
59+
JsonNode schemaRootNode = mapper.readTree(serializedSchema);
60+
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(schemaRootNode));
61+
JsonSchema schema = factory.getSchema(schemaRootNode);
62+
JsonNode node = mapper.readTree(serialized);
63+
Set<ValidationMessage> validationMessages = schema.validate(node);
4564
return generalizeValidationMessagesAsStringSet(validationMessages);
4665
} catch (JsonProcessingException e) {
4766
return Set.of(e.getMessage());
4867
}
4968
}
5069

70+
private String loadDefaultSchema() throws IOException, URISyntaxException {
71+
return new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(SCHEMA))).lines().collect(Collectors.joining("\n"));
72+
}
73+
5174
private Set<String> generalizeValidationMessagesAsStringSet(Set<ValidationMessage> messages) {
5275
return messages.stream()
53-
.map(x -> x.getMessage())
76+
.map(ValidationMessage::getMessage)
5477
.collect(Collectors.toSet());
5578
}
5679
}

dataformat-json/src/main/java/io/adminshell/aas/v3/dataformat/json/ReflectionHelper.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.adminshell.aas.v3.dataformat.json;
22

33
import io.adminshell.aas.v3.model.Constraint;
4-
import io.adminshell.aas.v3.model.DataSpecification;
4+
import io.adminshell.aas.v3.model.DataSpecificationContent;
55
import io.adminshell.aas.v3.model.Referable;
66
import io.github.classgraph.ClassGraph;
77
import io.github.classgraph.ClassInfo;
@@ -78,28 +78,28 @@ public class ReflectionHelper {
7878
* not have any default implementation and therefore are excluded
7979
* explicitely.
8080
*/
81-
public static final List<Class<?>> INTERFACES_WITHOUT_DEFAULT_IMPLEMENTATION = List.of(DataSpecification.class);
81+
public static final Set<Class<?>> INTERFACES_WITHOUT_DEFAULT_IMPLEMENTATION; // = List.of(DataSpecificationContent.class);
8282
/**
8383
* List of enums from the MODEL_PACKAGE_NAME package.
8484
*/
8585
public static final List<Class<Enum>> ENUMS;
8686

8787
public static class ImplementationInfo<T> {
8888

89-
private final Class<T> implementedClass;
90-
private final Class<? extends T> implementationClass;
89+
private final Class<T> interfaceType;
90+
private final Class<? extends T> implementationType;
9191

92-
protected ImplementationInfo(Class<T> implementedClass, Class<? extends T> implementationClass) {
93-
this.implementedClass = implementedClass;
94-
this.implementationClass = implementationClass;
92+
protected ImplementationInfo(Class<T> interfaceType, Class<? extends T> implementationType) {
93+
this.interfaceType = interfaceType;
94+
this.implementationType = implementationType;
9595
}
9696

97-
public Class<T> getImplementedClass() {
98-
return implementedClass;
97+
public Class<T> getInterfaceType() {
98+
return interfaceType;
9999
}
100100

101-
public Class<? extends T> getImplementationClass() {
102-
return implementationClass;
101+
public Class<? extends T> getImplementationType() {
102+
return implementationType;
103103
}
104104
}
105105

@@ -122,7 +122,17 @@ public static boolean isModelInterface(Class<?> type) {
122122
* @return whether the given class is a default implementation or not
123123
*/
124124
public static boolean isDefaultImplementation(Class<?> type) {
125-
return DEFAULT_IMPLEMENTATIONS.stream().anyMatch(x -> Objects.equals(x.getImplementationClass(), type));
125+
return DEFAULT_IMPLEMENTATIONS.stream().anyMatch(x -> Objects.equals(x.getImplementationType(), type));
126+
}
127+
128+
/**
129+
* Returns whether the given interface has a default implementation or not
130+
*
131+
* @param interfaceType the interface to check
132+
* @return whether the given interface has a default implementation or not
133+
*/
134+
public static boolean hasDefaultImplementation(Class<?> interfaceType) {
135+
return DEFAULT_IMPLEMENTATIONS.stream().anyMatch(x -> x.getInterfaceType().equals(interfaceType));
126136
}
127137

128138
/**
@@ -147,6 +157,13 @@ public static boolean isModelInterfaceOrDefaultImplementation(Class<?> type) {
147157
MIXINS = scanMixins(modelScan);
148158
DEFAULT_IMPLEMENTATIONS = scanDefaultImplementations(modelScan);
149159
ENUMS = modelScan.getAllEnums().loadClasses(Enum.class);
160+
INTERFACES_WITHOUT_DEFAULT_IMPLEMENTATION = getInterfacesWithoutDefaultImplementation(modelScan);
161+
}
162+
163+
private static Set<Class<?>> getInterfacesWithoutDefaultImplementation(ScanResult modelScan) {
164+
return modelScan.getAllInterfaces().loadClasses().stream()
165+
.filter(x -> !hasDefaultImplementation(x))
166+
.collect(Collectors.toSet());
150167
}
151168

152169
private static List<ImplementationInfo> scanDefaultImplementations(ScanResult modelScan) {
@@ -166,16 +183,11 @@ private static List<ImplementationInfo> scanDefaultImplementations(ScanResult mo
166183
logger.warn("could not find interface realized by default implementation class '{}'", x.getSimpleName());
167184
} else {
168185
Class<?> implementedClass = interfaceClassInfos.get(0).loadClass();
169-
if (INTERFACES_WITHOUT_DEFAULT_IMPLEMENTATION.contains(implementedClass)) {
170-
logger.info("skipping found default implementation class '{}' for interface '{}' because explicitely excluded",
171-
x.getSimpleName(),
172-
interfaceClassInfos.get(0).getName());
173-
} else {
174-
defaultImplementations.add(new ImplementationInfo(implementedClass, x));
175-
logger.info("using default implementation class '{}' for interface '{}'",
176-
x.getSimpleName(),
177-
interfaceClassInfos.get(0).getName());
178-
}
186+
defaultImplementations.add(new ImplementationInfo(implementedClass, x));
187+
logger.info("using default implementation class '{}' for interface '{}'",
188+
x.getSimpleName(),
189+
interfaceClassInfos.get(0).getName());
190+
179191
}
180192
});
181193
return defaultImplementations;

dataformat-json/src/main/java/io/adminshell/aas/v3/dataformat/json/mixins/ConceptDescriptionMixin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.adminshell.aas.v3.dataformat.json.mixins;
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
4-
import io.adminshell.aas.v3.model.DataSpecification;
4+
import io.adminshell.aas.v3.model.EmbeddedDataSpecification;
55
import io.adminshell.aas.v3.model.Reference;
66
import java.util.List;
77

@@ -19,5 +19,5 @@ public interface ConceptDescriptionMixin {
1919
// @JsonProperty("embeddedDataSpecifications")
2020
// @JsonDeserialize(using = DataSpecificationDeserializer.class)
2121

22-
public void setEmbeddedDataSpecifications(List<DataSpecification> embeddedDataSpecifications);
22+
public void setEmbeddedDataSpecifications(List<EmbeddedDataSpecification> embeddedDataSpecifications);
2323
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.adminshell.aas.v3.dataformat.json.deserialization;
2+
3+
import static io.adminshell.aas.v3.dataformat.json.DataSpecificationManager.PROP_DATA_SPECIFICATION;
4+
import static io.adminshell.aas.v3.dataformat.json.DataSpecificationManager.PROP_DATA_SPECIFICATION_CONTENT;
5+
6+
import java.io.IOException;
7+
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.core.JsonProcessingException;
10+
import com.fasterxml.jackson.databind.DeserializationContext;
11+
import com.fasterxml.jackson.databind.JsonDeserializer;
12+
import com.fasterxml.jackson.databind.JsonMappingException;
13+
import com.fasterxml.jackson.databind.JsonNode;
14+
import com.fasterxml.jackson.databind.node.ObjectNode;
15+
16+
import io.adminshell.aas.v3.dataformat.json.DataSpecificationManager;
17+
import io.adminshell.aas.v3.model.DataSpecificationContent;
18+
import io.adminshell.aas.v3.model.EmbeddedDataSpecification;
19+
import io.adminshell.aas.v3.model.Reference;
20+
import io.adminshell.aas.v3.model.impl.DefaultEmbeddedDataSpecification;
21+
22+
/**
23+
* Custom Deserializer for class DataSpecification. First reads property
24+
* PROP_DATA_SPECIFICATION and tries to resolve which Java class to use for
25+
* deserialization based on the found value with the help of
26+
* DataSpecificationManager.
27+
*/
28+
public class DefaultEmbeddedDataSpecificationDeserializer extends JsonDeserializer<EmbeddedDataSpecification> {
29+
30+
@Override
31+
public EmbeddedDataSpecification deserialize(JsonParser parser, DeserializationContext context)
32+
throws IOException, JsonProcessingException {
33+
Object temp = parser.getCodec().readTree(parser);
34+
ObjectNode node = (ObjectNode) temp;
35+
if (node == null) {
36+
return null;
37+
}
38+
JsonNode nodeDataSpecification = node.get(PROP_DATA_SPECIFICATION);
39+
if (nodeDataSpecification == null) {
40+
throw new JsonMappingException(parser,
41+
String.format("data specification must contain node '%s'", PROP_DATA_SPECIFICATION));
42+
}
43+
JsonParser parserReference = parser.getCodec().getFactory().getCodec().treeAsTokens(nodeDataSpecification);
44+
parserReference.nextToken();
45+
Reference reference = parserReference.readValueAs(Reference.class);
46+
JsonNode nodeContent = node.get(PROP_DATA_SPECIFICATION_CONTENT);
47+
if (nodeContent != null) {
48+
Class<? extends DataSpecificationContent> targetClass = DataSpecificationManager.getImplementation(reference);
49+
JsonParser parserContent = parser.getCodec().getFactory().getCodec().treeAsTokens(nodeContent);
50+
parserContent.nextToken();
51+
DataSpecificationContent content = parserContent.readValueAs(targetClass);
52+
return new DefaultEmbeddedDataSpecification.Builder().dataSpecificationContent(content).build();
53+
}
54+
return new DefaultEmbeddedDataSpecification.Builder().dataSpecification(reference).build();
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.adminshell.aas.v3.dataformat.json.deserialization;
2+
3+
import static io.adminshell.aas.v3.dataformat.json.DataSpecificationManager.PROP_DATA_SPECIFICATION;
4+
import static io.adminshell.aas.v3.dataformat.json.DataSpecificationManager.PROP_DATA_SPECIFICATION_CONTENT;
5+
6+
import java.io.IOException;
7+
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.core.JsonProcessingException;
10+
import com.fasterxml.jackson.databind.DeserializationContext;
11+
import com.fasterxml.jackson.databind.JsonDeserializer;
12+
import com.fasterxml.jackson.databind.JsonMappingException;
13+
import com.fasterxml.jackson.databind.JsonNode;
14+
import com.fasterxml.jackson.databind.node.ObjectNode;
15+
16+
import io.adminshell.aas.v3.dataformat.json.DataSpecificationManager;
17+
import io.adminshell.aas.v3.model.DataSpecificationContent;
18+
import io.adminshell.aas.v3.model.EmbeddedDataSpecification;
19+
import io.adminshell.aas.v3.model.Reference;
20+
import io.adminshell.aas.v3.model.impl.DefaultEmbeddedDataSpecification;
21+
22+
/**
23+
* Custom Deserializer for class DataSpecification. First reads property
24+
* PROP_DATA_SPECIFICATION and tries to resolve which Java class to use for
25+
* deserialization based on the found value with the help of
26+
* DataSpecificationManager.
27+
*/
28+
public class EmbeddedDataSpecificationDeserializer extends JsonDeserializer<EmbeddedDataSpecification> {
29+
30+
@Override
31+
public EmbeddedDataSpecification deserialize(JsonParser parser, DeserializationContext context)
32+
throws IOException, JsonProcessingException {
33+
Object temp = parser.getCodec().readTree(parser);
34+
ObjectNode node = (ObjectNode) temp;
35+
if (node == null) {
36+
return null;
37+
}
38+
JsonNode nodeDataSpecification = node.get(PROP_DATA_SPECIFICATION);
39+
if (nodeDataSpecification == null) {
40+
throw new JsonMappingException(parser,
41+
String.format("data specification must contain node '%s'", PROP_DATA_SPECIFICATION));
42+
}
43+
JsonParser parserReference = parser.getCodec().getFactory().getCodec().treeAsTokens(nodeDataSpecification);
44+
parserReference.nextToken();
45+
Reference reference = parserReference.readValueAs(Reference.class);
46+
JsonNode nodeContent = node.get(PROP_DATA_SPECIFICATION_CONTENT);
47+
if (nodeContent != null) {
48+
Class<? extends DataSpecificationContent> targetClass = DataSpecificationManager.getImplementation(reference);
49+
JsonParser parserContent = parser.getCodec().getFactory().getCodec().treeAsTokens(nodeContent);
50+
parserContent.nextToken();
51+
DataSpecificationContent content = parserContent.readValueAs(targetClass);
52+
return new DefaultEmbeddedDataSpecification.Builder().dataSpecificationContent(content).build();
53+
}
54+
return new DefaultEmbeddedDataSpecification.Builder().dataSpecification(reference).build();
55+
}
56+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.adminshell.aas.v3.dataformat.json.deserialization;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.core.JsonParser;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.databind.DeserializationContext;
8+
import com.fasterxml.jackson.databind.JsonDeserializer;
9+
10+
/**
11+
* Deserializes enum values converting element names from UpperCamelCase to
12+
* SCREAMING_SNAKE_CASE
13+
*
14+
* @param <T> Type of enum to deserialize
15+
*/
16+
public class EnumDeserializer<T extends Enum> extends JsonDeserializer<T> {
17+
18+
protected static final char UNDERSCORE = '_';
19+
protected final Class<T> type;
20+
21+
public EnumDeserializer(Class<T> type) {
22+
this.type = type;
23+
}
24+
25+
@Override
26+
public T deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
27+
return (T) Enum.valueOf(type, translate(parser.getText()));
28+
}
29+
30+
protected String translate(String input) {
31+
String result = "";
32+
if (input == null || input.isEmpty()) {
33+
return result;
34+
}
35+
result += input.charAt(0);
36+
for (int i = 1; i < input.length(); i++) {
37+
char currentChar = input.charAt(i);
38+
if (Character.isUpperCase(currentChar)) {
39+
result += UNDERSCORE;
40+
}
41+
result += Character.toUpperCase(currentChar);
42+
}
43+
return result;
44+
}
45+
}

0 commit comments

Comments
 (0)