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

feat: implement asset information serialisation #163

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V.
* Copyright (c) 2022 SAP SE or an SAP affiliate company
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,64 +28,29 @@
import java.util.stream.Collectors;

import org.eclipse.digitaltwin.aas4j.v3.dataformat.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.internal.ReflectionAnnotationIntrospector;
import org.eclipse.digitaltwin.aas4j.v3.model.Environment;
import org.eclipse.digitaltwin.aas4j.v3.model.Referable;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver;
import com.fasterxml.jackson.databind.module.SimpleModule;

import static org.eclipse.digitaltwin.aas4j.v3.dataformat.json.ObjectMapperFactory.createMapper;
import static org.eclipse.digitaltwin.aas4j.v3.dataformat.json.ObjectMapperFactory.createTypeResolver;

/**
* Class for deserializing/parsing AAS JSON documents.
*/
public class JsonDeserializer {

protected JsonMapper mapper;
protected SimpleAbstractTypeResolver typeResolver;

private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
protected ObjectMapper mapper;
protected final SimpleAbstractTypeResolver typeResolver;

public JsonDeserializer() {
initTypeResolver();
buildMapper();
}

protected void buildMapper() {
mapper = JsonMapper.builder()
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.annotationIntrospector(new ReflectionAnnotationIntrospector())
.addModule(buildEnumModule())
.addModule(buildImplementationModule())
.build();
ReflectionHelper.JSON_MIXINS.entrySet().forEach(x -> mapper.addMixIn(x.getKey(), x.getValue()));
}

@SuppressWarnings("unchecked")
private void initTypeResolver() {
typeResolver = new SimpleAbstractTypeResolver();
ReflectionHelper.DEFAULT_IMPLEMENTATIONS
.stream()
.forEach(x -> typeResolver.addMapping(x.getInterfaceType(), x.getImplementationType()));
}

protected SimpleModule buildEnumModule() {
SimpleModule module = new SimpleModule();
ReflectionHelper.ENUMS.forEach(x -> module.addDeserializer(x, new EnumDeserializer<>(x)));
return module;
}

protected SimpleModule buildImplementationModule() {
SimpleModule module = new SimpleModule();
module.setAbstractTypes(typeResolver);
return module;
typeResolver = createTypeResolver();
mapper = createMapper(typeResolver);
}

/**
Expand Down Expand Up @@ -183,7 +149,7 @@ public Environment read(java.io.File file) throws FileNotFoundException, Deseria
*/
public <T> void useImplementation(Class<T> aasInterface, Class<? extends T> implementation) {
typeResolver.addMapping(aasInterface, implementation);
buildMapper();
mapper = createMapper(typeResolver);
}

/**
Expand Down Expand Up @@ -374,4 +340,12 @@ public <T extends Referable> List<T> readReferables(File src, Class<T> outputCla
public <T extends Referable> List<T> readReferables(File src, Charset charset, Class<T> outputClass) throws DeserializationException, FileNotFoundException {
return readReferables(new FileInputStream(src), charset, outputClass);
}

public <T> T read(String json, Class<T> clazz) throws DeserializationException {
try {
return mapper.readValue(json, clazz);
} catch (JsonProcessingException ex) {
throw new DeserializationException("Error by deserializing the json string:\n" + json, ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V.
* Copyright (c) 2022 SAP SE or an SAP affiliate company
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,59 +16,33 @@
*/
package org.eclipse.digitaltwin.aas4j.v3.dataformat.json;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;

import org.eclipse.digitaltwin.aas4j.v3.dataformat.SerializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.internal.ReflectionAnnotationIntrospector;
import org.eclipse.digitaltwin.aas4j.v3.model.Environment;
import org.eclipse.digitaltwin.aas4j.v3.model.Referable;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Objects;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Class for serializing an instance of AssetAdministrationShellEnvironment or Referables to JSON.
*/
public class JsonSerializer {

protected JsonMapper mapper;

private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
protected final ObjectMapper mapper;

public JsonSerializer() {
buildMapper();
}

protected void buildMapper() {
mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.addModule(buildEnumModule())
.annotationIntrospector(new ReflectionAnnotationIntrospector())
.build();
ReflectionHelper.JSON_MIXINS.entrySet().forEach(x -> mapper.addMixIn(x.getKey(), x.getValue()));
}

protected SimpleModule buildEnumModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(Enum.class, new EnumSerializer());
return module;
mapper = ObjectMapperFactory.createMapper();
}

/**
Expand Down Expand Up @@ -214,4 +189,15 @@ public JsonNode toNode(Collection<? extends Referable> referables) {
}
return mapper.valueToTree(referables);
}

public String write(Object aas_element) throws SerializationException {
try {
// the new schema (version 3.0.RC02) defines modelType as a string, therefore the ModelTypeProcessor is not needed anymore
//return mapper.writeValueAsString(ModelTypeProcessor.postprocess(this.mapper.readTree(json)));
return mapper.writeValueAsString(aas_element);
} catch (JsonProcessingException ex) {
throw new SerializationException("Error serializing an aas-element", ex);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.eclipse.digitaltwin.aas4j.v3.dataformat.json;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion the ObjectMapper created by this class can fully replace the JsonSerializer and JsonDeserializer.


import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper;

/**
* This class contains the initialization code used by the JsonSerializer and JsonDeserializer. It also contains a
* factory method, that is very useful to be used in SpringBoot environment, to automate the serialization. Here is a
* sample code:
* <pre>
* {@code @Configuration}
* public class JacksonConfig {
* {@code @Bean}
* {@code @Primary}
* public ObjectMapper objectMapper() {
* return ObjectMapperFactory.createMapper();
* }
* </pre>
* Having such JacksonConfig class in your SpringBoot web application, the framework will always use the created mapper
* for serialization and deserialization, and you don't need to carry about it anymore.
*/
public class ObjectMapperFactory {
private ObjectMapperFactory() {}

/**
* The factory method to create the object mapper for serialization/deserialization of AAS objects.
* @return the configured object mapper. According to the Jackson documentation the ObjectMapper class is
* thread-safe.
*
* The returned object mapper can be used directly for AAS serialization/deserialization, or you can use the utility
* JsonSerializer and JsonDeserializer wrapper classes.
*/
public static ObjectMapper createMapper() {
return createMapper(null);
}

/**
* A package-private method used by JsonDeserializer wrapper class. It is needed only to support its
* useImplementation method, which require modification of the type resolver.
* @param typeResolver The type resolver used for deserialization.
* @return the configured object mapper. According to the Jackson documentation the ObjectMapper class is
* thread-safe.
*/
static ObjectMapper createMapper(SimpleAbstractTypeResolver typeResolver) {
ObjectMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.annotationIntrospector(new ReflectionAnnotationIntrospector())
.addModule(buildEnumModule())
.addModule(buildTypeResolverModule(typeResolver))
.build();
ReflectionHelper.JSON_MIXINS.entrySet().forEach(x -> mapper.addMixIn(x.getKey(), x.getValue()));
return mapper;
}

/**
* A package-private method, to create a type resolver, which is used for serialization/deserialization of AAS
* object.
* @return the type resolver.
*/
static SimpleAbstractTypeResolver createTypeResolver() {
SimpleAbstractTypeResolver typeResolver = new SimpleAbstractTypeResolver();
ReflectionHelper.DEFAULT_IMPLEMENTATIONS
.stream()
.forEach(x -> typeResolver.addMapping(x.getInterfaceType(), x.getImplementationType()));
return typeResolver;
}

private static SimpleModule buildTypeResolverModule(SimpleAbstractTypeResolver typeResolver) {
final SimpleModule module = new SimpleModule();
if(typeResolver == null) {
typeResolver = createTypeResolver();
}
module.setAbstractTypes(typeResolver);
return module;
}

private static SimpleModule buildEnumModule() {
SimpleModule module = new SimpleModule();
ReflectionHelper.ENUMS.forEach(x -> {
module.addDeserializer(x, new EnumDeserializer<>(x));
module.addSerializer(x, new EnumSerializer());
});
return module;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have decided that it is better to decrease the class visibility to package-private. In such way the class is visible only from dataformat-json project. But it needs to be in the same package as ObjectMapperFactory.

* Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V.
* Copyright (C) 2023 SAP SE or an SAP affiliate company.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -13,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.internal;
package org.eclipse.digitaltwin.aas4j.v3.dataformat.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
Expand All @@ -31,7 +32,7 @@
import java.util.stream.Collectors;

/**
* This class helps to dynamically decide how to de-/serialize classes and
* This utility class has package-private visibility and helps to dynamically decide how to de-/serialize classes and
* properties defined in the AAS model library.
*
* This is equivalent to adding the following annotations
Expand All @@ -52,7 +53,7 @@
* </ul>
* </ul>
*/
public class ReflectionAnnotationIntrospector extends JacksonAnnotationIntrospector {
class ReflectionAnnotationIntrospector extends JacksonAnnotationIntrospector {

private static final long serialVersionUID = 1L;

Expand Down Expand Up @@ -108,5 +109,4 @@ public JsonInclude.Value findPropertyInclusion(Annotated a) {
}
return result;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V.
* Copyright (c) 2022 SAP SE or an SAP affiliate company
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -46,6 +47,14 @@ public void testSimpleExample() throws Exception {
Assert.assertEquals(expected, actual);
}

@Test
public void testSimpleExampleDirectly() throws Exception {
ObjectMapper mapper = ObjectMapperFactory.createMapper();
Environment expected = Examples.EXAMPLE_SIMPLE.getModel();
Environment actual = mapper.readValue(Examples.EXAMPLE_SIMPLE.fileContentStream(), Environment.class);
Assert.assertEquals(expected, actual);
}

@Test
public void testFullExample() throws Exception {
Environment expected = Examples.EXAMPLE_FULL.getModel();
Expand All @@ -61,6 +70,14 @@ public void testFullExampleFromNode() throws Exception {
Assert.assertEquals(expected, actual);
}

@Test
public void testFullExampleDirectly() throws Exception {
ObjectMapper mapper = ObjectMapperFactory.createMapper();
Environment expected = Examples.EXAMPLE_FULL.getModel();
Environment actual = mapper.readValue(Examples.EXAMPLE_FULL.fileContentStream(), Environment.class);
Assert.assertEquals(expected, actual);
}

@Test
public void testCustomImplementationClass() throws Exception {
String json = new JsonSerializer().write(AASSimple.createEnvironment());
Expand Down
Loading