View Javadoc
1   /*
2    * Copyright 2018-2022 Medical Information Systems Research Group (https://medical.zcu.cz),
3    * Department of Computer Science and Engineering, University of West Bohemia.
4    * Address: Univerzitni 8, 306 14 Plzen, Czech Republic.
5    *
6    * Author Petr Vcelak (vcelak@kiv.zcu.cz).
7    *
8    * This file is part of MRECore project.
9    *
10   * MRECore is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU General Public License as published by
12   * the Free Software Foundation, either version 3 of the License.
13   *
14   * MRECore is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with MRECore. If not, see <http://www.gnu.org/licenses/>.
21   */
22  package cz.zcu.mre.service.data;
23  
24  import cz.zcu.mre.data.core.MREData;
25  import cz.zcu.mre.data.core.MREStringLang;
26  import cz.zcu.mre.mrelib.util.DateUtil;
27  import java.time.LocalDate;
28  import java.time.LocalDateTime;
29  import java.util.Date;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Set;
33  import org.apache.jena.datatypes.xsd.XSDDatatype;
34  import org.apache.jena.datatypes.xsd.impl.XSDDateType;
35  import org.apache.jena.ontology.Individual;
36  import org.apache.jena.rdf.model.Literal;
37  import org.apache.jena.rdf.model.Resource;
38  import org.apache.jena.rdf.model.ResourceFactory;
39  import org.apache.jena.vocabulary.RDF;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  /**
44   *
45   * SPARQL Builder String Implementation.
46   *
47   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
48   */
49  public class SPARQLBuilderStringImpl implements SPARQLBuilderString {
50  
51      private static final Logger LOG = LoggerFactory.getLogger(SPARQLBuilderStringImpl.class);
52      private static final String EMPTY_STRING = "";
53      private static final String GRAPH_BEGIN = "{ GRAPH ";
54      private static final String GRAPH_NAME = "<NAME>";
55      private static final String GRAPH_END = "}";
56  
57      private static final String TEMPLATE_ASK = "ASK { TRIPLES }";
58      private static final String TEMPLATE_INSERT = "INSERT DATA GRAPH_BEGIN { TRIPLES } GRAPH_END";
59      private static final String TEMPLATE_DELETE = "DELETE DATA GRAPH_BEGIN { TRIPLES } GRAPH_END";
60      private static final String TEMPLATE_SELECT = "SELECT DISTINCT ATTRIBUTES WHERE {\nTRIPLES}";
61  
62      private String graph = null;
63  
64      //@Autowired
65      private MREDataService dataService;
66  
67      /**
68       * Default constructor.
69       */
70      public SPARQLBuilderStringImpl() {
71      }
72  
73      public SPARQLBuilderStringImpl(MREDataService dataService) {
74          this.dataService = dataService;
75      }
76  
77      @Override
78      public String ask(Object instance) {
79          return buildQuery(QUERY.ASK, instance, false);
80      }
81  
82      @Override
83      public String ask(String queryTriples) {
84  
85          String ask = TEMPLATE_ASK.replace("TRIPLES", queryTriples);
86          return ask;
87      }
88  
89      @Override
90      public String insert(Object instance) {
91          return buildQuery(QUERY.INSERT, instance, false);
92      }
93  
94      @Override
95      public String insertAll(Object instance) {
96          return buildQuery(QUERY.INSERT, instance, true);
97      }
98  
99      @Override
100     public String delete(Object instance) {
101         return buildQuery(QUERY.DELETE, instance, false);
102     }
103 
104     @Override
105     public String deleteAll(Object instance) {
106         return buildQuery(QUERY.DELETE, instance, true);
107     }
108 
109     @Override
110     public String update(Object instanceOld, Object instanceNew) {
111         StringBuilder sb = new StringBuilder();
112 
113         sb.append(buildQuery(QUERY.DELETE, instanceOld, false));
114         sb.append(buildQuery(QUERY.INSERT, instanceNew, false));
115 
116         return sb.toString();
117     }
118 
119     @Override
120     public String updateAll(Object instanceOld, Object instanceNew) {
121         StringBuilder sb = new StringBuilder();
122 
123         sb.append(buildQuery(QUERY.DELETE, instanceOld, true));
124         sb.append(buildQuery(QUERY.INSERT, instanceNew, true));
125 
126         return sb.toString();
127     }
128 
129     /**
130      * Generate SELECT for list of instances that is based on the instance
131      * (type) and its attributes.
132      *
133      * This can work as query for "select all instances" and "select instance(s)
134      * with value of one/more attributes".
135      *
136      * @param instance Instance that defines member attributes and rdf:type.
137      * @return Generated SELECT query.
138      */
139     @Override
140     public String select(Object instance) {
141 
142         return buildQuery(QUERY.SELECT, instance, false);
143     }
144 
145     private String buildQuery(QUERY type, Object instance, boolean deep) {
146 
147         StringBuilder query = new StringBuilder();
148         // TODO query template
149         switch (type) {
150             case INSERT:
151                 query.append(TEMPLATE_INSERT);
152                 break;
153             case DELETE:
154                 query.append(TEMPLATE_DELETE);
155                 break;
156             case SELECT:
157                 query.append(TEMPLATE_SELECT);
158                 break;
159             case ASK:
160                 query.append(TEMPLATE_ASK);
161                 break;
162             default:
163                 break;
164         }
165 
166         String result = query.toString();
167 
168         if (graph != null) {
169             result = result.replace("GRAPH_BEGIN", GRAPH_BEGIN);
170             String graphName = GRAPH_NAME.replace("NAME", graph);
171             result = result.replace("GRAPH", "GRAPH " + graphName);
172             result = result.replace("GRAPH_END", GRAPH_END);
173         } else {
174             result = result.replace("GRAPH_BEGIN", "");
175             result = result.replace("GRAPH_END", "");
176         }
177 
178         String queryWherePart = buildTriples(type, instance, deep);
179 
180         if (type == QUERY.SELECT) {
181             // all where variables definition
182             queryWherePart = queryWherePart.concat(getSelectWhereVariables(instance));
183 
184             // replace all instance URIs by variable
185             String uri = getInstanceURI(instance);
186             if (uri != null) {
187                 // replace all subject URIs to SELECT_VARIABLE_URI
188                 queryWherePart = queryWherePart.replaceAll("<" + uri + ">", SELECT_VARIABLE_URI);
189 
190                 // insert: VALUES ?mreuri {<the_uri>}
191                 queryWherePart = queryWherePart.replaceFirst(SELECT_VARIABLE_URI.replace("?", "\\?"), " VALUES " + SELECT_VARIABLE_URI + " { <" + uri + "> }\n " + SELECT_VARIABLE_URI);
192             }
193 
194             result = result.replace("ATTRIBUTES", getSelectVariables(instance));
195         }
196 
197         result = result.replace("TRIPLES", queryWherePart);
198 
199         return result;
200     }
201 
202     private String buildTriples(QUERY type, Object instance, boolean deep) {
203 
204         if (type == null) {
205             return "";
206         }
207 
208         StringBuilder sb = new StringBuilder();
209 
210         MREDataFieldValue[] fields = dataService.builderValuesGet(instance);
211         for (int i = 0; i < fields.length; i++) {
212 
213             Set<Object> fieldValues = fields[i].getValue();
214 
215             // ignore empty fields
216             if (fieldValues.isEmpty()) {
217                 continue;
218             }
219 
220             // rdf:type only once
221             if (i == 0) {
222                 buildTripleRDFType(sb, fields, i, instance);
223                 sb.append("\n");
224             }
225 
226             // loop over all values
227             Iterator<Object> it = fieldValues.iterator();
228             while (it.hasNext()) {
229                 Object object = it.next();
230 
231                 // ignore empty values
232                 if (object == null) {
233                     continue;
234                 }
235 
236                 if (object instanceof List) {
237 
238                     // save list of objects
239                     List list = (List) object;
240                     for (Object o : list) {
241                         LOG.debug("Build triples - {}, List field '{}'", o.getClass(), fields[i].getName());
242                         sb.append(buildTriple(type, fields[i], o, deep));
243                         if (deep) {
244                             sb.append(buildTriples(type, o, deep));
245                         }
246                     }
247 
248                 } else if (object instanceof Set) {
249 
250                     // save set of objects
251                     Set set = (Set) object;
252                     for (Object o : set) {
253                         LOG.debug("Build triples - {}, Set field '{}'", o.getClass(), fields[i].getName());
254                         sb.append(buildTriple(type, fields[i], o, deep));
255                         if (deep) {
256                             sb.append(buildTriples(type, o, deep));
257                         }
258                     }
259 
260                 } else if (object instanceof MREData
261                         || object instanceof Individual) {
262 
263                     LOG.debug("Build triples - {}, MREData field '{}', type {}", instance.getClass(), fields[i].getName(), object.getClass());
264                     sb.append(buildTripleObject(type, fields[i], object, deep));
265 
266                 } else {
267                     LOG.debug("Build triples - {}, primitive type field '{}', type {}", instance.getClass(), fields[i].getName(), object.getClass());
268                     sb.append(buildTriple(type, fields[i], object, deep));
269                 }
270             }
271         }
272 
273         // The query.
274         String query = sb.toString();
275 
276         // return the query
277         return query;
278     }
279 
280     private void buildTripleRDFType(StringBuilder sb, MREDataFieldValue[] fields, int i, Object instance) {
281 
282         sb.append(" <").append(fields[i].getSubject()).append("> ");
283         sb.append("<").append(RDF.type).append("> ");
284         if (fields[i].getConfig().getTypeRDF() == null) {
285             sb.append("<").append(fields[i].getConfig().getOntology().concat(instance.getClass().getSimpleName())).append("> .");
286         } else {
287             sb.append("<").append(fields[i].getConfig().getTypeRDF()).append("> .");
288         }
289     }
290 
291     /**
292      * Get the string with all variable names to the SELECT part.
293      *
294      * @param instance MREData instance.
295      * @return String with all variable names for the instance class/type.
296      */
297     private String getSelectVariables(Object instance) {
298 
299         StringBuilder variables = new StringBuilder();
300         MREDataFieldValue[] fields = dataService.builderValuesGet(instance);
301         variables.append(SELECT_VARIABLE_URI);
302         variables.append(" ");
303         variables.append(SELECT_VARIABLE_TYPE).append(" ");
304         for (MREDataFieldValue field : fields) {
305             variables.append("?");
306             variables.append(field.getName());
307             variables.append(" ");
308         }
309 
310         return variables.toString();
311     }
312 
313     /**
314      * Get WHERE part (triples) with variables definition as a String.
315      *
316      * @param instance MREData instance.
317      * @return String with variables for SELECT clause. The first variable is
318      * ?mreuri (see static field SELECT_VARIABLE_URI), the second is ?mretype
319      * (see static field SELECT_VARIABLE_TYPE) and all other variables follows.
320      */
321     private String getSelectWhereVariables(Object instance) {
322 
323         MREDataFieldValue[] fields = dataService.builderValuesGet(instance);
324 
325         StringBuilder variables = new StringBuilder();
326 
327         // default variables for all generated SELECT queries (URI, rdf:type)
328         variables.append(" ");
329         variables.append(SELECT_VARIABLE_URI);
330         variables.append(" <");
331         variables.append(RDF.type);
332         variables.append("> ");
333         variables.append(SELECT_VARIABLE_TYPE);
334         variables.append(" .");
335         variables.append("\n");
336 
337         for (MREDataFieldValue field : fields) {
338             // put OPTIONAL keyword for SELECT
339             variables.append(" OPTIONAL {");
340             variables.append(" ");
341             variables.append(SELECT_VARIABLE_URI);
342             variables.append(" <");
343             variables.append(field.getProperty());
344             variables.append("> ?");
345             variables.append(field.getName());
346             variables.append(" .");
347             variables.append(" }"); // end OPTIONAL block
348             variables.append("\n");
349         }
350 
351         return variables.toString();
352     }
353 
354     /**
355      * Get instance URI.
356      *
357      * @param instance MREData instance.
358      * @return URI as a String or null.
359      */
360     private String getInstanceURI(Object instance) {
361 
362         if (instance instanceof MREData) {
363             MREData data = (MREData) instance;
364             if (data.hasURI()) {
365                 return data.getUri();
366             }
367         }
368 
369         return null;
370     }
371 
372     private String buildTriple(QUERY type, MREDataFieldValue field, Object object, boolean deep) {
373 
374         if (object == null) {
375             return EMPTY_STRING;
376         }
377 
378         //LOG.debug("Build triple - {} field {}", object.getClass(), field.getName());
379         if (object instanceof List) {
380             return buildTriples(type, object, deep);
381         } else if (object instanceof Set) {
382             return buildTriples(type, object, deep);
383         }
384 
385         if (object instanceof Individual) {
386             return buildTripleObject(type, field, ((Individual) object).getURI(), deep);
387         } else if (object instanceof MREData) {
388             return buildTripleObject(type, field, ((MREData) object).getUri(), deep);
389         } else {
390             return buildTripleDatatype(type, field, object);
391         }
392     }
393 
394     private String buildTripleObject(QUERY type, MREDataFieldValue field, Object object, boolean deep) {
395 
396         if (object == null) {
397             return EMPTY_STRING;
398         }
399 
400         StringBuilder sb = new StringBuilder();
401 
402         sb.append(" <").append(field.getSubject()).append("> ");
403         sb.append("<").append(field.getProperty().toString()).append("> ");
404 
405         sb.append("<");
406         if (object instanceof MREData) {
407             // add MREData instance resource URI
408             sb.append(((MREData) object).getResource().getURI());
409         } else if (object instanceof Individual) {
410             sb.append(((Individual) object).getURI());
411         } else if (object instanceof String) {
412             sb.append(((String) object));
413         } else {
414             LOG.warn("Unsupported Java {} value {}. Using toString() method.", object.getClass(), object);
415             // use toString() for other Java classes
416             sb.append(object.toString());
417         }
418 
419         sb.append("> .");
420         sb.append("\n");
421         return sb.toString();
422     }
423 
424     private String buildTripleDatatype(QUERY type, MREDataFieldValue field, Object value) {
425         StringBuilder sb = new StringBuilder();
426 
427         sb.append(" <").append(field.getSubject()).append("> ");
428         sb.append("<").append(field.getProperty().toString()).append("> ");
429 
430         Literal literalValue;
431         if (value instanceof Date) {
432             // XSD Date format as String
433             String date = DateUtil.xsdDate((Date) value);
434             literalValue = ResourceFactory.createTypedLiteral(date);
435 
436         } else if (value instanceof LocalDate) {
437             // XSD Date format as String
438             LocalDate localDate = (LocalDate) value;
439             String date = localDate.toString();
440             literalValue = ResourceFactory.createTypedLiteral(date, XSDDateType.XSDdate);
441 
442         } else if (value instanceof LocalDateTime) {
443             // XSD Date format as String
444             LocalDateTime localDateTime = (LocalDateTime) value;
445             String dateTime = localDateTime.toString();
446 
447             // TODO check for dateTime format (due to ignored zeros) to fill in correct date type
448             literalValue = ResourceFactory.createTypedLiteral(dateTime, XSDDateType.XSDdateTime);
449 
450         } else {
451             // literal value
452             literalValue = buildTripleDatatypeValue(type, field, value);
453         }
454 
455         // output literal value in N-TRIPLE
456         if (literalValue != null) {
457 
458             if (literalValue.getLanguage() != null
459                     && !literalValue.getLanguage().isEmpty()) {
460                 // only for a non empty language
461                 sb.append("\"").append(literalValue.getString()).append("\"@").append(literalValue.getLanguage());
462             } else if (literalValue.getDatatype() != null
463                     && !literalValue.getDatatype().equals(XSDDatatype.XSDstring)) {
464                 // only for datatype different to xsd:string
465                 sb.append("\"").append(makeValidString(literalValue.getString())).append("\"^^<").append(literalValue.getDatatypeURI()).append(">");
466             } else {
467                 sb.append("\"").append(makeValidString(literalValue.getString())).append("\"");
468                 //sb.append(literalValue);
469             }
470         }
471 
472         sb.append(" .");
473         sb.append("\n");
474         return sb.toString();
475     }
476 
477     private String makeValidString(String textContent) {
478 
479         String result = textContent;
480 
481         if (textContent.contains("\"")) {
482             result = textContent.replace("\"", "\\\"");
483         }
484 
485         return result;
486     }
487 
488     private Literal buildTripleDatatypeValue(QUERY type, MREDataFieldValue field, Object instance) {
489 
490         if (instance == null) {
491             return null;
492         }
493 
494         if (instance instanceof String) {
495             // just string, no language, (default)
496             return ResourceFactory.createPlainLiteral((String) instance);
497 
498         } else if (instance instanceof MREStringLang) {
499 
500             // no lang specified here means plain literal
501             if (((MREStringLang) instance).getLang() == null) {
502                 return ResourceFactory.createPlainLiteral(((MREStringLang) instance).getString());
503             }
504 
505             // string with language
506             return ResourceFactory.createLangLiteral(((MREStringLang) instance).getString(), ((MREStringLang) instance).getLang());
507         }
508 
509         // typed literal
510         return ResourceFactory.createTypedLiteral(instance);
511     }
512 
513     /**
514      * Build ASK query for test if the instance is persistent/exist.
515      *
516      * @param instance MREData instance.
517      * @return String with ASK query with only one triple (instance URI,
518      * rdf:type property and class URI for object).
519      */
520     @Override
521     public String askExist(MREData instance) {
522 
523         StringBuilder sb = new StringBuilder();
524         MREDataFieldValue[] fields = dataService.builderValuesGet(instance);
525         int i = 0;
526 
527         // add rdf:type triple
528         buildTripleRDFType(sb, fields, i, instance);
529 
530         String ask = TEMPLATE_ASK.replace("TRIPLES", sb.toString());
531 
532         return ask;
533     }
534 
535     @Override
536     public String selectType(String resourceURI) {
537 
538         StringBuilder sb = new StringBuilder();
539         sb.append("SELECT DISTINCT ?type WHERE { ");
540         sb.append("<").append(resourceURI).append("> ");
541         sb.append("<").append(RDF.type).append("> ");
542         sb.append("?type . } ");
543 
544         return sb.toString();
545     }
546 
547     @Override
548     public String selectType(Resource resource) {
549 
550         return selectType(resource.getURI());
551     }
552 
553     @Override
554     public String getGraph() {
555         return graph;
556     }
557 
558     @Override
559     public void setGraph(String graph) {
560         this.graph = graph;
561     }
562 
563 }