3131import java .time .ZonedDateTime ;
3232import java .time .format .DateTimeParseException ;
3333import 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}
0 commit comments