View Javadoc
1   /*
2    * Copyright 2013-2023 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    * This file is part of Sparkle project.
7    *
8    * Sparkle is free software: you can redistribute it and/or modify
9    * it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License.
11   *
12   * Sparkle is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with Sparkle. If not, see <http://www.gnu.org/licenses/>.
19   */
20  package cz.zcu.mre.sparkle.data;
21  
22  import cz.zcu.mre.sparkle.data.dataAgentFactory.DataAgentFactory;
23  import cz.zcu.mre.sparkle.data.dataAgentFactory.SparqlEndpointDataAgent;
24  import cz.zcu.mre.sparkle.tools.CancellableConsumer;
25  import cz.zcu.mre.sparkle.tools.CancellableConsumer.State;
26  import cz.zcu.mre.sparkle.tools.Changeable;
27  import javafx.beans.Observable;
28  import javafx.beans.property.SimpleBooleanProperty;
29  import javafx.beans.value.ChangeListener;
30  import org.apache.jena.query.*;
31  import org.apache.jena.rdf.model.*;
32  import org.apache.jena.update.UpdateAction;
33  import org.apache.jena.update.UpdateRequest;
34  import org.apache.jena.util.FileManager;
35  import org.apache.jena.vocabulary.RDF;
36  import org.apache.jena.vocabulary.RDFS;
37  import java.io.File;
38  import java.sql.SQLException;
39  import java.util.AbstractMap.SimpleEntry;
40  import java.util.*;
41  import org.apache.jena.sparql.exec.http.QueryExecutionHTTPBuilder;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  /**
46   * Třída představující rozhraní mezi úložištěm a zbytkem aplikace. Pro vytvoření
47   * instance je nutné použít metod třídy {@link DataAgentFactory}.
48   *
49   * @author Jan Smucr
50   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
51   * @author Klara Hlavacova
52   */
53  public abstract class DataAgent implements Observable, Changeable {
54  
55      private static final Logger LOG = LoggerFactory.getLogger(DataAgent.class);
56  
57      protected DataAgent() {
58  
59      }
60  
61      public abstract SimpleBooleanProperty isNullProperty();
62  
63      /**
64       * @return {@link Model} pro přístup k úložišti.
65       */
66      protected abstract Model getModel();
67  
68      /**
69       * @return Název úložiště (connection string, umístění a pod.).
70       */
71      public abstract String getStorageName();
72  
73      /**
74       * It makes class instance that implementing {@link Query}.
75       *
76       * @param query Query text.
77       *
78       * @return Instance of class that implementing {@link Query}.
79       */
80      public static Query createQuery(final String query) {
81          return QueryFactory.create(query);
82      }
83  
84      /**
85       * Vrati {@code QueryExecution} pro dany model nebo sparql sluzbu ktera se
86       * provede pres http
87       *
88       * @param query sparql dotaz
89       *
90       * @return {@code QueryExecution} pro dany modelem nebo sparql sluzbu ktera
91       * se provede pres http
92       */
93      private QueryExecution getQueryExecution(Query query) {
94          // sparqlService - pripojeni pres htttp k sparql endpoint
95          // create - lokalni/vzdalene uloziste
96          if (getModel() == null) {
97              QueryExecutionHTTPBuilder builder = QueryExecution.service(getStorageName());
98              builder.httpClient(SparqlEndpointDataAgent.getHttpClient());
99              builder.query(query);
100             return builder.build();
101         } else {
102             return QueryExecution.create(query, getModel());
103         }
104     }
105 
106     /**
107      * Vykoná dotaz typu SELECT.
108      *
109      * @param query Znění dotazu.
110      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
111      * <code>null</code>, výsledek je vypsán do {@link System#out}.
112      */
113     public void select(final String query, final CancellableConsumer<? super QuerySolution> consumer) {
114         select(QueryFactory.create(query), consumer);
115     }
116 
117     /**
118      * javascript cannot recognize the correct select function to call, call
119      * this one for 100 % correctness
120      *
121      * @param query
122      * @param consumer
123      */
124     public void selectVisual(final String query, final CancellableConsumer<? super QuerySolution> consumer) {
125         select(QueryFactory.create(query), consumer);
126     }
127 
128     /**
129      * Vykoná dotaz typu SELECT.
130      *
131      * @param query Dotaz.
132      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
133      * <code>null</code>, výsledek je vypsán do {@link System#out}.
134      */
135     public void select(final Query query, final CancellableConsumer<? super QuerySolution> consumer) {
136         //System.out.println(query);
137         final QueryExecution queryExecution = getQueryExecution(query);
138 
139         if (consumer == null) {
140             // System.out.println(ResultSetFormatter.asText(queryExecution.execSelect()));
141 
142             try {
143                 queryExecution.close();
144             } catch (final Exception e) {
145                 //System.out.println("e");
146                 LOG.error("Exception: ", e);//$NON-NLS-1$
147             }
148         } else {
149             // Vytvoří posluchače, který přeruší vykonávání dotazu, pokud přejde consumer do stavu Cancelled.
150             final ChangeListener<State> cancellationListener = createStateListener(queryExecution);
151 
152             consumer.addListener(cancellationListener);
153 
154             try {
155                 final Iterator<QuerySolution> iter = queryExecution.execSelect();
156                 final Model model = getModel();
157 
158                 while (iter.hasNext()) {
159                     if (consumer.isCancelled() || model != null && model.isClosed()) {
160                         break;
161                     }
162                     consumer.accept(iter.next());
163                     if (consumer.isCancelled() || model != null && model.isClosed()) {
164                         break;
165                     }
166                 }
167             } finally {
168                 consumer.removeListener(cancellationListener);
169                 consumer.notifyFinished();
170                 try {
171 
172                     queryExecution.close();
173                 } catch (final Exception e) {
174                     LOG.error("Exception: ", e);//$NON-NLS-1$
175                 }
176             }
177         }
178     }
179 
180     /**
181      * Vykoná dotaz typu ASK.
182      *
183      * @param query Znění dotazu.
184      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
185      * <code>null</code>, výsledek je vypsán do {@link System#out}.
186      */
187     public void ask(final String query, final CancellableConsumer<? super Boolean> consumer) {
188         ask(QueryFactory.create(query), consumer);
189     }
190 
191     /**
192      * Vykoná dotaz typu ASK.
193      *
194      * @param query Dotaz.
195      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
196      * <code>null</code>, výsledek je vypsán do {@link System#out}.
197      */
198     public void ask(final Query query, final CancellableConsumer<? super Boolean> consumer) {
199         final QueryExecution queryExecution = getQueryExecution(query);
200 
201         if (consumer == null) {
202             // System.out.println(queryExecution.execAsk());
203 
204             try {
205                 queryExecution.close();
206             } catch (final Exception e) {
207                 LOG.error("Exception: ", e);//$NON-NLS-1$
208             }
209         } else {
210             // Vytvoří posluchače, který přeruší vykonávání dotazu, pokud přejde consumer do stavu Cancelled.
211             final ChangeListener<State> cancellationListener = createStateListener(queryExecution);
212             consumer.addListener(cancellationListener);
213 
214             try {
215                 consumer.accept(queryExecution.execAsk());
216             } finally {
217                 consumer.removeListener(cancellationListener);
218                 consumer.notifyFinished();
219                 try {
220                     queryExecution.close();
221                 } catch (final Exception e) {
222                     LOG.error("Exception: ", e);//$NON-NLS-1$
223                 }
224             }
225         }
226     }
227 
228     /**
229      * Vykoná dotaz typu CONSTRUCT.
230      *
231      * @param query Znění dotazu.
232      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
233      * <code>null</code>, výsledek je vypsán do {@link System#out}.
234      */
235     public void construct(final String query, final CancellableConsumer<? super Model> consumer) {
236         construct(QueryFactory.create(query), consumer);
237     }
238 
239     /**
240      * Vykoná dotaz typu CONSTRUCT.
241      *
242      * @param query Dotaz.
243      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
244      * <code>null</code>, výsledek je vypsán do {@link System#out}.
245      */
246     public void construct(final Query query, final CancellableConsumer<? super Model> consumer) {
247         //System.err.println(query);
248         final QueryExecution queryExecution = getQueryExecution(query);
249 
250         if (consumer == null) {
251             queryExecution.execConstruct().write(System.out);
252 
253             try {
254                 queryExecution.close();
255             } catch (final Exception e) {
256                 LOG.error("Exception: ", e);//$NON-NLS-1$
257             }
258         } else {
259             // Vytvoří posluchače, který přeruší vykonávání dotazu, pokud přejde consumer do stavu Cancelled.
260             final ChangeListener<State> cancellationListener = createStateListener(queryExecution);
261             consumer.addListener(cancellationListener);
262 
263             try {
264                 consumer.accept(queryExecution.execConstruct());
265             } finally {
266                 consumer.removeListener(cancellationListener);
267                 consumer.notifyFinished();
268 
269                 try {
270                     queryExecution.close();
271                 } catch (final Exception e) {
272                     LOG.error("Exception: ", e);//$NON-NLS-1$
273                 }
274             }
275         }
276     }
277 
278     /**
279      * Vykoná dotaz typu DESCRIBE.
280      *
281      * @param query Znění dotazu.
282      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
283      * <code>null</code>, výsledek je vypsán do {@link System#out}.
284      */
285     public void describe(final String query, final CancellableConsumer<? super Model> consumer) {
286         describe(QueryFactory.create(query), consumer);
287     }
288 
289     /**
290      * Vykoná dotaz typu DESCRIBE.
291      *
292      * @param query Dotaz.
293      * @param consumer Instance třídy sloužící k předání výsledků. Pokud je
294      * <code>null</code>, výsledek je vypsán do {@link System#out}.
295      */
296     public void describe(final Query query, final CancellableConsumer<? super Model> consumer) {
297         final QueryExecution queryExecution = getQueryExecution(query);
298 
299         if (consumer == null) {
300             queryExecution.execDescribe().write(System.out);
301 
302             try {
303                 queryExecution.close();
304             } catch (final Exception e) {
305                 LOG.error("Exception: ", e);//$NON-NLS-1$
306             }
307         } else {
308             // Vytvoří posluchače, který přeruší vykonávání dotazu, pokud přejde consumer do stavu Cancelled.
309             final ChangeListener<State> cancellationListener = createStateListener(queryExecution);
310             consumer.addListener(cancellationListener);
311 
312             try {
313                 consumer.accept(queryExecution.execDescribe());
314             } finally {
315                 consumer.removeListener(cancellationListener);
316                 consumer.notifyFinished();
317 
318                 try {
319                     queryExecution.close();
320                 } catch (final Exception e) {
321                     LOG.error("Exception: ", e);//$NON-NLS-1$
322                 }
323             }
324         }
325     }
326 
327     /**
328      * Vytvoří posluchače, který přeruší vykonávání dotazu, pokud přejde
329      * consumer do stavu Cancelled.
330      *
331      * @param queryExecution Objekt zodpověný za vykonání dotazu.
332      *
333      * @return Posluchač.
334      */
335     private static ChangeListener<State> createStateListener(final QueryExecution queryExecution) {
336         return (observable, oldValue, newValue) -> {
337             if (newValue == State.Cancelled) {
338                 queryExecution.abort();
339             }
340         };
341     }
342 
343     /**
344      * It executes INSERT, DELETE and INSERT/DELETE query.
345      *
346      * @param request Query text.
347      * @param consumer Instance of class used for transmission of results. If it
348      * is <Code>null</code>, the result is printed in {@link System#out}.
349      */
350     public void updateAction(final UpdateRequest request, final CancellableConsumer<? super Boolean> consumer)
351             throws UnsupportedOperationException {
352 
353         //TODO: endpoint - model == null -> hlaska
354         if (getModel() == null) {
355             throw new UnsupportedOperationException();
356         }
357 
358         try {
359             UpdateAction.execute(request, getModel());
360 
361             consumer.accept(true);
362             consumer.notifyFinished();
363 
364         } catch (final Exception e) {
365             consumer.accept(false);
366             LOG.error("Exception: ", e);//$NON-NLS-1$
367         }
368     }
369 
370     /**
371      * Přidá do grafu RDF soubor v jednom z formátů podporovaných knihovnou
372      * Jena.
373      *
374      * @param file Soubor.
375      */
376     public void addToModel(final File file) {
377         //getModel().read(file.toURI().toASCIIString());
378         FileManager.get().readModel(getModel(), file.getAbsolutePath());
379     }
380 
381     public Model getFullModel() {
382         return getModel();
383     }
384 
385     /**
386      * Vyčistí graf.
387      */
388     public void clearModel() {
389         final Model model = getModel();
390         model.removeAll();
391     }
392 
393     /**
394      * Uzavře graf (u lokálního úložiště způsobí uložení na disk).
395      *
396      * @throws SQLException when closing the graph.
397      */
398     public abstract void close() throws SQLException;
399 
400     /**
401      * Pokusí se o nalezení cesty mezi dvěma zdroji.
402      *
403      * @param start Počáteční zdroj v kontextu aktuálního grafu.
404      * @param end Koncový zdroj v kontextu aktuálního grafu.
405      *
406      * @return Cesta.
407      */
408     public LinkedList<Resource> findForwardOntologyPath(Resource start, Resource end) {
409         if (start.getModel() == null) {
410             start = inModel(start);
411         }
412         if (end.getModel() == null) {
413             end = inModel(end);
414         }
415 
416         // Navštívené uzly
417         final LinkedHashMap<Resource, Resource> visited = new LinkedHashMap<>();
418         // Nenavštívené ale již objevené uzly
419         final LinkedList<SimpleEntry<Resource, Resource>> queue = new LinkedList<>();
420 
421         // Identifikace všech možných konců cesty (podtřídy nebo odděděné vlastnosti)
422         final boolean endIsProperty = end.hasProperty(RDF.type, RDF.Property);
423         final LinkedHashSet<String> endSubClassesOrSubPropertiesIRIs = new LinkedHashSet<>();
424         final StmtIterator si = end.listProperties(endIsProperty ? RDFS.subPropertyOf : RDFS.subClassOf);
425 
426         while (si.hasNext()) {
427             final RDFNode objectNode = si.next().getObject();
428             if (objectNode.isURIResource()) {
429                 endSubClassesOrSubPropertiesIRIs.add(objectNode.asResource().getURI());
430             }
431         }
432         si.close();
433 
434         // Počáteční bod cesty do fronty
435         queue.add(new SimpleEntry<>(start, null));
436 
437         // Flag nalezení cesty
438         boolean found = false;
439 
440         // Předchůdce aktuálně prohledávaného uzlu
441         Resource parent = start;
442 
443         while (!queue.isEmpty()) {
444             final SimpleEntry<Resource, Resource> entry = queue.pollFirst();
445             if (entry == null) {
446                 continue;
447             }
448             final Resource resource = entry.getKey();
449             parent = entry.getValue();
450 
451             // Uložení uzlu i předchůdce kvůli rekonstrukci cesty
452             visited.put(resource, parent);
453 
454             // Druhá část podmínky by mohla působit problémy
455             if (resource.equals(end)
456                     || (resource.isURIResource() && endSubClassesOrSubPropertiesIRIs.contains(resource.getURI()))) {
457                 found = true;
458                 break;
459             }
460 
461             final Set<? extends Resource> _toVisit;
462 
463             // Stanovení, zda jde nebo nejde o vlastnost a rozbalení uzlu
464             if (resource.hasProperty(RDF.type, RDF.Property)) {
465                 final Property property = resource.as(Property.class);
466                 final Set<Resource> toVisit = DataHelper.getResourcesInRange(property);
467                 DataHelper.eliminateParentTypes(toVisit);
468                 _toVisit = toVisit;
469             } else {
470                 final Set<Property> toVisit = DataHelper.getPropertiesWithResourceInDomain(resource);
471                 toVisit.addAll(DataHelper.getResourceKeys(resource));
472                 _toVisit = toVisit;
473             }
474 
475             // Vyhodí ze seznamu všechny typy rodičovské pro ostatní typy na seznamu
476             //DataHelper.eliminateParentTypes(toVisit);
477             _toVisit.stream().filter((resourceToVisit) -> (!visited.containsKey(resourceToVisit)))
478                     .forEach((resourceToVisit) -> queue.addLast(new SimpleEntry<>(resourceToVisit, resource))); // Ochrana proti zacyklení
479         }
480 
481         if (!found) {
482             return null;
483         }
484 
485         // Rekonstrukce cesty
486         final LinkedList<Resource> path = new LinkedList<>();
487         path.addFirst(end);
488         Resource pathItem = parent;
489 
490         while (pathItem != start) {
491             path.addFirst(pathItem);
492             pathItem = visited.get(pathItem);
493         }
494         path.addFirst(pathItem);
495 
496         return path;
497     }
498 
499     /**
500      * Převede zdroj do kontextu aktuálního grafu.
501      *
502      * @param <T> type of Resource.
503      * @param resource Zdroj.
504      *
505      * @return Zdroj v kontextu grafu.
506      */
507     @SuppressWarnings("unchecked")
508     public <T extends Resource> T inModel(final Resource resource) {
509         final Class<? extends Resource> classOfResource = resource.getClass();
510         return (T) resource.inModel(getModel()).as(classOfResource);
511     }
512 
513     /**
514      * Vrati seznam cest k datovym souborum importovanych do uloziste,
515      * {@code null} pokud neni podporovano
516      *
517      * @return Seznam datovych souboru importovanych do uloziste, {@code null}
518      * pokud neni podporovano
519      */
520     public abstract List<String> getImportedFiles();
521 }