]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.impl/src/org/simantics/db/impl/graph/WriteGraphImpl.java
Multiple reader thread support for db client
[simantics/platform.git] / bundles / org.simantics.db.impl / src / org / simantics / db / impl / graph / WriteGraphImpl.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2018 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.db.impl.graph;
13
14 import java.io.IOException;
15 import java.io.PrintWriter;
16 import java.io.StringWriter;
17 import java.util.TreeMap;
18 import java.util.function.Consumer;
19
20 import org.simantics.databoard.Bindings;
21 import org.simantics.databoard.accessor.Accessor;
22 import org.simantics.databoard.binding.Binding;
23 import org.simantics.databoard.binding.error.BindingConstructionException;
24 import org.simantics.databoard.binding.mutable.Variant;
25 import org.simantics.databoard.primitives.MutableBoolean;
26 import org.simantics.databoard.primitives.MutableByte;
27 import org.simantics.databoard.primitives.MutableDouble;
28 import org.simantics.databoard.primitives.MutableFloat;
29 import org.simantics.databoard.primitives.MutableInteger;
30 import org.simantics.databoard.primitives.MutableLong;
31 import org.simantics.databoard.primitives.MutableString;
32 import org.simantics.databoard.serialization.SerializationException;
33 import org.simantics.databoard.serialization.Serializer;
34 import org.simantics.databoard.type.Datatype;
35 import org.simantics.databoard.util.binary.RandomAccessBinary;
36 import org.simantics.db.DevelopmentKeys;
37 import org.simantics.db.ExternalValueSupport;
38 import org.simantics.db.Metadata;
39 import org.simantics.db.Resource;
40 import org.simantics.db.Statement;
41 import org.simantics.db.VirtualGraph;
42 import org.simantics.db.WriteGraph;
43 import org.simantics.db.WriteOnlyGraph;
44 import org.simantics.db.common.request.WriteOnlyRequest;
45 import org.simantics.db.common.utils.Literals;
46 import org.simantics.db.common.utils.Logger;
47 import org.simantics.db.exception.ArgumentException;
48 import org.simantics.db.exception.DatabaseException;
49 import org.simantics.db.exception.InternalException;
50 import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
51 import org.simantics.db.exception.ServiceException;
52 import org.simantics.db.impl.DatabaseUtils;
53 import org.simantics.db.impl.MemWatch;
54 import org.simantics.db.impl.ResourceImpl;
55 import org.simantics.db.impl.internal.RandomAccessValueSupport;
56 import org.simantics.db.impl.internal.ResourceData;
57 import org.simantics.db.impl.query.CacheEntry;
58 import org.simantics.db.impl.query.QueryProcessor;
59 import org.simantics.db.impl.support.WriteRequestScheduleSupport;
60 import org.simantics.db.procedure.Procedure;
61 import org.simantics.db.request.DelayedWrite;
62 import org.simantics.db.request.DelayedWriteResult;
63 import org.simantics.db.request.Write;
64 import org.simantics.db.request.WriteOnly;
65 import org.simantics.db.request.WriteOnlyResult;
66 import org.simantics.db.request.WriteResult;
67 import org.simantics.db.request.WriteTraits;
68 import org.simantics.layer0.Layer0;
69 import org.simantics.utils.Development;
70 import org.simantics.utils.datastructures.Pair;
71
72 import gnu.trove.map.hash.THashMap;
73
74
75 final public class WriteGraphImpl extends ReadGraphImpl implements WriteGraph {
76
77     final public static Binding DATA_TYPE_BINDING = Bindings.getBindingUnchecked(Datatype.class);
78     
79     final public WriteSupport writeSupport;
80     final public VirtualGraph provider;
81     
82     private String resourceName(Resource resource) throws DatabaseException {
83         if(Development.DEVELOPMENT) {
84                 Boolean names = Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_NAMES, Bindings.BOOLEAN); 
85                 if(names && !writeSupport.writeOnly()) return DatabaseUtils.getReadableName(this, resource);
86                 else return resource.toString();
87         }
88         throw new IllegalStateException();
89     }
90     
91     private Layer0 getBuiltins() {
92         return getService(Layer0.class);
93     }
94
95     private WriteRequestScheduleSupport getWriteRequestScheduler() {
96         return (WriteRequestScheduleSupport) getSession();
97     }
98
99     private WriteGraphImpl(CacheEntry parent2, QueryProcessor readSupport,
100             WriteSupport writeSupport, VirtualGraph provider) {
101         super(parent2, readSupport);
102         this.writeSupport = writeSupport;
103         this.provider = provider;
104     }
105
106     public final static WriteGraphImpl create(QueryProcessor support, WriteSupport writeSupport, VirtualGraph provider) {
107
108         WriteGraphImpl impl = new WriteGraphImpl(null, support, writeSupport, provider); 
109         
110         try {
111                         writeSupport.setDefaultClusterSet(null);
112                 } catch (ServiceException e) {
113                         Logger.defaultLogError(e);
114                 }
115         
116         return impl;
117         
118     }
119
120     final public WriteGraphImpl newAsync() {
121         return this;
122     }
123     
124     public WriteGraphImpl newSync(final VirtualGraph provider) {
125         return new WriteGraphImpl(parent, processor, writeSupport, provider);
126     }
127     final public WriteGraphImpl newSync(final CacheEntry parent) {
128         return new WriteGraphImpl(parent, processor, writeSupport, provider);
129     }
130
131     @Override
132     public ReadGraphImpl newRestart(ReadGraphImpl impl) {
133
134         WriteGraphImpl write = processor.getSession().getService(WriteGraphImpl.class);
135
136         return write;
137         
138     }
139
140     @Override
141     final public Resource newResource() throws ServiceException {
142         
143         try {
144                 
145                 Resource result = writeSupport.createResource(provider); 
146
147                 if(Development.DEVELOPMENT) {
148                         if(Development.<Boolean>getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN))
149                         WriteLogger.logNewResource(this, result);
150                 }
151                 
152             return result;
153             
154         } catch (DatabaseException e) {
155             throw new ServiceException(e);
156         }
157         
158     }
159
160     @Override
161     final public Resource newResource(long clusterId) throws ServiceException {
162         
163         try {
164             
165                 Resource result = writeSupport.createResource(provider, clusterId); 
166
167                 if(Development.DEVELOPMENT) {
168                         if(Development.<Boolean>getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN))
169                         WriteLogger.logNewResource(this, result);
170                 }
171                 
172             return result;
173             
174         } catch (DatabaseException e) {
175             throw new ServiceException(e);
176         }
177         
178     }
179
180     @Override
181     public Resource newResource(Resource clusterSet) throws ServiceException {
182         try {
183                 Resource result;
184                 if (provider != null)
185                         result = writeSupport.createResource(provider);
186                 else
187                         result = writeSupport.createResource(provider, clusterSet); 
188             if(Development.DEVELOPMENT) {
189                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN))
190                     WriteLogger.logNewResource(this, result);
191             }
192             return result;
193         } catch (ServiceException e) {
194             throw e;
195         } catch (DatabaseException e) {
196             throw new ServiceException(e);
197         }
198     }
199
200     @Override
201     public void newClusterSet(Resource clusterSet) throws ServiceException {
202         try {
203                 if (provider == null)
204                         writeSupport.createClusterSet(provider, clusterSet); 
205             if(Development.DEVELOPMENT) {
206                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN))
207                     WriteLogger.logNewResource(this, clusterSet);
208             }
209         } catch (ServiceException e) {
210             throw e;
211         } catch (DatabaseException e) {
212             throw new ServiceException(e);
213         }
214     }
215
216     @Override
217     public Resource setClusterSet4NewResource(Resource clusterSet)
218     throws ServiceException {
219         return writeSupport.setDefaultClusterSet(clusterSet);
220     }
221     /**
222      * Compares two object for equality, allowing null objects also.
223      * 
224      * @param o1 obj1
225      * @param o2 obj2
226      * @return true if both arguments are <code>null</code> or equal
227      */
228     static boolean safeEquals(Object o1, Object o2) {
229         if (o1 == o2)
230             return true;
231         if (o1 == null && o2 == null)
232             return true;
233         if (o1 == null || o2 == null)
234             return false;
235         return o1.equals(o2);
236     }
237
238     @Override
239     public void asyncRequest(DelayedWrite request) {
240         assert (request != null);
241         getWriteRequestScheduler().scheduleRequest(request, e -> {
242             if(e != null)
243                 Logger.defaultLogError(e);
244         }, null, Boolean.TRUE);
245     }
246
247     @Override
248     public void asyncRequest(DelayedWrite request, Consumer<DatabaseException> callback) {
249         assert (request != null);
250         getWriteRequestScheduler().scheduleRequest(request, callback, null, Boolean.TRUE);
251     }
252
253     @Override
254     public <T> void asyncRequest(DelayedWriteResult<T> request, Procedure<T> procedure) {
255         assert (request != null);
256         getWriteRequestScheduler().scheduleRequest(request, procedure, null, Boolean.TRUE);
257     }
258
259     @Override
260     public void asyncRequest(final Write r) {
261         assert (r != null);
262         getWriteRequestScheduler().scheduleRequest(r, e -> {
263             if(e != null)
264                 Logger.defaultLogError(e);
265         }, null, Boolean.TRUE);
266     }
267
268     @Override
269     public void asyncRequest(Write request, Consumer<DatabaseException> callback) {
270         assert (request != null);
271         getWriteRequestScheduler().scheduleRequest(request, callback, null, Boolean.TRUE);
272     }
273
274     @Override
275     public void asyncRequest(WriteOnly request) {
276         assert (request != null);
277         getWriteRequestScheduler().scheduleRequest(request, e -> {
278             if(e != null)
279                 Logger.defaultLogError(e);
280         }, null, Boolean.TRUE);
281     }
282     
283     @Override
284     public void asyncRequest(WriteOnly request, Consumer<DatabaseException> callback) {
285         assert (request != null);
286         getWriteRequestScheduler().scheduleRequest(request, callback, null, Boolean.TRUE);
287     }
288
289     @Override
290     public <T> void asyncRequest(WriteOnlyResult<T> request, Procedure<T> procedure) {
291         assert (request != null);
292         getWriteRequestScheduler().scheduleRequest(request, procedure, null, Boolean.TRUE);
293     }
294
295     @Override
296     public <T> void asyncRequest(WriteResult<T> request, Procedure<T> procedure) {
297         assert (request != null);
298         getWriteRequestScheduler().scheduleRequest(request, procedure, null, Boolean.TRUE);
299     }
300
301     @Override
302     public void syncRequest(Write request) throws DatabaseException {
303         
304         Resource defaultClusterSet = setClusterSet4NewResource(null);
305         
306         WriteGraphImpl graph = (WriteGraphImpl)newSync(request.getProvider()); 
307         try {
308             writeSupport.performWriteRequest(graph, request);
309         } catch (DatabaseException e) {
310             throw e;
311         } catch (Throwable t) {
312             throw new DatabaseException(t);
313         } finally {
314             setClusterSet4NewResource(defaultClusterSet);
315         }
316         
317         
318     }
319     
320     @Override
321     public <T> T syncRequest(WriteResult<T> request) throws DatabaseException {
322
323         Resource defaultClusterSet = setClusterSet4NewResource(null);
324
325         WriteGraphImpl graph = (WriteGraphImpl)newSync(request.getProvider()); 
326         try {
327             return writeSupport.performWriteRequest(graph, request);
328         } catch (DatabaseException e) {
329             throw e;
330         } catch (Throwable t) {
331             throw new DatabaseException(t);
332         } finally {
333             setClusterSet4NewResource(defaultClusterSet);
334         }
335     }
336
337     @Override
338     public void syncRequest(final DelayedWrite request) throws DatabaseException {
339         
340         try {
341
342                 final DelayedWriteGraph dwg = new DelayedWriteGraph(this);
343                 request.perform(dwg);
344
345                 syncRequest(new WriteOnlyRequest() {
346
347                         @Override
348                         public void perform(WriteOnlyGraph graph) throws DatabaseException {
349                                 dwg.commit(graph, request);
350                         }
351
352                 });
353
354         } catch (DatabaseException e) {
355                 
356                 throw e;
357                 
358         } catch (Throwable e) {
359                 
360                 throw new DatabaseException(e);
361                 
362         } finally {
363                 
364         }
365         
366     }
367
368     @Override
369     public void syncRequest(WriteOnly request) throws DatabaseException {
370         
371         Resource defaultClusterSet = setClusterSet4NewResource(null);
372         
373         try {
374             writeSupport.performWriteRequest(this, request);
375         } catch (DatabaseException e) {
376             throw e;
377         } catch (Throwable t) {
378             throw new DatabaseException(t);
379         }  finally {
380             setClusterSet4NewResource(defaultClusterSet);
381         }
382         
383     }
384     
385     @Override
386     public void claim(Resource subject, Resource predicate, Resource object) throws ServiceException {
387
388         if(subject == null || predicate == null || object == null) {
389                 throw new ServiceException("Claim does not accept null arguments (subject=" + subject + ",predicate=" + predicate + ",object=" + object + ").");
390         }
391
392         if(processor.isImmutable(object)) {
393                 claim(subject, predicate, null, object);                
394         } else {
395                 claim(subject, predicate, getPossibleInverse(predicate), object);               
396         }
397
398     }
399
400     @Override
401     public void claim(Resource subject, Resource predicate, Resource inverse, Resource object) throws ServiceException {
402         
403         if (MemWatch.isLowOnMemory())
404             writeSupport.gc();
405
406         if(subject == null) throw new ServiceException("Claim does not accept null arguments (subject).");
407         if(predicate == null) throw new ServiceException("Claim does not accept null arguments (predicate).");
408         if(object == null) throw new ServiceException("Claim does not accept null arguments (object).");
409         if(provider == null && subject.isPersistent() && !object.isPersistent())
410             throw new ServiceException("Cannot claim persistent statements where subject is persistent and object is virtual. Persistent database cannot contain statements that refer to virtual graphs.");
411
412                 try {
413                         if(Development.DEVELOPMENT) {
414                                 try {
415                                         if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) {
416                                                 String text = "claim(" + resourceName(subject) + ":" + subject + ", " + resourceName(predicate) + ":" + predicate + ", " + resourceName(object) + ":" + object + ")";
417                                                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) {
418                                                         StringWriter writer = new StringWriter();
419                                                         PrintWriter out = new PrintWriter(writer);
420                                                         new Exception(text).printStackTrace(out);
421                                                         Development.dispatchEvent(new ClaimEventImpl(this, provider, subject, predicate, object, writer.toString()));
422                                                 } else {
423                                                         Development.dispatchEvent(new ClaimEventImpl(this, provider, subject, predicate, object, text));
424                                                 }
425                                         }
426                                         if(Development.<Boolean>getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN))
427                                                 WriteLogger.logClaim(this, subject, predicate, inverse, object);
428                                 } catch (DatabaseException e) {
429                                         Logger.defaultLogError(e);
430                                 }
431                         }
432                         writeSupport.claim(provider, subject, predicate, object);
433                 } catch(RuntimeException e) {
434                         throw new ServiceException(e);
435                 }
436     
437         if(inverse == null) return;
438         if(subject.equals(object) && predicate.equals(inverse)) {
439             return;
440         }
441
442         // Do as claim(s,p,o) already works -
443         // don't write inverse relations if object is immutable.
444         if(processor.isImmutable(object))
445             return;
446
447         try {
448             writeSupport.claim(provider, object, inverse, subject);
449         } catch(RuntimeException e) {
450             throw new ServiceException(e);
451         }
452         
453     }
454
455     @Override
456     public void deny(Resource subject) throws ServiceException {
457         
458         assert(subject != null);
459
460         try {
461         
462             for(Statement stm : getStatements(subject, getBuiltins().IsWeaklyRelatedTo)) {
463                 if(subject.equals(stm.getSubject())) {
464                     Resource inverse = getPossibleInverse(stm.getPredicate());
465 //                    if(inverse == null) {
466 //                        try {
467 //                            String name = adapt(stm.getPredicate(), String.class);
468 //                            throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + name + "' does not have an inverse.");
469 //                        } catch (AdaptionException e) {
470 //                            throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + stm.getPredicate() + "' does not have an inverse.");
471 //                        }
472 //                    }
473                     deny(stm.getSubject(), stm.getPredicate(), inverse, stm.getObject());
474                 }
475             }
476             
477         } catch(DatabaseException e) {
478             
479             throw new ServiceException(INTERNAL_ERROR_STRING, e);
480             
481         }
482         
483     }
484
485     @Override
486     public void deny(Resource subject, Resource predicate) throws ServiceException {
487         
488         assert(subject != null);
489         assert(predicate != null);
490
491         try {
492
493             for (Statement stm : getStatements(subject, predicate)) {
494                 if (subject.equals(stm.getSubject())) {
495                     Resource inverse = getPossibleInverse(stm.getPredicate());
496 //                    if(inverse == null) {
497 //                        try {
498 //                            String name = adapt(stm.getPredicate(), String.class);
499 //                            throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + name + "' does not have an inverse.");
500 //                        } catch (AdaptionException e) {
501 //                            throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + stm.getPredicate() + "' does not have an inverse.");
502 //                        }
503 //                    }
504                     deny(stm.getSubject(), stm.getPredicate(), inverse, stm.getObject());
505             }
506         }
507
508         } catch(DatabaseException e) {
509             
510             throw new ServiceException(INTERNAL_ERROR_STRING, e);
511             
512         }
513
514     }
515
516     @Override
517     public void deny(Resource subject, Resource predicate, Resource object) throws ServiceException {
518
519         assert(subject != null);
520         assert(predicate != null);
521         assert(object != null);
522
523         try { 
524             for (Statement stm : getStatements(subject, predicate)) {
525                 if (subject.equals(stm.getSubject()) && object.equals(stm.getObject())) {
526                     Resource inverse = getPossibleInverse(stm.getPredicate());
527                     deny(stm.getSubject(), stm.getPredicate(), inverse, stm.getObject());
528                 }
529             }
530         } catch (DatabaseException e) {
531             throw new ServiceException(INTERNAL_ERROR_STRING, e);
532         }
533
534     }
535
536     @Override
537     public void denyStatement(Resource subject, Resource predicate, Resource object) throws ServiceException {
538         
539         assert(subject != null);
540         assert(predicate != null);
541         assert(object != null);
542         
543         Resource inverse = getPossibleInverse(predicate);
544 //        if(inverse == null) {
545 //            try {
546 //                String name = adapt(predicate, String.class);
547 //                throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + name + "' does not have an inverse.");
548 //            } catch (AdaptionException e) {
549 //                throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + predicate + "' does not have an inverse.");
550 //            }
551 //        }
552         deny(subject, predicate, inverse, object);
553         
554     }
555
556     @Override
557     public void deny(Statement statement) throws ServiceException {
558         assert(statement != null);
559         denyStatement(statement.getSubject(), statement.getPredicate(), statement.getObject());
560     }
561
562     @Override
563     public void deny(Resource subject, Resource predicate, Resource inverse, Resource object) throws ServiceException {
564
565         assert(subject != null);
566         assert(predicate != null);
567         assert(object != null);
568         
569         VirtualGraph provider = processor.getProvider(subject, predicate, object);
570         
571         deny(subject, predicate, inverse, object, provider);
572         
573     }
574
575     /*
576      * Note: We assume here that all inverse pairs of statements are stored in the same virtual graph.
577      */
578     @Override
579     public void deny(Resource subject, Resource predicate, Resource inverse, Resource object, VirtualGraph provider) throws ServiceException {
580         
581         assert(subject != null);
582         assert(predicate != null);
583         assert(object != null);
584
585                 if(Development.DEVELOPMENT) {
586                         try {
587                                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) {
588                                         String text = "deny(" + resourceName(subject) + ":" + subject + ", " + resourceName(predicate) + ":" + predicate + ", " + resourceName(object) + ":" + object + ")";
589                                         if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) {
590                                                 StringWriter writer = new StringWriter();
591                                                 PrintWriter out = new PrintWriter(writer);
592                                                 new Exception(text).printStackTrace(out);
593                                                 Development.dispatchEvent(new DenyEventImpl(this, provider, subject, predicate, object, writer.toString()));
594                                         } else {
595                                                 Development.dispatchEvent(new DenyEventImpl(this, provider, subject, predicate, object, text));
596                                         }
597                                 }
598                         } catch (DatabaseException e) {
599                                 Logger.defaultLogError(e);
600                         }
601                 }
602         
603         try {
604             writeSupport.removeStatement(provider, subject, predicate, object);
605         } catch(Throwable e) {
606             throw new InternalException("bug in deny(s,p,i,o)", e);
607         }
608     
609         if(inverse == null || (subject.equals(object) && predicate.equals(inverse))) return;
610
611         // don't deny inverse relations if object is immutable.
612         if(processor.isImmutable(object))
613             return;
614         
615         try {
616             writeSupport.removeStatement(provider, object, inverse, subject);
617         } catch(Throwable e) {
618             throw new InternalException("bug in deny(s,p,i,o)", e);
619         }
620         
621     }
622     
623     @Override
624     public void claimValue(Resource resource, Object value) throws ServiceException {
625         
626         try {
627             Binding b = Bindings.getBinding(value.getClass());
628             claimValue(resource, value, b);
629         } catch(BindingConstructionException e) {
630             throw new IllegalArgumentException(e);
631         }
632         
633     }
634
635     @Override
636     public void claimValue(Resource resource, Object value, Binding binding) throws ServiceException {
637         
638         if(value == null) throw new ServiceException("claimValue does not accept null value");
639         if(binding == null) throw new ServiceException("claimValue does not accept null binding");
640         
641                 if(Development.DEVELOPMENT) {
642                 try {
643                                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) {
644                                         String valueText = Literals.shortString(value.toString());
645                                         String text = "claimValue(" + resourceName(resource) + ", " + valueText + ")";
646                                         if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) {
647                                                 StringWriter writer = new StringWriter();
648                                                 PrintWriter out = new PrintWriter(writer);
649                                                 new Exception(text).printStackTrace(out);
650                                                 Development.dispatchEvent(new ClaimValueEventImpl(this, provider, resource, value, binding, writer.toString()));
651                                         } else {
652                                                 Development.dispatchEvent(new ClaimValueEventImpl(this, provider, resource, value, binding, text));
653                                         }
654                                 }
655                 } catch (DatabaseException e) {
656                                 Logger.defaultLogError(e);
657                 }
658                 }
659         
660         try {
661                 Serializer serializer = getSerializer(binding);
662             //Serializer serializer = binding.serializer();
663             byte[] data = serializer.serialize(value);
664
665                 if(Development.DEVELOPMENT) {
666                 try {
667                     if(Development.<Boolean>getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN))
668                         WriteLogger.logValue(this, resource, data);
669                 } catch (DatabaseException e) {
670                                 Logger.defaultLogError(e);
671                 }
672                 }
673             
674             writeSupport.claimValue(provider, resource, data);
675             
676         } catch (DatabaseException e) {
677             throw new ServiceException(e);
678         } catch (SerializationException e) {
679             throw new ServiceException(e);
680         } catch (IOException e) {
681             throw new ServiceException(e);
682         } 
683         
684     }
685
686     THashMap<Class<?>, Resource> builtinValues = new THashMap<Class<?>, Resource>(32);
687
688     private void initBuiltinValues(Layer0 b) {
689
690         if(!builtinValues.isEmpty()) return;
691
692         builtinValues.put(String.class, b.String);
693         builtinValues.put(Double.class, b.Double);
694         builtinValues.put(Float.class, b.Float);
695         builtinValues.put(Long.class, b.Long);
696         builtinValues.put(Integer.class, b.Integer);
697         builtinValues.put(Byte.class, b.Byte);
698         builtinValues.put(Boolean.class, b.Boolean);
699         builtinValues.put(Variant.class, b.Variant);
700
701         builtinValues.put(String[].class, b.StringArray);
702         builtinValues.put(double[].class, b.DoubleArray);
703         builtinValues.put(float[].class, b.FloatArray);
704         builtinValues.put(long[].class, b.LongArray);
705         builtinValues.put(int[].class, b.IntegerArray);
706         builtinValues.put(byte[].class, b.ByteArray);
707         builtinValues.put(boolean[].class, b.BooleanArray);
708
709         builtinValues.put(MutableString.class, b.String);
710         builtinValues.put(MutableDouble.class, b.Double);
711         builtinValues.put(MutableFloat.class, b.Float);
712         builtinValues.put(MutableLong.class, b.Long);
713         builtinValues.put(MutableInteger.class, b.Integer);
714         builtinValues.put(MutableByte.class, b.Byte);
715         builtinValues.put(MutableBoolean.class, b.Boolean);
716
717     }
718
719     @Override
720     public void addLiteral(Resource resource, Resource predicate, Resource inverse, Resource type, Object value, Binding binding) throws ManyObjectsForFunctionalRelationException, ServiceException {
721
722         Layer0 b = getBuiltins();
723         Resource valueResource = newResource();
724         claim(valueResource, b.InstanceOf, null, type);
725         claim(resource, predicate, inverse, valueResource);
726         claimValue(valueResource, value, binding);
727         
728     }
729
730     @Override
731     public void addLiteral(Resource resource, Resource predicate, Resource inverse, Object value, Binding binding) throws ManyObjectsForFunctionalRelationException, ServiceException {
732
733         Layer0 b = getBuiltins();
734         initBuiltinValues(b);
735         Resource literal = newResource();
736         
737         Class<?> clazz = value.getClass();
738         Resource type = builtinValues.get(clazz);
739         if (type == null) {
740             type = b.Literal;
741             Resource dataType = newResource();
742             claim(dataType, b.InstanceOf, null, b.DataType);
743             claimValue(dataType, binding.type(), DATA_TYPE_BINDING);
744             claim(literal, b.HasDataType, b.HasDataType_Inverse, dataType);
745         }
746         
747         claim(literal, b.InstanceOf, null, type);
748         claim(resource, predicate, inverse, literal);
749         claimValue(literal, value, binding);
750         
751     }
752
753     @Override
754     public <T extends Accessor> T newLiteral(Resource resource, Resource predicate, Datatype datatype, Object initialValue)
755     throws DatabaseException {
756         Layer0 b = getBuiltins();
757         initBuiltinValues(b);       
758         Resource literal = newResource();
759         claim(literal, b.InstanceOf, null, b.Literal);
760         Resource dataType = newResource();
761         claim(dataType, b.InstanceOf, null, b.DataType);
762         claimValue(dataType, datatype, DATA_TYPE_BINDING);
763         claim(literal, b.HasDataType, dataType);
764         claim(resource, predicate, literal);
765         return createAccessor(literal, datatype, initialValue);
766     }
767     @Override
768     public RandomAccessBinary createRandomAccessBinary
769     (Resource resource, Resource predicate, Datatype datatype, Object initiaValue)
770     throws DatabaseException {
771         Layer0 b = getBuiltins();
772         initBuiltinValues(b);       
773         Resource literal = newResource();
774         claim(literal, b.InstanceOf, null, b.Literal);
775         Resource dataType = newResource();
776         claim(dataType, b.InstanceOf, null, b.DataType);
777         claimValue(dataType, datatype, DATA_TYPE_BINDING);
778         claim(literal, b.HasDataType, dataType);
779         claim(resource, predicate, literal);
780         return createRandomAccessBinary(literal, datatype, initiaValue);
781     }
782     @Override
783     public void claimLiteral(Resource resource, Resource predicate, Object value) throws ManyObjectsForFunctionalRelationException, ServiceException {
784
785         try {
786             Binding binding = Bindings.getBinding(value.getClass());
787             claimLiteral(resource, predicate, value, binding);
788         } catch(BindingConstructionException e) {
789             throw new IllegalArgumentException(e);
790         } 
791
792     }
793
794     @Override
795     public void claimLiteral(Resource resource, Resource predicate, Object value, Binding binding) throws ManyObjectsForFunctionalRelationException, ServiceException {
796         
797         Layer0 b = getBuiltins();
798         initBuiltinValues(b);       
799
800         Statement literalStatement = getPossibleStatement(resource, predicate);
801
802         if(literalStatement != null && resource.equals(literalStatement.getSubject())) {
803
804             claimValue(literalStatement.getObject(), value, binding);
805
806         } else {
807
808             Class<?> clazz = value.getClass();
809             Resource type = builtinValues.get(clazz);
810             Resource literal = newResource();
811             if (type == null) {
812                 type = b.Literal;
813                 Resource dataType = newResource();
814                 claim(dataType, b.InstanceOf, null, b.DataType);
815                 claimValue(dataType, binding.type(), DATA_TYPE_BINDING);
816                 claim(literal, b.HasDataType, null, dataType);
817             }
818             claim(literal, b.InstanceOf, null, type);
819             claimValue(literal, value, binding); 
820             claim(resource, predicate, literal);
821
822         }
823
824         if(Development.DEVELOPMENT) {
825                 try {
826                         getPossibleStatement(resource, predicate);
827                 } catch (ManyObjectsForFunctionalRelationException e) {
828                         System.err.println("err " + ((ResourceImpl)resource).id + " " + ((ResourceImpl)predicate).id);
829                         getPossibleStatement(resource, predicate);
830                 }
831         }
832         
833     }
834     
835     @Override
836     public void claimLiteral(Resource resource, Resource predicate, Resource type, Object value)
837             throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException {
838         
839         try {
840             Binding binding = Bindings.getBinding(value.getClass());
841             claimLiteral(resource, predicate, type, value, binding);
842         } catch(BindingConstructionException e) {
843             throw new IllegalArgumentException(e);
844         } 
845         
846     }
847     
848     @Override
849     public void claimLiteral(Resource resource, Resource predicate, Resource type, Object value, Binding binding)
850             throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException {
851         
852         Layer0 b = getBuiltins();
853         
854         Statement literalStatement = getPossibleStatement(resource, predicate);
855
856         if(literalStatement != null && resource.equals(literalStatement.getSubject())) {
857
858             claimValue(literalStatement.getObject(), value, binding);
859
860         } else {
861
862             Resource literal = newResource();
863             claim(literal, b.InstanceOf, null, type);
864             claimValue(literal, value, binding); 
865             claim(resource, predicate, literal);
866
867         }
868         
869     }
870     
871     @Override
872     public void claimLiteral(Resource resource, Resource predicate, Resource inverse, Resource type, Object value)
873             throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException {
874
875         try {
876             Binding binding = Bindings.getBinding(value.getClass());
877             claimLiteral(resource, predicate, inverse, type, value, binding);
878         } catch(BindingConstructionException e) {
879             throw new IllegalArgumentException(e);
880         } 
881         
882     }
883     
884     @Override
885     public void claimLiteral(Resource resource, Resource predicate, Resource inverse, Resource type, Object value, Binding binding)
886             throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException {
887         
888         Layer0 b = getBuiltins();
889         
890         Statement literalStatement = getPossibleStatement(resource, predicate);
891
892         if(literalStatement != null && resource.equals(literalStatement.getSubject())) {
893
894             claimValue(literalStatement.getObject(), value, binding);
895
896         } else {
897
898             Resource literal = newResource();
899             claim(literal, b.InstanceOf, null, type);
900             claimValue(literal, value, binding); 
901             claim(resource, predicate, inverse, literal);
902
903         }
904         
905     }
906
907
908     @Override
909     public void denyValue(Resource resource) throws ServiceException {
910
911         denyValue0(resource, null);
912
913     }
914
915     @Override
916     public void denyValue(Resource resource, VirtualGraph valueProvider) throws ServiceException {
917
918         denyValue0(resource, valueProvider);
919
920     }
921     
922     /**
923      * @param resource resource to remove value from
924      * @param valueProvider <code>null</code> means the caller doesn't know and
925      *        this method must find out
926      * @throws ServiceException
927      */
928     private void denyValue0(Resource resource, VirtualGraph valueProvider) throws ServiceException {
929
930         if(Development.DEVELOPMENT) {
931                 try {
932                                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) {
933                                         Object oldValue = getPossibleValue(resource);
934                                         String valueText = oldValue == null ? "<no value>" : oldValue.toString();
935                                         String text = "denyValue(" + resourceName(resource) + ", " + valueText + ")";
936                                         if (oldValue != null) {
937                                                 if(Development.<Boolean>getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) {
938                                                         StringWriter writer = new StringWriter();
939                                                         PrintWriter out = new PrintWriter(writer);
940                                                         new Exception(text).printStackTrace(out);
941                                                         Development.dispatchEvent(new DenyValueEventImpl(this, provider, resource, oldValue, writer.toString()));
942                                                 } else {
943                                                         Development.dispatchEvent(new DenyValueEventImpl(this, provider, resource, oldValue, text));
944                                                 }
945                                         }
946                                 }
947                 } catch (DatabaseException e) {
948                                 Logger.defaultLogError(e);
949                 }
950                 }
951         
952         if (provider != null) {
953
954             // Can only remove from the specified VirtualGraph
955             if (resource.isPersistent())
956                 throw new ArgumentException("Tried to remove literal value from persistent resource " + resource
957                         + " using virtual graph '" + provider.getIdentifier() + "'");
958             writeSupport.denyValue(provider, resource);
959
960         } else {
961
962             // Can remove from any provider
963             if (resource.isPersistent()) {
964                 // A persistent resource cannot have a virtual literal
965                 // so this should be safe.
966                 writeSupport.denyValue(provider, resource);
967             } else {
968                 // A virtual resource cannot have a persistent literal.
969                 // Must look for a virtual graph for resource.
970                 Layer0 L0 = getBuiltins();
971                 if (valueProvider == null) {
972                     if (hasStatement(resource, L0.InstanceOf)) {
973                         valueProvider = processor.getProvider(resource, L0.InstanceOf);
974                     } else if (hasStatement(resource, L0.Inherits)) {
975                         valueProvider = processor.getProvider(resource, L0.Inherits);
976                     } else if (hasStatement(resource, L0.SubrelationOf)) {
977                         valueProvider = processor.getProvider(resource, L0.SubrelationOf);
978                     }
979                 }
980                 if (valueProvider != null)
981                     writeSupport.denyValue(valueProvider, resource);
982             }
983
984         }
985
986     }
987
988     @Override
989     public void denyValue(Resource resource, Resource predicate) throws ManyObjectsForFunctionalRelationException, ServiceException {
990
991         Statement valueStatement = getPossibleStatement(resource, predicate);
992
993         if (valueStatement != null && !valueStatement.isAsserted(resource)) {
994
995             Resource value = valueStatement.getObject();
996             Resource inverse = getPossibleInverse(predicate);
997
998             if (provider != null) {
999
1000                 // Can only remove from the specified provider
1001                 deny(resource, predicate, inverse, value, provider);
1002                 writeSupport.denyValue(provider, value);
1003
1004             } else {
1005
1006                 // Can remove from any provider
1007                 VirtualGraph statementProvider = processor.getProvider(resource, valueStatement.getPredicate(), value);
1008                 deny(resource, predicate, inverse, value, statementProvider);
1009                 denyValue0(resource, statementProvider);
1010
1011             }
1012
1013         }
1014
1015     }
1016
1017     @Override
1018     public void flushCluster() {
1019         
1020         writeSupport.flushCluster();
1021         
1022     }
1023
1024     @Override
1025     public void flushCluster(Resource r) {
1026         writeSupport.flushCluster(r);
1027     }
1028     
1029     Object serialize(Object value) {
1030         
1031         Class<?> clazz = value.getClass();
1032         
1033         if(clazz.isArray()) return value;
1034         
1035         if(Double.class == clazz) return new double[] { (Double)value };
1036         else if(String.class == clazz) return new String[] { (String)value };
1037         else if(Integer.class == clazz) return new int[] { (Integer)value };
1038         else if(Boolean.class == clazz) return new boolean[] { (Boolean)value };
1039         else if(Long.class == clazz) return new long[] { (Long)value };
1040         else if(Byte.class == clazz) return new byte[] { (Byte)value };
1041         else if(Float.class == clazz) return new float[] { (Float)value };
1042         else return value;
1043         
1044     }
1045     
1046     @Override
1047     public <T> void addMetadata(Metadata data) throws ServiceException {
1048         writeSupport.addMetadata(data);
1049     }
1050
1051     @Override
1052     public <T extends Metadata> T getMetadata(Class<T> clazz) throws ServiceException {
1053         return writeSupport.getMetadata(clazz);
1054     }
1055
1056     @Override
1057     public TreeMap<String, byte[]> getMetadata() {
1058         return writeSupport.getMetadata();
1059     }
1060
1061     @Override
1062     public String toString() {
1063         return "WriteGraphImpl[thread=" + Thread.currentThread() + "]";
1064     }     
1065
1066     public void commitAccessorChanges(WriteTraits writeTraits) {
1067         try {
1068             RandomAccessValueSupport ravs = getSession().peekService(RandomAccessValueSupport.class);
1069             if (ravs == null)
1070                 return;
1071
1072             for(Pair<Resource, ResourceData> entry : ravs.entries()) {
1073                 ResourceData rd = entry.second;
1074                 if(!rd.isChanged()) continue;
1075                 Resource resource = entry.first;
1076                 try {
1077                     ExternalValueSupport evs = getService(ExternalValueSupport.class);
1078                     long vsize = rd.oldExternalValue ? evs.getValueSize(this, resource) : -1;
1079                     long bsize = rd.binaryFile.length();
1080                     final int N = 1<<20;
1081                     final int LIMIT = 10 * 1000 * 1000;
1082                     long left = bsize;
1083                     long offset = 0;
1084                     byte[] bytes = new byte[N];
1085                     rd.binaryFile.reset();
1086                     rd.binaryFile.position(0);
1087                     int count = 0;
1088 //                    if (LIMIT < left)
1089 //                        evs.moveValue(this, resource);
1090                     while (left > 0) {
1091                         int length = N < left ? N : (int)left;
1092                         rd.binaryFile.readFully(bytes, 0, length);
1093                         evs.writeValue(this, resource, offset, length, bytes);
1094                         offset += length;
1095                         left -= length;
1096                         count += length;
1097                         if (count > LIMIT) {
1098                             count = 0;
1099 //                            evs.commitAndContinue(this, writeTraits, resource);
1100 //                            evs.moveValue(this, resource);
1101                             // This is needed so we don't create too many requests and run out of heap.
1102                             evs.wait4RequestsLess(1);
1103                         }
1104                     }
1105                     if (bsize < vsize) // truncate
1106                         evs.writeValue(this, resource, bsize, 0, new byte[0]);
1107                 } catch (DatabaseException e) {
1108                     Logger.defaultLogError(e);
1109                 }
1110             }
1111         } catch(IOException e) {
1112             Logger.defaultLogError(e);
1113         } catch (RuntimeException e) {
1114           Logger.defaultLogError(e);
1115         }
1116     }
1117
1118     @Override
1119     public VirtualGraph getProvider() {
1120         return provider;
1121     }
1122
1123     @Override
1124     public void clearUndoList(WriteTraits writeTraits) {
1125         writeSupport.clearUndoList(writeTraits);
1126     }
1127     
1128     public void markUndoPoint() {
1129         writeSupport.startUndo();
1130     }
1131     
1132 }