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

Commit 552daae

Browse files
Matthias BöckmannSebastian Bader
authored andcommitted
Pull request #3: Feature/rdfSerializer
Merge in EAR/aas-serializer from feature/rdfSerializer to master * commit '5a986db45c385ef5850ee9c404059daad83db17b': Making parser work on almost all examples. The remaining two examples seem to be semantically broken though. Not yet working on collections of blank nodes, as the group_concat fails Add generation of random URIs to serialization Adding examples for deserialization Support multiple RDF formats Adding a first RDF parser. However, serialization is not yet fully done, so it is difficult to test. Merge Removing parsing from serialization test Initial commit of RDF Serializer Not yet functional, as serialization has no IDs
2 parents 0c4bb80 + 5a986db commit 552daae

101 files changed

Lines changed: 7051 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* This class helps to dynamically decide how to de-/serialize classes and
3535
* properties defined in the AAS model library.
3636
*
37-
* This is equivialent to adding the following annotations
37+
* This is equivalent to adding the following annotations
3838
* <ul>
3939
* <li> to all interfaces defined in the AAS model:
4040
* <ul>

dataformat-jsonld/pom.xml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>dataformat-parent</artifactId>
7+
<groupId>io.admin-shell.aas</groupId>
8+
<version>${revision}</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>dataformat-jsonld</artifactId>
13+
14+
<properties>
15+
<!--<maven.compiler.source>12</maven.compiler.source>
16+
<maven.compiler.target>12</maven.compiler.target>-->
17+
</properties>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>io.admin-shell.aas</groupId>
22+
<artifactId>dataformat-core</artifactId>
23+
<version>${revision}</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.apache.jena</groupId>
27+
<artifactId>jena-arq</artifactId>
28+
<version>${jena.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>junit</groupId>
32+
<artifactId>junit</artifactId>
33+
<version>${junit.version}</version>
34+
<scope>test</scope>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.slf4j</groupId>
38+
<artifactId>slf4j-simple</artifactId>
39+
<version>1.7.30</version>
40+
<scope>test</scope>
41+
</dependency>
42+
43+
</dependencies>
44+
45+
46+
</project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.adminshell.aas.v3.dataformat.jsonld;
2+
3+
import com.fasterxml.jackson.core.JsonGenerator;
4+
import com.fasterxml.jackson.databind.SerializerProvider;
5+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
6+
7+
import java.io.IOException;
8+
import java.util.Map;
9+
10+
public class FallbackSerializer extends StdSerializer<Map<String, Object>> {
11+
12+
13+
public FallbackSerializer() {
14+
this(null);
15+
}
16+
17+
public FallbackSerializer(Class clazz) {
18+
super(clazz);
19+
}
20+
21+
22+
@Override
23+
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
24+
gen.writeString(value.toString());
25+
}
26+
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.adminshell.aas.v3.dataformat.jsonld;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreType;
4+
5+
@JsonIgnoreType
6+
public class IgnoreTypeMixIn {
7+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.adminshell.aas.v3.dataformat.jsonld;
2+
3+
import com.fasterxml.jackson.databind.module.SimpleModule;
4+
5+
import io.adminshell.aas.v3.dataformat.jsonld.custom.BigDecimalSerializer;
6+
import io.adminshell.aas.v3.dataformat.jsonld.custom.XMLGregorianCalendarDeserializer;
7+
import io.adminshell.aas.v3.dataformat.jsonld.custom.XMLGregorianCalendarSerializer;
8+
import io.adminshell.aas.v3.model.LangString;
9+
10+
import java.math.BigDecimal;
11+
import java.net.URI;
12+
import java.util.Map;
13+
14+
15+
import javax.xml.datatype.XMLGregorianCalendar;
16+
17+
18+
/**
19+
* Jackson module which provides support for JSON-LD serialization
20+
*/
21+
public class JsonLDModule extends SimpleModule {
22+
23+
24+
public JsonLDModule(Map<Object, String> idMap) {
25+
super();
26+
27+
setSerializerModifier(new JsonLDSerializerModifier(idMap));
28+
29+
addSerializer(XMLGregorianCalendar.class, new XMLGregorianCalendarSerializer());
30+
addDeserializer(XMLGregorianCalendar.class, new XMLGregorianCalendarDeserializer());
31+
addSerializer(BigDecimal.class, new BigDecimalSerializer());
32+
33+
addSerializer(URI.class, new UriSerializer());
34+
addSerializer(LangString.class, new LangStringSerializer());
35+
}
36+
37+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package io.adminshell.aas.v3.dataformat.jsonld;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.annotation.JsonTypeName;
5+
import com.fasterxml.jackson.core.JsonGenerator;
6+
import com.fasterxml.jackson.core.JsonToken;
7+
import com.fasterxml.jackson.core.type.WritableTypeId;
8+
import com.fasterxml.jackson.databind.SerializerProvider;
9+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
10+
import com.fasterxml.jackson.databind.ser.BeanSerializer;
11+
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
12+
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
import java.io.IOException;
17+
import java.lang.reflect.Field;
18+
import java.math.BigInteger;
19+
import java.util.*;
20+
import java.util.stream.Stream;
21+
22+
23+
public class JsonLDSerializer extends BeanSerializer {
24+
25+
private final Logger logger = LoggerFactory.getLogger(JsonLDSerializer.class);
26+
27+
private static int currentRecursionDepth = 0;
28+
29+
static final Map<String, String> contextItems = new HashMap<>();
30+
31+
private final Map<Object, String> idMap;
32+
33+
JsonLDSerializer(BeanSerializerBase src, Map<Object, String> idMap) {
34+
super(src);
35+
this.idMap = Objects.requireNonNullElseGet(idMap, HashMap::new);
36+
}
37+
38+
39+
40+
@Override
41+
public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
42+
gen.setCurrentValue(bean);
43+
44+
currentRecursionDepth++;
45+
gen.writeStartObject();
46+
47+
if (currentRecursionDepth == 1) {
48+
Map<String, String> filteredContext = new HashMap<>();
49+
filterContextWrtBean(bean, filteredContext);
50+
gen.writeObjectField("@context", filteredContext);
51+
//gen.writeStringField("@context", "https://jira.iais.fraunhofer.de/stash/projects/ICTSL/repos/ids-infomodel-commons/raw/jsonld-context/3.0.0/context.jsonld"); // only add @context on top level
52+
53+
}
54+
55+
if(idMap.containsKey(bean))
56+
{
57+
gen.writeStringField("@id", idMap.get(bean));
58+
}
59+
else
60+
{
61+
String randomUri = "https://admin-shell.io/autogen/" + UUID.randomUUID();
62+
idMap.put(bean, randomUri);
63+
gen.writeStringField("@id", randomUri);
64+
}
65+
66+
WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_OBJECT);
67+
String resolvedTypeId = typeIdDef.id != null ? typeIdDef.id.toString() : typeSer.getTypeIdResolver().idFromValue(bean);
68+
if (resolvedTypeId != null) {
69+
gen.writeStringField(typeIdDef.asProperty, resolvedTypeId);
70+
}
71+
if (_propertyFilterId != null) {
72+
serializeFieldsFiltered(bean, gen, provider);
73+
} else {
74+
serializeFields(bean, gen, provider);
75+
}
76+
gen.writeEndObject();
77+
currentRecursionDepth--;
78+
}
79+
80+
81+
private void filterContextWrtBean(Object bean, Map<String, String> filteredContext) {
82+
//Some default entries for AAS
83+
filteredContext.put("aas", "https://admin-shell.io/aas/3/0/RC01/");
84+
filteredContext.put("iec61360", "https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0/RC01/");
85+
filteredContext.put("phys_unit", "https://admin-shell.io/DataSpecificationTemplates/DataSpecificationPhysicalUnit/3/0/RC01/");
86+
87+
if(bean == null || bean.getClass().getName().equals("com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl") || bean.getClass().getName().equals("org.apache.jena.ext.xerces.jaxp.datatype.XMLGregorianCalendarImpl") || bean.getClass() == BigInteger.class) return; // XMLGregorianCalendarImpl causes infinite recursion
88+
89+
//Check if RdfResource or TypedLiteral is used. They contain a field called "type" which can reference to any namespace
90+
//Therefore it is vital to also check the value of the type field for prefixes that need to be included in the context
91+
if(bean.getClass().getSimpleName().equals("LangString"))
92+
{
93+
//LangString is of type rdf:langString, so this must be present
94+
filteredContext.put("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
95+
}
96+
contextItems.forEach((p, u) -> {
97+
JsonTypeName typeNameAnnotation = bean.getClass().getAnnotation(JsonTypeName.class);
98+
if(typeNameAnnotation != null && typeNameAnnotation.value().contains(p)) {
99+
filteredContext.put(p, u);
100+
}
101+
Stream.of(bean.getClass().getMethods()).forEach(m -> {
102+
JsonProperty propertyAnnotation = m.getAnnotation(JsonProperty.class);
103+
if(propertyAnnotation != null && propertyAnnotation.value().contains(p)) {
104+
filteredContext.put(p, u);
105+
}
106+
});
107+
});
108+
Stream.of(bean.getClass().getMethods()).forEach(m -> {
109+
// run though all properties and check annotations. These annotations should contain the prefixes
110+
JsonProperty prop = m.getAnnotation(JsonProperty.class);
111+
if(prop != null)
112+
{
113+
for(Map.Entry<String, String> entry : contextItems.entrySet())
114+
{
115+
if(prop.value().startsWith(entry.getKey()))
116+
{
117+
filteredContext.put(entry.getKey(), entry.getValue());
118+
break;
119+
}
120+
}
121+
}
122+
});
123+
// run through fields recursively
124+
for(Field f : getAllFields(new HashSet<>(), bean.getClass())) {
125+
126+
if(Collection.class.isAssignableFrom(f.getType()))
127+
{
128+
try {
129+
if(f.getType().getName().startsWith("java.") && !f.getType().getName().startsWith("java.util")) continue;
130+
boolean accessible = f.isAccessible();
131+
f.setAccessible(true);
132+
Collection<?> c = (Collection<?>) f.get(bean);
133+
if(c == null) {
134+
continue;
135+
}
136+
for(Object o : c)
137+
{
138+
filterContextWrtBean(o, filteredContext);
139+
}
140+
f.setAccessible(accessible);
141+
}
142+
catch (IllegalAccessException e) {
143+
e.printStackTrace();
144+
}
145+
}
146+
147+
if (f.getType().isPrimitive() || f.getType().isEnum() || f.getType().isArray()
148+
|| f.getType().getName().contains("java.")
149+
|| f.getType().getName().contains("javax.")) continue;
150+
151+
try {
152+
boolean wasAccessible = f.isAccessible();
153+
f.setAccessible(true);
154+
filterContextWrtBean(f.get(bean), filteredContext);
155+
f.setAccessible(wasAccessible);
156+
} catch (IllegalAccessException ignored) {
157+
//logger.error("setting accessible failed"); //We can catch that here, as IllegalReflectiveAccess cannot occur on our own packages
158+
}
159+
160+
//f.trySetAccessible(wasAccessible);
161+
162+
}
163+
164+
}
165+
166+
/**
167+
* This function retrieves a set of all available fields of a class, including inherited fields
168+
* @param fields Set to which discovered fields will be added. An empty HashSet should do the trick
169+
* @param type The class for which fields should be discovered
170+
* @return set of all available fields
171+
*/
172+
private static Set<Field> getAllFields(Set<Field> fields, Class<?> type) {
173+
fields.addAll(Arrays.asList(type.getDeclaredFields()));
174+
175+
if (type.getSuperclass() != null) {
176+
getAllFields(fields, type.getSuperclass());
177+
}
178+
179+
return fields;
180+
}
181+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.adminshell.aas.v3.dataformat.jsonld;
2+
3+
4+
import com.fasterxml.jackson.databind.BeanDescription;
5+
import com.fasterxml.jackson.databind.JsonSerializer;
6+
import com.fasterxml.jackson.databind.SerializationConfig;
7+
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
8+
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
9+
import io.adminshell.aas.v3.dataformat.jsonld.JsonLDSerializer;
10+
11+
import java.util.Map;
12+
13+
14+
public class JsonLDSerializerModifier extends BeanSerializerModifier {
15+
16+
private final Map<Object, String> idMap;
17+
18+
public JsonLDSerializerModifier(Map<Object, String> idMap) {
19+
this.idMap = idMap;
20+
}
21+
22+
@Override
23+
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
24+
if (serializer instanceof BeanSerializerBase) {
25+
return new JsonLDSerializer((BeanSerializerBase) serializer, idMap);
26+
} else {
27+
return serializer;
28+
}
29+
}
30+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.adminshell.aas.v3.dataformat.jsonld;
18+
19+
import com.fasterxml.jackson.core.JsonGenerator;
20+
import com.fasterxml.jackson.databind.JsonSerializer;
21+
import com.fasterxml.jackson.databind.SerializerProvider;
22+
import io.adminshell.aas.v3.model.annotations.IRI;
23+
24+
import java.io.IOException;
25+
26+
public class JsonLdEnumSerializer extends JsonSerializer<Enum<?>> {
27+
28+
29+
@Override
30+
public void serialize(Enum value, JsonGenerator gen, SerializerProvider provider) throws IOException {
31+
if(value.getClass().isEnum() && value.getClass().getName().startsWith("io.adminshell.aas."))
32+
{
33+
gen.writeString(translate(value.getClass(), value.name()));
34+
} else {
35+
provider.findValueSerializer(Enum.class).serialize(value, gen, provider);
36+
}
37+
}
38+
39+
40+
41+
public static String translate(Class<?> enumClass, String input) {
42+
String[] iriValues = enumClass.getAnnotation(IRI.class).value();
43+
String result = "";
44+
if(iriValues.length > 0)
45+
{
46+
result = iriValues[0];
47+
if(!result.endsWith("/"))
48+
{
49+
result += "/";
50+
}
51+
}
52+
result += input;
53+
return result;
54+
}
55+
}

0 commit comments

Comments
 (0)