]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.common/src/org/simantics/db/common/utils/Transaction.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.db.common / src / org / simantics / db / common / utils / Transaction.java
1 /*******************************************************************************
2  * Copyright (c) 2007 VTT Technical Research Centre of Finland and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     VTT Technical Research Centre of Finland - initial API and implementation
10  *******************************************************************************/
11 package org.simantics.db.common.utils;
12
13 import java.util.Collection;
14 import java.util.Set;
15 import java.util.concurrent.Semaphore;
16 import java.util.function.Consumer;
17
18 import org.simantics.databoard.accessor.Accessor;
19 import org.simantics.databoard.binding.Binding;
20 import org.simantics.databoard.type.Datatype;
21 import org.simantics.db.AsyncRequestProcessor;
22 import org.simantics.db.ReadGraph;
23 import org.simantics.db.RequestProcessor;
24 import org.simantics.db.Resource;
25 import org.simantics.db.Statement;
26 import org.simantics.db.WriteGraph;
27 import org.simantics.db.WriteOnlyGraph;
28 import org.simantics.db.common.request.DelayedWriteRequest;
29 import org.simantics.db.exception.AdaptionException;
30 import org.simantics.db.exception.BindingException;
31 import org.simantics.db.exception.CancelTransactionException;
32 import org.simantics.db.exception.DatabaseException;
33 import org.simantics.db.exception.DoesNotContainValueException;
34 import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
35 import org.simantics.db.exception.NoInverseException;
36 import org.simantics.db.exception.NoSingleResultException;
37 import org.simantics.db.exception.ResourceNotFoundException;
38 import org.simantics.db.exception.RuntimeDatabaseException;
39 import org.simantics.db.exception.ServiceException;
40 import org.simantics.db.exception.ValidationException;
41 import org.simantics.db.procedure.Procedure;
42 import org.simantics.db.request.DelayedWrite;
43 import org.simantics.db.request.Read;
44 import org.simantics.db.request.Write;
45
46 /**
47  * Synchronous Transaction. <p>
48  * 
49  * Hint: Import all methods as static.
50  * import static org.simantics.db.Transaction.*; 
51  * 
52  * Remember also to change Eclipse Preferences:
53  *   Organize Imports: number of static imports needed for .* on vakiona 99, siihen vaikka 3
54  *
55  * 
56  * Usage A:
57  * 
58  * startTransaction(session, true);
59  * try {
60  *  ...
61  *    commit();
62  * } finally {
63  *    endTransaction();
64  * }
65  * 
66  * Usage B:
67  * setGraph(g);
68  * ...
69  * setGraph(null);
70  *
71  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
72  */
73 public class Transaction {
74
75         public static enum Type {
76                 READ,
77                 WRITE,
78                 DELAYED_WRITE
79         }
80         
81         /** Thread local transactions */
82         private static ThreadLocal<TransactionInfo> transactions = new ThreadLocal<TransactionInfo>();
83         
84         static private class TransactionInfo {
85                 WriteGraph wg;
86                 ReadGraph rg;
87                 
88                 /** Semaphore that is released when the transaction is complete */
89                 Semaphore ts;
90                 
91                 /** Semaphore that is release after transaction ended */
92                 Semaphore es;
93                 
94                 /** Error */
95                 DatabaseException error;
96                 
97                 /** */
98                 boolean commit = false;
99                 
100                 TransactionInfo() {
101                 }
102                 
103                 TransactionInfo(Object graph) {
104                         if (graph instanceof WriteGraph) {
105                                 wg = (WriteGraph) graph;
106                                 rg = (ReadGraph) graph;
107                         } else if (graph instanceof ReadGraph) {
108                                 wg = null;
109                                 rg = (ReadGraph) graph;
110                         } else {
111                                 throw new RuntimeDatabaseException("Not a sync graph");
112                         }                       
113                 }
114         }
115         
116         public static ReadGraph readGraph() {
117                 TransactionInfo t = transactions.get();
118                 return t == null ? null : t.rg;
119         }
120         
121         public static WriteGraph writeGraph() {
122                 TransactionInfo t = transactions.get();
123                 return t == null ? null : t.wg;
124         }
125                 
126         public static Object setGraph(Object graph) {
127                 if (graph==null) {
128                         transactions.set(null);
129                         return null;
130                 }
131                 
132                 {
133                         Object oldGraph = null;
134                         TransactionInfo t = transactions.get();
135                         if (t!=null) {                          
136                                 oldGraph = t.rg;
137                                 if (graph instanceof WriteGraph) {
138                                         t.wg = (WriteGraph) graph;
139                                         t.rg = (ReadGraph) graph;
140                                 } else if (graph instanceof ReadGraph) {
141                                         t.wg = null;
142                                         t.rg = (ReadGraph) graph;
143                                 } else {
144                                         throw new RuntimeDatabaseException("Not a sync graph");
145                                 }                                                       
146                         } else {
147                                 t = new TransactionInfo(graph);
148                         }
149                         transactions.set(t);
150                         return oldGraph;
151                 }
152         }
153         
154         public static void startTransaction(AsyncRequestProcessor processor, boolean write) throws DatabaseException {
155                 startTransaction(processor, write ? Type.WRITE : Type.READ);
156         }
157         
158         public static void startTransaction(AsyncRequestProcessor processor, Type type) throws DatabaseException {
159                 switch (type) {
160                         case READ:
161                         {
162                                 if (transactions.get()!=null) throw new RuntimeDatabaseException("There is already a transaction.");
163                                 final Semaphore started = new Semaphore(0);
164                                 final TransactionInfo t = new TransactionInfo();
165                                 t.es = new Semaphore(0);
166                                 t.ts = new Semaphore(0);
167                                 transactions.set(t);
168
169                                 Read<Object> request = new Read<Object>() {
170                                         @Override
171                                         public Object perform(ReadGraph g) throws DatabaseException {
172                                                 t.wg = null;
173                                                 t.rg = g;
174                                                 started.release();
175                                                 try {
176                                                         t.ts.acquire();
177                                                 } catch (InterruptedException e) {
178                                                 }
179                                                 return null;
180                                         }
181                                 };
182                                 Procedure<Object> procedure = new Procedure<Object>() {
183                                     @Override
184                                     public void execute(Object result) {
185                                         t.es.release(9999);
186                                     }
187                                     @Override
188                                     public void exception(Throwable ex) {
189                                         if (ex instanceof DatabaseException)
190                                             t.error = (DatabaseException) ex;
191                                         else {
192                                             t.error = new DatabaseException(ex);
193                                         }
194                                         t.es.release(9999);
195                                     }
196                                 };
197
198                                 processor.asyncRequest(request, procedure);
199
200                                 // Wait until transaction has started
201                                 try {
202                                         // Sleep this thread until transaction has started
203                                         started.acquire(1);
204                                 } catch (InterruptedException e) {
205                                         throw new DatabaseException("Thread was interrupted.");
206                                 } finally {
207                                 }
208                         }
209                         break;
210
211                         case WRITE:
212                         {
213                                 if (transactions.get()!=null) throw new RuntimeDatabaseException("There is already a transaction.");
214                                 final Semaphore started = new Semaphore(0);
215                                 final TransactionInfo t = new TransactionInfo();
216                                 t.es = new Semaphore(0);
217                                 t.ts = new Semaphore(0);
218                                 transactions.set(t);
219
220                                 Consumer<DatabaseException> callback = parameter -> {
221                                         t.error = parameter;
222                                         t.es.release(9999);
223                                 };
224
225                                 Write request =  new Write() { 
226                                         @Override
227                                         public void perform(WriteGraph g) throws DatabaseException {
228                                                 t.wg = g;
229                                                 t.rg = g;
230                                                 started.release();
231                                                 try {
232                                                         t.ts.acquire();
233                                                 } catch (InterruptedException e) {
234                                                 }
235                                                 if (!t.commit) throw new CancelTransactionException();
236                                         }
237                                 };
238
239                                 processor.asyncRequest( request, callback );
240
241                                 // Wait until transaction has started
242                                 try {
243                                         // Sleep this thread until transaction has started
244                                         started.acquire(1);
245                                 } catch (InterruptedException e) {
246                                         throw new DatabaseException("Thread was interrupted.");
247                                 } finally {
248                                 }
249                         }
250                         break;
251
252                         case DELAYED_WRITE:
253                         {
254                                 if (transactions.get()!=null) throw new RuntimeDatabaseException("There is already a transaction.");
255                                 final Semaphore started = new Semaphore(0);
256                                 final TransactionInfo t = new TransactionInfo();
257                                 t.es = new Semaphore(0);
258                                 t.ts = new Semaphore(0);
259                                 transactions.set(t);
260
261                                 Consumer<DatabaseException> callback = parameter -> {
262                                         t.error = parameter;
263                                         t.es.release(9999);
264                                 };
265
266                                 DelayedWrite request =  new DelayedWriteRequest() { 
267                                         @Override
268                                         public void perform(WriteGraph g) throws DatabaseException {
269                                                 t.wg = g;
270                                                 t.rg = g;
271                                                 started.release();
272                                                 try {
273                                                         t.ts.acquire();
274                                                 } catch (InterruptedException e) {
275                                                 }
276                                                 if (!t.commit) throw new CancelTransactionException();
277                                         }
278                                 };
279
280                                 processor.asyncRequest( request, callback );
281
282                                 // Wait until transaction has started
283                                 try {
284                                         // Sleep this thread until transaction has started
285                                         started.acquire(1);
286                                 } catch (InterruptedException e) {
287                                         throw new DatabaseException("Thread was interrupted.");
288                                 } finally {
289                                 }
290                         }
291                         break;
292                 }
293         }
294         
295         /**
296          * Commits transaction if no error occurred 
297          * 
298          * @throws DatabaseException
299          */
300         public static void endTransaction() throws DatabaseException {
301                 TransactionInfo t = transactions.get();
302                 if (t == null) return; //throw new RuntimeDatabaseException("There is no transaction to commit.");
303                 
304                 t.ts.release(9999);
305                 
306                 try {
307                         t.es.acquire();
308                 } catch (InterruptedException e) {
309                         throw new DatabaseException(e);
310                 }                               
311                 
312                 if (t.error!=null) {
313                         if (t.error instanceof CancelTransactionException==false) throw t.error;                
314                 }
315                 transactions.set(null);
316         }
317
318         /**
319          * Commits transaction  
320          * 
321          * @throws DatabaseException
322          */
323         public static void commit() throws DatabaseException {
324                 TransactionInfo t = transactions.get();
325                 if (t == null) throw new RuntimeDatabaseException("There is not transaction to commit.");
326                 t.commit = true;
327                 endTransaction();
328         }
329         
330     public static String getURI(Resource resource) throws ResourceNotFoundException, ValidationException, ServiceException {
331         return readGraph().getPossibleURI(resource);
332     }
333     
334     public static String getPossibleURI(Resource resource) throws ResourceNotFoundException, ValidationException, ServiceException {
335         return readGraph().getPossibleURI(resource);
336     }
337         
338     public static Resource getResource(String uri) throws ResourceNotFoundException, ValidationException, ServiceException 
339     {
340         return readGraph().getResource(uri);
341     }
342
343     public static Resource getPossibleResource(String uri) throws ResourceNotFoundException, ValidationException, ServiceException
344     {
345         return readGraph().getPossibleResource(uri);
346     }
347     
348     public static Resource getBuiltin(String id) throws ResourceNotFoundException, ServiceException
349     {
350         return readGraph().getBuiltin(id);
351     }
352
353     public static Collection<Statement> getStatements(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
354     {
355         return readGraph().getStatements(subject, relation);
356     }
357
358     public static Collection<Statement> getAssertedStatements(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
359     {
360         return readGraph().getAssertedStatements(subject, relation);            
361     }    
362     
363     public static Collection<Resource> getPredicates(Resource subject) throws ServiceException
364     {
365         return readGraph().getPredicates(subject);      
366     }
367
368     public static Collection<Resource> getPrincipalTypes(Resource subject) throws ServiceException
369     {
370         return readGraph().getPrincipalTypes(subject);
371     }
372
373     public static Set<Resource> getTypes(Resource subject) throws ServiceException
374     {
375         return readGraph().getTypes(subject);
376     }
377
378     public static Set<Resource> getSupertypes(Resource subject) throws ServiceException
379     {
380         return readGraph().getSupertypes(subject);
381     }
382
383     public static Set<Resource> getSuperrelations(Resource subject) throws ServiceException
384     {
385         return readGraph().getSuperrelations(subject);
386     }
387
388     public static Collection<Resource> getObjects(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
389     {
390         return readGraph().getObjects(subject, relation);
391     }
392
393     public static Collection<Resource> getAssertedObjects(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
394     {
395         return readGraph().getAssertedObjects(subject, relation);
396     }
397     
398     public static Resource getInverse(Resource relation) throws NoInverseException, ManyObjectsForFunctionalRelationException, ServiceException
399     {
400         return readGraph().getInverse(relation);
401     }
402     
403     public static Resource getSingleObject(Resource subject, Resource relation) throws NoSingleResultException, ManyObjectsForFunctionalRelationException, ServiceException
404     {
405         return readGraph().getSingleObject(subject, relation);
406     }
407     
408     public static Statement getSingleStatement(Resource subject, Resource relation) throws NoSingleResultException, ManyObjectsForFunctionalRelationException, ServiceException
409     {
410         return readGraph().getSingleStatement(subject, relation);
411     }
412
413     public static Resource getSingleType(Resource subject) throws NoSingleResultException, ServiceException
414     {
415         return readGraph().getSingleType(subject);
416     }
417     
418     public static Resource getSingleType(Resource subject, Resource baseType) throws NoSingleResultException, ServiceException
419     {
420         return readGraph().getSingleType(subject, baseType);
421     }
422
423     public static <T> T getValue(Resource subject) throws DoesNotContainValueException, ServiceException
424     {
425         return readGraph().<T>getValue(subject);
426     }
427
428     public static <T> T getValue(Resource subject, Binding binding) throws DoesNotContainValueException, BindingException, ServiceException
429     {
430         return readGraph().<T>getValue(subject, binding);
431     }
432
433     public static <T> T getRelatedValue(Resource subject, Resource relation) throws NoSingleResultException, DoesNotContainValueException, ServiceException
434     {
435         return readGraph().<T>getRelatedValue(subject, relation);
436     }
437
438     public static <T> T getRelatedValue(Resource subject, Resource relation, Binding binding) throws NoSingleResultException, DoesNotContainValueException, BindingException, ServiceException
439     {
440         return readGraph().<T>getRelatedValue(subject, relation, binding);
441     }
442
443     public static <T> T adapt(Resource resource, Class<T> clazz) throws AdaptionException, ValidationException, ServiceException
444     {
445         return readGraph().adapt(resource, clazz);
446     }
447
448     public static <T> T adaptUnique(Resource resource, Class<T> clazz) throws AdaptionException, ValidationException, ServiceException
449     {
450         return readGraph().adaptUnique(resource, clazz);
451     }
452     
453     public static Resource getPossibleInverse(Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
454     {
455         return readGraph().getPossibleInverse(relation);
456     }
457     
458     public static Resource getPossibleObject(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
459     {
460         return readGraph().getPossibleObject(subject, relation);
461     }
462     
463     public static Statement getPossibleStatement(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
464     {
465         return readGraph().getPossibleStatement(subject, relation);
466     }
467     
468     public static Resource getPossibleType(Resource subject, Resource baseType) throws ServiceException
469     {
470         return readGraph().getPossibleType(subject, baseType);
471     }
472     
473     public static <T> T getPossibleValue(Resource subject) throws ServiceException
474     {
475         return readGraph().<T>getPossibleValue(subject);
476     }
477     
478     public static <T> T getPossibleValue(Resource subject, Binding binding) throws BindingException, ServiceException
479     {
480         return readGraph().<T>getPossibleValue(subject, binding);
481     }
482
483     public static <T> T getPossibleRelatedValue(Resource subject, Resource relation) throws ManyObjectsForFunctionalRelationException, ServiceException
484     {
485         return readGraph().<T>getPossibleRelatedValue(subject, relation);
486     }
487     
488     public static <T> T getPossibleRelatedValue(Resource subject, Resource relation, Binding binding) throws ManyObjectsForFunctionalRelationException, BindingException, ServiceException
489     {
490         return readGraph().<T>getPossibleRelatedValue(subject, relation, binding);
491     }
492     
493     public static <T> T getPossibleAdapter(Resource resource, Class<T> clazz) throws ValidationException, ServiceException
494     {
495         return readGraph().<T>getPossibleAdapter(resource, clazz);
496     }
497     
498     public static <T> T getPossibleUniqueAdapter(Resource resource, Class<T> clazz) throws ValidationException, ServiceException
499     {
500         return readGraph().<T>getPossibleUniqueAdapter(resource, clazz);
501     }
502     
503     public static boolean isInstanceOf(Resource resource, Resource type) throws ServiceException
504     {
505         return readGraph().isInstanceOf(resource, type);
506     }
507     
508     public static boolean isInheritedFrom(Resource resource, Resource type) throws ServiceException
509     {
510         return readGraph().isInheritedFrom(resource, type);
511     }
512     
513     public static boolean isSubrelationOf(Resource resource, Resource relation) throws ServiceException
514     {
515         return readGraph().isSubrelationOf(resource, relation);
516     }
517     
518     public static boolean hasStatement(Resource subject) throws ServiceException
519     {
520         return readGraph().hasStatement(subject);
521     }
522     
523     public static boolean hasStatement(Resource subject, Resource relation) throws ServiceException
524     {
525         return readGraph().hasStatement(subject, relation);
526     }
527     
528     public static boolean hasStatement(Resource subject, Resource relation, Resource object) throws ServiceException
529     {
530         return readGraph().hasStatement(subject, relation, object);
531     }
532     
533     public static boolean hasValue(Resource subject) throws ServiceException
534     {
535         return readGraph().hasValue(subject);
536     }
537     
538     public static Datatype getDataType(Resource subject) throws DatabaseException
539     {
540         return readGraph().getDataType(subject);
541     }
542     
543     public static <T extends Accessor> T getAccessor(Resource subject) throws DatabaseException
544     {
545         return readGraph().<T>getAccessor(subject);
546     }
547     
548     
549     /**
550      * Makes sure that the statements (s,p,o) and (o,p',s) are found in the
551      * graph, where p' is the inverse predicate of p. Contrary to
552      * {@link WriteOnlyGraph#claim(Resource, Resource, Resource, Resource)} this
553      * method assures that the the statement and its inverse are semantically
554      * valid after the invocation of this method.
555      * 
556      * @param subject subject, i.e. source resource of the statement to be
557      *        claimed
558      * @param predicate predicate resource of the statement to be claimed
559      * @param object object, i.e. target resource of the statement to be claimed
560      * @throws ServiceException
561      */
562     public static void claim(Resource subject, Resource predicate, Resource object) throws ServiceException {
563         writeGraph().claim(subject, predicate, object);
564     }
565
566     /**
567      * Sets literal value related to the specified resource with the specified
568      * predicate. If such value exists (s,p), the value is overridden with the
569      * new specified value.
570      * 
571      * @param resource
572      * @param predicate
573      * @param value Value of the literal (boxed primitive/String or
574      *        primitive/String array)
575      * @throws ManyObjectsForFunctionalRelationException
576      */
577     public static void claimValue(Resource resource, Resource predicate, Object value)
578     throws ManyObjectsForFunctionalRelationException, ServiceException, DatabaseException {
579         writeGraph().claimLiteral(resource, predicate, value);
580     }
581     public static void claimValue(Resource resource, Resource predicate, Object value, Binding binding)
582     throws BindingException, ManyObjectsForFunctionalRelationException, ServiceException, DatabaseException {
583         writeGraph().claimValue(resource, value, binding);
584     }
585     public static void claimValue(Resource resource, Resource predicate, Resource inverse, Resource type, Object value, Binding binding)
586     throws BindingException, ManyObjectsForFunctionalRelationException, ServiceException, DatabaseException {
587         writeGraph().claimLiteral(resource, predicate, inverse, type, value, binding);
588     }
589
590     /**
591      * Makes sure that no statements matching the patterns (s,?p,?o) and
592      * (?o,?p',s), where ?p' is the inverse predicate of ?p, exist in the graph.
593      * In other words, removes all statements outgoing from the specified
594      * resource along with the inverses of those statements.
595      * 
596      * @param subject
597      * @throws ServiceException
598      */
599     public static void deny(Resource subject) throws ServiceException {
600         writeGraph().deny(subject);
601     }
602
603     /**
604      * Makes sure that no statements matching the patterns (s,p,?o) and
605      * (?o,p',s), where p' is the inverse predicate of p, exist in the graph.
606      * Also statements where <code>isSubrelationOf(p, predicate)</code> returns
607      * <code>true</code> shall be removed. In other words, removes all
608      * statements outgoing from the specified resource with the specified
609      * predicate or any of its subrelations, along with the inverses of those
610      * statements.
611      * 
612      * @param subject
613      * @throws ServiceException
614      */
615     public static void deny(Resource subject, Resource predicate) throws ServiceException {
616         writeGraph().deny(subject, predicate);
617     }
618
619     /**
620      * Makes sure that no statements matching the patterns (s,p,o) and (o,p',s),
621      * where p' is the inverse predicate of p, exist in the graph. Contrary to
622      * {@link #denyStatement(Resource, Resource, Resource)}, all statements
623      * where <code>isSubrelationOf(p, predicate)</code> returns
624      * <code>true</code> shall be removed. In other words, removes all
625      * statements between the specified subject and object with the specified
626      * predicate or any of its subrelations, along with the inverses of those
627      * statements.
628      * 
629      * @param subject
630      * @param predicate
631      * @param object
632      * @throws ServiceException
633      */
634     public static void deny(Resource subject, Resource predicate, Resource object) throws ServiceException {
635         writeGraph().deny(subject, predicate, object);
636     }
637
638     /**
639      * Makes sure that no statements matching the patterns (s,p,o) and (o,p',s),
640      * where p' is the inverse predicate of p, exist in the graph. In other
641      * words, removes the specified statement and its possible inverse.
642      * 
643      * <p>
644      * This method behaves exactly like {@link #deny(Statement)}, it just takes
645      * the arguments as resources instead of a statement.
646      * 
647      * @param subject
648      * @throws ServiceException
649      * 
650      * @see {@link #deny(Statement)}
651      */
652     public static void denyStatement(Resource subject, Resource predicate, Resource object) throws ServiceException {
653         writeGraph().denyStatement(subject, predicate, object);
654     }
655
656     /**
657      * Makes sure that the specified statements (s,p,o) and its inverse
658      * statements (o,p',s), where p' is the inverse predicate of p, do not exist
659      * in the graph.
660      * 
661      * <p>
662      * This method behaves exactly like
663      * {@link #denyStatement(Resource, Resource, Resource)}, it just takes the
664      * arguments as a statement instead of 3 resources.
665      * 
666      * @param statement
667      * 
668      * @see #denyStatement(Resource, Resource, Resource)
669      */
670     public static void deny(Statement statement) throws ServiceException {
671         writeGraph().deny(statement);
672     }
673
674     /**
675      * Removes all statements (resource,predicate,?o) and literal contained by
676      * ?o.
677      * 
678      * @param resource
679      * @param predicate
680      * @throws ManyObjectsForFunctionalRelationException
681      */
682     public static void denyValue(Resource resource, Resource predicate) throws ManyObjectsForFunctionalRelationException, ServiceException {
683         writeGraph().denyValue(resource, predicate);
684     }
685     
686 }
687