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

Commit 6723682

Browse files
author
Matthias Böckmann
committed
Making parser work on almost all examples. The remaining two examples seem to be semantically broken though.
1 parent 4593897 commit 6723682

7 files changed

Lines changed: 1408 additions & 2723 deletions

File tree

dataformat-jsonld/src/main/java/io/adminshell/aas/v3/dataformat/jsonld/Parser.java

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.time.ZonedDateTime;
3232
import java.time.format.DateTimeParseException;
3333
import java.util.*;
34+
import java.util.stream.Collectors;
3435

3536

3637
/**
@@ -219,6 +220,10 @@ private <T> T handleObject(Model inputModel, String objectUri, Class<T> targetCl
219220
}
220221
queryStringBuilder.append(" (GROUP_CONCAT(?").append(key1).append(";separator=\"||\") AS ?").append(key1).append("s) ");
221222

223+
//Additional case for blank nodes
224+
queryStringBuilder.append(" (GROUP_CONCAT(?").append(key1).append("Blank;separator=\"||\") AS ?").append(key1).append("sBlank) ");
225+
226+
222227
} else {
223228
//No, it's not a list. No need to aggregate
224229
queryStringBuilder.append(" ?").append(key1);
@@ -273,7 +278,12 @@ private <T> T handleObject(Model inputModel, String objectUri, Class<T> targetCl
273278
logger.warn("Failed to retrieve JsonAlias for field " + field + ". Assuming aas:" + entry.getKey());
274279
queryStringBuilder.append("aas:").append(entry.getKey());
275280
}
281+
//if(isBlank(?entry.getKey(), use value of artificial, use original value)
276282
queryStringBuilder.append(" ?").append(entry.getKey()).append(" ."); //object
283+
284+
//In case of the object being a blank node, we construct a second result variable with the blank node id
285+
queryStringBuilder.append("OPTIONAL { ?").append(entry.getKey()).append(" <").append(blankNodeIdPropertyUri).append("> ?").append(entry.getKey()).append("Blank . } ");
286+
277287
queryStringBuilder.append("} ");
278288
}
279289

@@ -297,7 +307,7 @@ private <T> T handleObject(Model inputModel, String objectUri, Class<T> targetCl
297307

298308
if(!targetClass.equals(LangString.class)) { //LangString has no additional properties map. Skip this step
299309

300-
//CONSTRUCT { ?s ?p ?o } { ?s ?p ?o. FILTER(?p NOT IN (liste der ids: properties)) }
310+
//CONSTRUCT { ?s ?p ?o } { ?s ?p ?o. FILTER(?p NOT IN (list of ids properties)) }
301311
for (Map.Entry<String, String> entry : knownNamespaces.entrySet()) {
302312
queryForOtherProperties.append("PREFIX ").append(entry.getKey());
303313
if (!entry.getKey().endsWith(":")) {
@@ -465,9 +475,13 @@ private <T> T handleObject(Model inputModel, String objectUri, Class<T> targetCl
465475
if (Collection.class.isAssignableFrom(currentType)) {
466476
sparqlParameterName += "s"; //plural form for the concatenated values
467477
}
468-
querySolution.varNames().forEachRemaining(System.out::println);
469-
System.out.println(querySolution.contains(sparqlParameterName));
470-
if (querySolution.contains(sparqlParameterName)) { //TODO why is this false for keys?! Answer: Because you can't do a GROUP_CONCAT on blank nodes
478+
if(!querySolution.contains(sparqlParameterName))
479+
{
480+
sparqlParameterName += "Blank"; //If not present, try to go with the option for blank nodes instead
481+
//TODO: Note: This would not yield full results yet in case some of the values are encapsulated
482+
// in blank nodes and some are not, for the same property
483+
}
484+
if (querySolution.contains(sparqlParameterName)) {
471485
String currentSparqlBinding = querySolution.get(sparqlParameterName).toString();
472486

473487
boolean objectIsBlankNode = querySolution.get(sparqlParameterName).isResource() && querySolution.get(sparqlParameterName).asNode().isBlank();
@@ -476,32 +490,37 @@ private <T> T handleObject(Model inputModel, String objectUri, Class<T> targetCl
476490
//For that case, we add some artificial identifiers here
477491
if(objectIsBlankNode) {
478492
blankNodeId = querySolution.get(sparqlParameterName).asNode().getBlankNodeId().toString();
479-
inputModel.add(
480-
ResourceFactory.createStatement(
481-
querySolution.get(sparqlParameterName).asResource(), //subject: the blank node
482-
ResourceFactory.createProperty(blankNodeIdPropertyUri.toString()), //some artificial property
483-
ResourceFactory.createStringLiteral(blankNodeId))); //object: the ID of the blank node
484493
}
485-
486494
if (currentType.isEnum()) {
487495
//Two possibilities:
488496
//1: The URI of the enum value is given directly e.g. ?s ?p <someUri>
489497
//2: The URI of the enum value is encapsulated in a blank node, e.g.
490498
// ?s ?p [ a demo:myEnum, demo:enumValue ]
491499
if(objectIsBlankNode)
492500
{
501+
493502
Query innerEnumQuery = QueryFactory.create("SELECT ?type { ?s <" + blankNodeIdPropertyUri + "> + \"" + blankNodeId + "\" ; a ?type } ");
494503
QueryExecution innerEnumQueryExecution = QueryExecutionFactory.create(innerEnumQuery, inputModel);
495504
ResultSet innerEnumQueryExecutionResultSet = innerEnumQueryExecution.execSelect();
505+
506+
//Only throw this if there is no successful execution
507+
IOException anyIOException = null;
508+
boolean oneSuccessfulEnumFound = false;
496509
while(innerEnumQueryExecutionResultSet.hasNext())
497510
{
498511
try {
499512
entry.getValue().invoke(returnObject, handleEnum(currentType, innerEnumQueryExecutionResultSet.next().get("type").toString()));
513+
oneSuccessfulEnumFound = true;
500514
break; //Stop after the first successful execution
501515
}
502-
catch (IOException ignored) //There might be errors, if multiple types are present, see example above
503-
{}
516+
catch (IOException e) //There might be errors, if multiple types are present, see example above
517+
{
518+
anyIOException = e;
519+
}
504520
}
521+
//If nothing worked, but something failed (i.e. there exists a problematic element, but no proper element), we throw an exception
522+
if(anyIOException != null && !oneSuccessfulEnumFound)
523+
throw new IOException("Could not parse Enum. ", anyIOException);
505524
innerEnumQueryExecution.close();
506525
}
507526
else {
@@ -586,7 +605,6 @@ private <T> T handleObject(Model inputModel, String objectUri, Class<T> targetCl
586605
} else {
587606
//Not a primitive object, but a complex sub-object. Recursively call this function to handle it
588607
if (objectIsBlankNode) {
589-
System.out.println("Handling sub-object as blank node");
590608
entry.getValue().invoke(returnObject, handleObject(inputModel, blankNodeId, entry.getValue().getParameterTypes()[0]));
591609
} else {
592610

@@ -754,14 +772,12 @@ private <T> T handleEnum(Class<T> enumClass, String url) throws IOException {
754772
url = url.substring(url.lastIndexOf("/") + 1);
755773
}
756774
for (T constant : constants) {
757-
if (url.equalsIgnoreCase(constant.toString())) {
775+
//We artificially added some underscores in the AAS ontology. TODO: This might be a bit dangerous for other ontologies, which really contain underscores in enum names
776+
if (url.equalsIgnoreCase(constant.toString()) || url.equalsIgnoreCase(constant.toString().replace("_", ""))) {
758777
return constant;
759778
}
760779
}
761-
for(T constant : constants) {
762-
logger.info("Available enums are: " + constant.toString());
763-
}
764-
throw new IOException("Failed to find matching enum value for " + url);
780+
throw new IOException("Failed to find matching enum value for " + url + " . Available enums are: " + Arrays.stream(constants).map(Object::toString).collect(Collectors.joining(", ")));
765781
}
766782

767783
/**
@@ -941,6 +957,7 @@ private boolean isPrimitive(Class<?> input) throws IOException {
941957
* @throws IOException if the parsing of the message fails
942958
*/
943959
<T> T parseMessage(Model rdfModel, Class<T> targetClass) throws IOException {
960+
addArtificialBlankNodeLabels(rdfModel);
944961
ArrayList<Class<?>> implementingClasses = getImplementingClasses(targetClass);
945962

946963
// Query to retrieve all instances in the input graph that have a class assignment
@@ -1036,35 +1053,65 @@ <T> T parseMessage(String message, Class<T> targetClass) throws IOException {
10361053
}
10371054

10381055
/**
1039-
* Reads a message into an Apache Jena model.
1056+
* Entry point to this class. Takes a message and a desired target class (can be an interface)
1057+
* @param message Object to be parsed. Note that the name is misleading: One can also parse non-message IDS objects with this function
1058+
* @param targetClass Desired target class (something as abstract as "Message.class" is allowed)
1059+
* @param serializationFormat Input RDF format
1060+
* @param <T> Desired target class
1061+
* @return Object of desired target class, representing the values contained in input message
1062+
* @throws IOException if the parsing of the message fails
1063+
*/
1064+
<T> T parseMessage(String message, Class<T> targetClass, Lang serializationFormat) throws IOException {
1065+
Model model = readMessage(message, serializationFormat);
1066+
return parseMessage(model, targetClass);
1067+
}
1068+
1069+
/**
1070+
* Reads a message into an Apache Jena model, guessing the input language.
1071+
* Note: Guessing the language may cause some error messages during parsing attempts
10401072
*
10411073
* @param message Message to be read
10421074
* @return The model of the message
10431075
*/
10441076
private Model readMessage(String message) throws IOException {
10451077

1046-
Model targetModel = ModelFactory.createDefaultModel();
10471078
List<Lang> supportedLanguages = new ArrayList<>(
10481079
Arrays.asList(
10491080
RDFLanguages.JSONLD, //JSON-LD first
10501081
RDFLanguages.TURTLE, //N-TRIPLE is a subset of Turtle
10511082
RDFLanguages.RDFXML
10521083
));
10531084

1054-
boolean successfullyParsed = false;
10551085
for (Lang lang : supportedLanguages) {
10561086
try {
1057-
RDFDataMgr.read(targetModel, new ByteArrayInputStream(message.getBytes()), lang);
1058-
successfullyParsed = true; //Only set, if no exception occurred
1059-
} catch (RiotException ignored) {
1087+
return readMessage(message, lang);
1088+
} catch (IOException ignored) {
10601089
}
10611090
}
1062-
if (successfullyParsed) {
1063-
return targetModel;
1064-
}
10651091
throw new IOException("Could not parse string as any supported RDF format (JSON-LD, Turtle/N-Triple, RDF-XML).");
10661092
}
10671093

1094+
/**
1095+
* Reads a message into an Apache Jena model, guessing the input language.
1096+
* Note: Guessing the language may cause some error messages during parsing attempts
1097+
*
1098+
* @param message Message to be read
1099+
* @param language The RDF serialization of the input. Supported formats are JSON-LD, N-Triple, Turtle, and RDF-XML
1100+
* @return The model of the message
1101+
*/
1102+
private Model readMessage(String message, Lang language) throws IOException {
1103+
1104+
Model targetModel = ModelFactory.createDefaultModel();
1105+
1106+
try {
1107+
RDFDataMgr.read(targetModel, new ByteArrayInputStream(message.getBytes()), language);
1108+
}
1109+
catch (RiotException e)
1110+
{
1111+
throw new IOException("Failed to parse input as " + language, e);
1112+
}
1113+
return targetModel;
1114+
}
10681115

10691116
/**
10701117
* Get a list of all subclasses (by JsonSubTypes annotation) which can be instantiated
@@ -1080,9 +1127,28 @@ ArrayList<Class<?>> getImplementingClasses(Class<?> someClass) {
10801127
result.addAll(getImplementingClasses(type.value()));
10811128
}
10821129
}
1083-
if (!someClass.isInterface() && !Modifier.isAbstract(someClass.getModifiers()))
1084-
result.add(someClass);
1130+
if (!someClass.isInterface() && !Modifier.isAbstract(someClass.getModifiers())) {
1131+
result.add(Serializer.customImplementationMap.getOrDefault(someClass, someClass));
1132+
}
10851133
return result;
10861134
}
10871135

1136+
private void addArtificialBlankNodeLabels(Model m)
1137+
{
1138+
//Get all blank nodes
1139+
Query q = QueryFactory.create("SELECT DISTINCT ?s { ?s ?p ?o . FILTER(isBlank(?s)) } ");
1140+
QueryExecution qe = QueryExecutionFactory.create(q, m);
1141+
ResultSet rs = qe.execSelect();
1142+
List<Statement> statementsToAdd = new ArrayList<>();
1143+
while(rs.hasNext())
1144+
{
1145+
QuerySolution qs = rs.next();
1146+
statementsToAdd.add(ResourceFactory.createStatement(qs.get("?s").asResource(),
1147+
ResourceFactory.createProperty(blankNodeIdPropertyUri.toString()),
1148+
ResourceFactory.createStringLiteral(qs.get("?s").toString())));
1149+
}
1150+
qe.close();
1151+
m.add(statementsToAdd);
1152+
}
1153+
10881154
}

dataformat-jsonld/src/main/java/io/adminshell/aas/v3/dataformat/jsonld/Serializer.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class Serializer implements io.adminshell.aas.v3.dataformat.Serializer, D
3535
public static String implementingClassesNamePrefix = "Default";
3636
public static String implementingClassesNameSuffix = "";
3737

38+
static Map<Class<?>, Class<?>> customImplementationMap = new HashMap<>();
39+
3840
private static boolean charsetWarningPrinted = false;
3941

4042
public Serializer() {
@@ -129,8 +131,33 @@ public String serializePlainJson(Object instance) throws JsonProcessingException
129131
* @param <T> deserialized type
130132
* @return an object representing the provided JSON(-LD) structure
131133
*/
132-
public <T> T deserialize(String serialization, Class<T> valueType) throws IOException {
133-
return new Parser().parseMessage(serialization, valueType);
134+
public <T> T deserialize(String serialization, Class<T> valueType) throws DeserializationException {
135+
try {
136+
return new Parser().parseMessage(serialization, valueType);
137+
}
138+
catch (IOException e)
139+
{
140+
throw new DeserializationException("Failed to deserialize input.", e);
141+
}
142+
}
143+
144+
/**
145+
* Inverse method of "serialize"
146+
*
147+
* @param serialization JSON(-LD) string
148+
* @param valueType class of top level type
149+
* @param serializationFormat RDF input format
150+
* @param <T> deserialized type
151+
* @return an object representing the provided JSON(-LD) structure
152+
*/
153+
public <T> T deserialize(String serialization, Class<T> valueType, Lang serializationFormat) throws DeserializationException {
154+
try {
155+
return new Parser().parseMessage(serialization, valueType, serializationFormat);
156+
}
157+
catch (IOException e)
158+
{
159+
throw new DeserializationException("Failed to deserialize input.", e);
160+
}
134161
}
135162

136163
/**
@@ -141,8 +168,14 @@ public <T> T deserialize(String serialization, Class<T> valueType) throws IOExce
141168
* @param <T> deserialized type
142169
* @return an object representing the provided JSON(-LD) structure
143170
*/
144-
public <T> T deserialize(Model rdfModel, Class<T> valueType) throws IOException {
145-
return new Parser().parseMessage(rdfModel, valueType);
171+
public <T> T deserialize(Model rdfModel, Class<T> valueType) throws DeserializationException {
172+
try {
173+
return new Parser().parseMessage(rdfModel, valueType);
174+
}
175+
catch (IOException e)
176+
{
177+
throw new DeserializationException("Failed to deserialize input.", e);
178+
}
146179
}
147180

148181
/**
@@ -224,8 +257,19 @@ public AssetAdministrationShellEnvironment read(String value) throws Deserializa
224257
}
225258
}
226259

260+
public AssetAdministrationShellEnvironment read(String value, Lang serializationFormat) throws DeserializationException {
261+
try {
262+
return new Parser().parseMessage(value, AssetAdministrationShellEnvironment.class, serializationFormat);
263+
}
264+
catch (IOException e)
265+
{
266+
throw new DeserializationException("Could not deserialize to environment.", e);
267+
}
268+
}
269+
227270
@Override
228271
public <T> void useImplementation(Class<T> aasInterface, Class<? extends T> implementation) {
229-
throw new NotImplementedException("Custom implementation support not yet implemented"); //TODO
272+
customImplementationMap.put(aasInterface, implementation);
273+
//throw new NotImplementedException("Custom implementation support not yet implemented");
230274
}
231275
}

0 commit comments

Comments
 (0)