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