/******************************************************************************* * Copyright (c) 2007, 2018 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.db.impl.graph; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.TreeMap; import java.util.function.Consumer; import org.simantics.databoard.Bindings; import org.simantics.databoard.accessor.Accessor; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.BindingConstructionException; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.primitives.MutableBoolean; import org.simantics.databoard.primitives.MutableByte; import org.simantics.databoard.primitives.MutableDouble; import org.simantics.databoard.primitives.MutableFloat; import org.simantics.databoard.primitives.MutableInteger; import org.simantics.databoard.primitives.MutableLong; import org.simantics.databoard.primitives.MutableString; import org.simantics.databoard.serialization.SerializationException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.util.binary.RandomAccessBinary; import org.simantics.db.DevelopmentKeys; import org.simantics.db.ExternalValueSupport; import org.simantics.db.Metadata; import org.simantics.db.Resource; import org.simantics.db.Statement; import org.simantics.db.VirtualGraph; import org.simantics.db.WriteGraph; import org.simantics.db.WriteOnlyGraph; import org.simantics.db.common.request.WriteOnlyRequest; import org.simantics.db.common.utils.Literals; import org.simantics.db.common.utils.Logger; import org.simantics.db.exception.ArgumentException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.InternalException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.ServiceException; import org.simantics.db.impl.DatabaseUtils; import org.simantics.db.impl.MemWatch; import org.simantics.db.impl.ResourceImpl; import org.simantics.db.impl.internal.RandomAccessValueSupport; import org.simantics.db.impl.internal.ResourceData; import org.simantics.db.impl.query.CacheEntry; import org.simantics.db.impl.query.QueryProcessor; import org.simantics.db.impl.support.WriteRequestScheduleSupport; import org.simantics.db.procedure.Procedure; import org.simantics.db.request.DelayedWrite; import org.simantics.db.request.DelayedWriteResult; import org.simantics.db.request.Write; import org.simantics.db.request.WriteOnly; import org.simantics.db.request.WriteOnlyResult; import org.simantics.db.request.WriteResult; import org.simantics.db.request.WriteTraits; import org.simantics.layer0.Layer0; import org.simantics.utils.Development; import org.simantics.utils.datastructures.Pair; import gnu.trove.map.hash.THashMap; final public class WriteGraphImpl extends ReadGraphImpl implements WriteGraph { final public static Binding DATA_TYPE_BINDING = Bindings.getBindingUnchecked(Datatype.class); final public WriteSupport writeSupport; final public VirtualGraph provider; private String resourceName(Resource resource) throws DatabaseException { if(Development.DEVELOPMENT) { Boolean names = Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_NAMES, Bindings.BOOLEAN); if(names && !writeSupport.writeOnly()) return DatabaseUtils.getReadableName(this, resource); else return resource.toString(); } throw new IllegalStateException(); } private Layer0 getBuiltins() { return getService(Layer0.class); } private WriteRequestScheduleSupport getWriteRequestScheduler() { return (WriteRequestScheduleSupport) getSession(); } private WriteGraphImpl(CacheEntry parent2, QueryProcessor readSupport, WriteSupport writeSupport, VirtualGraph provider) { super(parent2, readSupport); this.writeSupport = writeSupport; this.provider = provider; } public final static WriteGraphImpl create(QueryProcessor support, WriteSupport writeSupport, VirtualGraph provider) { WriteGraphImpl impl = new WriteGraphImpl(null, support, writeSupport, provider); try { writeSupport.setDefaultClusterSet(null); } catch (ServiceException e) { Logger.defaultLogError(e); } return impl; } final public WriteGraphImpl newAsync() { return this; } public WriteGraphImpl newSync(final VirtualGraph provider) { return new WriteGraphImpl(parent, processor, writeSupport, provider); } final public WriteGraphImpl newSync(final CacheEntry parent) { return new WriteGraphImpl(parent, processor, writeSupport, provider); } @Override public ReadGraphImpl newRestart(ReadGraphImpl impl) { WriteGraphImpl write = processor.getSession().getService(WriteGraphImpl.class); return write; } @Override final public Resource newResource() throws ServiceException { try { Resource result = writeSupport.createResource(provider); if(Development.DEVELOPMENT) { if(Development.getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN)) WriteLogger.logNewResource(this, result); } return result; } catch (DatabaseException e) { throw new ServiceException(e); } } @Override final public Resource newResource(long clusterId) throws ServiceException { try { Resource result = writeSupport.createResource(provider, clusterId); if(Development.DEVELOPMENT) { if(Development.getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN)) WriteLogger.logNewResource(this, result); } return result; } catch (DatabaseException e) { throw new ServiceException(e); } } @Override public Resource newResource(Resource clusterSet) throws ServiceException { try { Resource result; if (provider != null) result = writeSupport.createResource(provider); else result = writeSupport.createResource(provider, clusterSet); if(Development.DEVELOPMENT) { if(Development.getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN)) WriteLogger.logNewResource(this, result); } return result; } catch (ServiceException e) { throw e; } catch (DatabaseException e) { throw new ServiceException(e); } } @Override public void newClusterSet(Resource clusterSet) throws ServiceException { try { if (provider == null) writeSupport.createClusterSet(provider, clusterSet); if(Development.DEVELOPMENT) { if(Development.getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN)) WriteLogger.logNewResource(this, clusterSet); } } catch (ServiceException e) { throw e; } catch (DatabaseException e) { throw new ServiceException(e); } } @Override public Resource setClusterSet4NewResource(Resource clusterSet) throws ServiceException { return writeSupport.setDefaultClusterSet(clusterSet); } /** * Compares two object for equality, allowing null objects also. * * @param o1 obj1 * @param o2 obj2 * @return true if both arguments are null or equal */ static boolean safeEquals(Object o1, Object o2) { if (o1 == o2) return true; if (o1 == null && o2 == null) return true; if (o1 == null || o2 == null) return false; return o1.equals(o2); } @Override public void asyncRequest(DelayedWrite request) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, e -> { if(e != null) Logger.defaultLogError(e); }, null, Boolean.TRUE); } @Override public void asyncRequest(DelayedWrite request, Consumer callback) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, callback, null, Boolean.TRUE); } @Override public void asyncRequest(DelayedWriteResult request, Procedure procedure) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, procedure, null, Boolean.TRUE); } @Override public void asyncRequest(final Write r) { assert (r != null); getWriteRequestScheduler().scheduleRequest(r, e -> { if(e != null) Logger.defaultLogError(e); }, null, Boolean.TRUE); } @Override public void asyncRequest(Write request, Consumer callback) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, callback, null, Boolean.TRUE); } @Override public void asyncRequest(WriteOnly request) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, e -> { if(e != null) Logger.defaultLogError(e); }, null, Boolean.TRUE); } @Override public void asyncRequest(WriteOnly request, Consumer callback) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, callback, null, Boolean.TRUE); } @Override public void asyncRequest(WriteOnlyResult request, Procedure procedure) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, procedure, null, Boolean.TRUE); } @Override public void asyncRequest(WriteResult request, Procedure procedure) { assert (request != null); getWriteRequestScheduler().scheduleRequest(request, procedure, null, Boolean.TRUE); } @Override public void syncRequest(Write request) throws DatabaseException { Resource defaultClusterSet = setClusterSet4NewResource(null); WriteGraphImpl graph = (WriteGraphImpl)newSync(request.getProvider()); try { writeSupport.performWriteRequest(graph, request); } catch (DatabaseException e) { throw e; } catch (Throwable t) { throw new DatabaseException(t); } finally { setClusterSet4NewResource(defaultClusterSet); } } @Override public T syncRequest(WriteResult request) throws DatabaseException { Resource defaultClusterSet = setClusterSet4NewResource(null); WriteGraphImpl graph = (WriteGraphImpl)newSync(request.getProvider()); try { return writeSupport.performWriteRequest(graph, request); } catch (DatabaseException e) { throw e; } catch (Throwable t) { throw new DatabaseException(t); } finally { setClusterSet4NewResource(defaultClusterSet); } } @Override public void syncRequest(final DelayedWrite request) throws DatabaseException { try { final DelayedWriteGraph dwg = new DelayedWriteGraph(this); request.perform(dwg); syncRequest(new WriteOnlyRequest() { @Override public void perform(WriteOnlyGraph graph) throws DatabaseException { dwg.commit(graph, request); } }); } catch (DatabaseException e) { throw e; } catch (Throwable e) { throw new DatabaseException(e); } finally { } } @Override public void syncRequest(WriteOnly request) throws DatabaseException { Resource defaultClusterSet = setClusterSet4NewResource(null); try { writeSupport.performWriteRequest(this, request); } catch (DatabaseException e) { throw e; } catch (Throwable t) { throw new DatabaseException(t); } finally { setClusterSet4NewResource(defaultClusterSet); } } @Override public void claim(Resource subject, Resource predicate, Resource object) throws ServiceException { if(subject == null || predicate == null || object == null) { throw new ServiceException("Claim does not accept null arguments (subject=" + subject + ",predicate=" + predicate + ",object=" + object + ")."); } if(processor.isImmutable(object)) { claim(subject, predicate, null, object); } else { claim(subject, predicate, getPossibleInverse(predicate), object); } } @Override public void claim(Resource subject, Resource predicate, Resource inverse, Resource object) throws ServiceException { if (MemWatch.isLowOnMemory()) writeSupport.gc(); if(subject == null) throw new ServiceException("Claim does not accept null arguments (subject)."); if(predicate == null) throw new ServiceException("Claim does not accept null arguments (predicate)."); if(object == null) throw new ServiceException("Claim does not accept null arguments (object)."); if(provider == null && subject.isPersistent() && !object.isPersistent()) 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."); try { if(Development.DEVELOPMENT) { try { if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) { String text = "claim(" + resourceName(subject) + ":" + subject + ", " + resourceName(predicate) + ":" + predicate + ", " + resourceName(object) + ":" + object + ")"; if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) { StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); new Exception(text).printStackTrace(out); Development.dispatchEvent(new ClaimEventImpl(this, provider, subject, predicate, object, writer.toString())); } else { Development.dispatchEvent(new ClaimEventImpl(this, provider, subject, predicate, object, text)); } } if(Development.getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN)) WriteLogger.logClaim(this, subject, predicate, inverse, object); } catch (DatabaseException e) { Logger.defaultLogError(e); } } writeSupport.claim(provider, subject, predicate, object); } catch(RuntimeException e) { throw new ServiceException(e); } if(inverse == null) return; if(subject.equals(object) && predicate.equals(inverse)) { return; } // Do as claim(s,p,o) already works - // don't write inverse relations if object is immutable. if(processor.isImmutable(object)) return; try { writeSupport.claim(provider, object, inverse, subject); } catch(RuntimeException e) { throw new ServiceException(e); } } @Override public void deny(Resource subject) throws ServiceException { assert(subject != null); try { for(Statement stm : getStatements(subject, getBuiltins().IsWeaklyRelatedTo)) { if(subject.equals(stm.getSubject())) { Resource inverse = getPossibleInverse(stm.getPredicate()); // if(inverse == null) { // try { // String name = adapt(stm.getPredicate(), String.class); // throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + name + "' does not have an inverse."); // } catch (AdaptionException e) { // throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + stm.getPredicate() + "' does not have an inverse."); // } // } deny(stm.getSubject(), stm.getPredicate(), inverse, stm.getObject()); } } } catch(DatabaseException e) { throw new ServiceException(INTERNAL_ERROR_STRING, e); } } @Override public void deny(Resource subject, Resource predicate) throws ServiceException { assert(subject != null); assert(predicate != null); try { for (Statement stm : getStatements(subject, predicate)) { if (subject.equals(stm.getSubject())) { Resource inverse = getPossibleInverse(stm.getPredicate()); // if(inverse == null) { // try { // String name = adapt(stm.getPredicate(), String.class); // throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + name + "' does not have an inverse."); // } catch (AdaptionException e) { // throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + stm.getPredicate() + "' does not have an inverse."); // } // } deny(stm.getSubject(), stm.getPredicate(), inverse, stm.getObject()); } } } catch(DatabaseException e) { throw new ServiceException(INTERNAL_ERROR_STRING, e); } } @Override public void deny(Resource subject, Resource predicate, Resource object) throws ServiceException { assert(subject != null); assert(predicate != null); assert(object != null); try { for (Statement stm : getStatements(subject, predicate)) { if (subject.equals(stm.getSubject()) && object.equals(stm.getObject())) { Resource inverse = getPossibleInverse(stm.getPredicate()); deny(stm.getSubject(), stm.getPredicate(), inverse, stm.getObject()); } } } catch (DatabaseException e) { throw new ServiceException(INTERNAL_ERROR_STRING, e); } } @Override public void denyStatement(Resource subject, Resource predicate, Resource object) throws ServiceException { assert(subject != null); assert(predicate != null); assert(object != null); Resource inverse = getPossibleInverse(predicate); // if(inverse == null) { // try { // String name = adapt(predicate, String.class); // throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + name + "' does not have an inverse."); // } catch (AdaptionException e) { // throw new ResourceDoesNotSatisfyAssumptionException("Resource '" + predicate + "' does not have an inverse."); // } // } deny(subject, predicate, inverse, object); } @Override public void deny(Statement statement) throws ServiceException { assert(statement != null); denyStatement(statement.getSubject(), statement.getPredicate(), statement.getObject()); } @Override public void deny(Resource subject, Resource predicate, Resource inverse, Resource object) throws ServiceException { assert(subject != null); assert(predicate != null); assert(object != null); VirtualGraph provider = processor.getProvider(subject, predicate, object); deny(subject, predicate, inverse, object, provider); } /* * Note: We assume here that all inverse pairs of statements are stored in the same virtual graph. */ @Override public void deny(Resource subject, Resource predicate, Resource inverse, Resource object, VirtualGraph provider) throws ServiceException { assert(subject != null); assert(predicate != null); assert(object != null); if(Development.DEVELOPMENT) { try { if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) { String text = "deny(" + resourceName(subject) + ":" + subject + ", " + resourceName(predicate) + ":" + predicate + ", " + resourceName(object) + ":" + object + ")"; if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) { StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); new Exception(text).printStackTrace(out); Development.dispatchEvent(new DenyEventImpl(this, provider, subject, predicate, object, writer.toString())); } else { Development.dispatchEvent(new DenyEventImpl(this, provider, subject, predicate, object, text)); } } } catch (DatabaseException e) { Logger.defaultLogError(e); } } try { writeSupport.removeStatement(provider, subject, predicate, object); } catch(Throwable e) { throw new InternalException("bug in deny(s,p,i,o)", e); } if(inverse == null || (subject.equals(object) && predicate.equals(inverse))) return; // don't deny inverse relations if object is immutable. if(processor.isImmutable(object)) return; try { writeSupport.removeStatement(provider, object, inverse, subject); } catch(Throwable e) { throw new InternalException("bug in deny(s,p,i,o)", e); } } @Override public void claimValue(Resource resource, Object value) throws ServiceException { try { Binding b = Bindings.getBinding(value.getClass()); claimValue(resource, value, b); } catch(BindingConstructionException e) { throw new IllegalArgumentException(e); } } @Override public void claimValue(Resource resource, Object value, Binding binding) throws ServiceException { if(value == null) throw new ServiceException("claimValue does not accept null value"); if(binding == null) throw new ServiceException("claimValue does not accept null binding"); if(Development.DEVELOPMENT) { try { if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) { String valueText = Literals.shortString(value.toString()); String text = "claimValue(" + resourceName(resource) + ", " + valueText + ")"; if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) { StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); new Exception(text).printStackTrace(out); Development.dispatchEvent(new ClaimValueEventImpl(this, provider, resource, value, binding, writer.toString())); } else { Development.dispatchEvent(new ClaimValueEventImpl(this, provider, resource, value, binding, text)); } } } catch (DatabaseException e) { Logger.defaultLogError(e); } } try { Serializer serializer = getSerializer(binding); //Serializer serializer = binding.serializer(); byte[] data = serializer.serialize(value); if(Development.DEVELOPMENT) { try { if(Development.getProperty(DevelopmentKeys.WRITELOGGER_LOG, Bindings.BOOLEAN)) WriteLogger.logValue(this, resource, data); } catch (DatabaseException e) { Logger.defaultLogError(e); } } writeSupport.claimValue(provider, resource, data); } catch (DatabaseException e) { throw new ServiceException(e); } catch (SerializationException e) { throw new ServiceException(e); } catch (IOException e) { throw new ServiceException(e); } } THashMap, Resource> builtinValues = new THashMap, Resource>(32); private void initBuiltinValues(Layer0 b) { if(!builtinValues.isEmpty()) return; builtinValues.put(String.class, b.String); builtinValues.put(Double.class, b.Double); builtinValues.put(Float.class, b.Float); builtinValues.put(Long.class, b.Long); builtinValues.put(Integer.class, b.Integer); builtinValues.put(Byte.class, b.Byte); builtinValues.put(Boolean.class, b.Boolean); builtinValues.put(Variant.class, b.Variant); builtinValues.put(String[].class, b.StringArray); builtinValues.put(double[].class, b.DoubleArray); builtinValues.put(float[].class, b.FloatArray); builtinValues.put(long[].class, b.LongArray); builtinValues.put(int[].class, b.IntegerArray); builtinValues.put(byte[].class, b.ByteArray); builtinValues.put(boolean[].class, b.BooleanArray); builtinValues.put(MutableString.class, b.String); builtinValues.put(MutableDouble.class, b.Double); builtinValues.put(MutableFloat.class, b.Float); builtinValues.put(MutableLong.class, b.Long); builtinValues.put(MutableInteger.class, b.Integer); builtinValues.put(MutableByte.class, b.Byte); builtinValues.put(MutableBoolean.class, b.Boolean); } @Override public void addLiteral(Resource resource, Resource predicate, Resource inverse, Resource type, Object value, Binding binding) throws ManyObjectsForFunctionalRelationException, ServiceException { Layer0 b = getBuiltins(); Resource valueResource = newResource(); claim(valueResource, b.InstanceOf, null, type); claim(resource, predicate, inverse, valueResource); claimValue(valueResource, value, binding); } @Override public void addLiteral(Resource resource, Resource predicate, Resource inverse, Object value, Binding binding) throws ManyObjectsForFunctionalRelationException, ServiceException { Layer0 b = getBuiltins(); initBuiltinValues(b); Resource literal = newResource(); Class clazz = value.getClass(); Resource type = builtinValues.get(clazz); if (type == null) { type = b.Literal; Resource dataType = newResource(); claim(dataType, b.InstanceOf, null, b.DataType); claimValue(dataType, binding.type(), DATA_TYPE_BINDING); claim(literal, b.HasDataType, b.HasDataType_Inverse, dataType); } claim(literal, b.InstanceOf, null, type); claim(resource, predicate, inverse, literal); claimValue(literal, value, binding); } @Override public T newLiteral(Resource resource, Resource predicate, Datatype datatype, Object initialValue) throws DatabaseException { Layer0 b = getBuiltins(); initBuiltinValues(b); Resource literal = newResource(); claim(literal, b.InstanceOf, null, b.Literal); Resource dataType = newResource(); claim(dataType, b.InstanceOf, null, b.DataType); claimValue(dataType, datatype, DATA_TYPE_BINDING); claim(literal, b.HasDataType, dataType); claim(resource, predicate, literal); return createAccessor(literal, datatype, initialValue); } @Override public RandomAccessBinary createRandomAccessBinary (Resource resource, Resource predicate, Datatype datatype, Object initiaValue) throws DatabaseException { Layer0 b = getBuiltins(); initBuiltinValues(b); Resource literal = newResource(); claim(literal, b.InstanceOf, null, b.Literal); Resource dataType = newResource(); claim(dataType, b.InstanceOf, null, b.DataType); claimValue(dataType, datatype, DATA_TYPE_BINDING); claim(literal, b.HasDataType, dataType); claim(resource, predicate, literal); return createRandomAccessBinary(literal, datatype, initiaValue); } @Override public void claimLiteral(Resource resource, Resource predicate, Object value) throws ManyObjectsForFunctionalRelationException, ServiceException { try { Binding binding = Bindings.getBinding(value.getClass()); claimLiteral(resource, predicate, value, binding); } catch(BindingConstructionException e) { throw new IllegalArgumentException(e); } } @Override public void claimLiteral(Resource resource, Resource predicate, Object value, Binding binding) throws ManyObjectsForFunctionalRelationException, ServiceException { Layer0 b = getBuiltins(); initBuiltinValues(b); Statement literalStatement = getPossibleStatement(resource, predicate); if(literalStatement != null && resource.equals(literalStatement.getSubject())) { claimValue(literalStatement.getObject(), value, binding); } else { Class clazz = value.getClass(); Resource type = builtinValues.get(clazz); Resource literal = newResource(); if (type == null) { type = b.Literal; Resource dataType = newResource(); claim(dataType, b.InstanceOf, null, b.DataType); claimValue(dataType, binding.type(), DATA_TYPE_BINDING); claim(literal, b.HasDataType, null, dataType); } claim(literal, b.InstanceOf, null, type); claimValue(literal, value, binding); claim(resource, predicate, literal); } if(Development.DEVELOPMENT) { try { getPossibleStatement(resource, predicate); } catch (ManyObjectsForFunctionalRelationException e) { System.err.println("err " + ((ResourceImpl)resource).id + " " + ((ResourceImpl)predicate).id); getPossibleStatement(resource, predicate); } } } @Override public void claimLiteral(Resource resource, Resource predicate, Resource type, Object value) throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException { try { Binding binding = Bindings.getBinding(value.getClass()); claimLiteral(resource, predicate, type, value, binding); } catch(BindingConstructionException e) { throw new IllegalArgumentException(e); } } @Override public void claimLiteral(Resource resource, Resource predicate, Resource type, Object value, Binding binding) throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException { Layer0 b = getBuiltins(); Statement literalStatement = getPossibleStatement(resource, predicate); if(literalStatement != null && resource.equals(literalStatement.getSubject())) { claimValue(literalStatement.getObject(), value, binding); } else { Resource literal = newResource(); claim(literal, b.InstanceOf, null, type); claimValue(literal, value, binding); claim(resource, predicate, literal); } } @Override public void claimLiteral(Resource resource, Resource predicate, Resource inverse, Resource type, Object value) throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException { try { Binding binding = Bindings.getBinding(value.getClass()); claimLiteral(resource, predicate, inverse, type, value, binding); } catch(BindingConstructionException e) { throw new IllegalArgumentException(e); } } @Override public void claimLiteral(Resource resource, Resource predicate, Resource inverse, Resource type, Object value, Binding binding) throws org.simantics.db.exception.BindingException, ManyObjectsForFunctionalRelationException, ServiceException { Layer0 b = getBuiltins(); Statement literalStatement = getPossibleStatement(resource, predicate); if(literalStatement != null && resource.equals(literalStatement.getSubject())) { claimValue(literalStatement.getObject(), value, binding); } else { Resource literal = newResource(); claim(literal, b.InstanceOf, null, type); claimValue(literal, value, binding); claim(resource, predicate, inverse, literal); } } @Override public void denyValue(Resource resource) throws ServiceException { denyValue0(resource, null); } @Override public void denyValue(Resource resource, VirtualGraph valueProvider) throws ServiceException { denyValue0(resource, valueProvider); } /** * @param resource resource to remove value from * @param valueProvider null means the caller doesn't know and * this method must find out * @throws ServiceException */ private void denyValue0(Resource resource, VirtualGraph valueProvider) throws ServiceException { if(Development.DEVELOPMENT) { try { if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG, Bindings.BOOLEAN)) { Object oldValue = getPossibleValue(resource); String valueText = oldValue == null ? "" : oldValue.toString(); String text = "denyValue(" + resourceName(resource) + ", " + valueText + ")"; if (oldValue != null) { if(Development.getProperty(DevelopmentKeys.WRITEGRAPH_DEBUG_STACK, Bindings.BOOLEAN)) { StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); new Exception(text).printStackTrace(out); Development.dispatchEvent(new DenyValueEventImpl(this, provider, resource, oldValue, writer.toString())); } else { Development.dispatchEvent(new DenyValueEventImpl(this, provider, resource, oldValue, text)); } } } } catch (DatabaseException e) { Logger.defaultLogError(e); } } if (provider != null) { // Can only remove from the specified VirtualGraph if (resource.isPersistent()) throw new ArgumentException("Tried to remove literal value from persistent resource " + resource + " using virtual graph '" + provider.getIdentifier() + "'"); writeSupport.denyValue(provider, resource); } else { // Can remove from any provider if (resource.isPersistent()) { // A persistent resource cannot have a virtual literal // so this should be safe. writeSupport.denyValue(provider, resource); } else { // A virtual resource cannot have a persistent literal. // Must look for a virtual graph for resource. Layer0 L0 = getBuiltins(); if (valueProvider == null) { if (hasStatement(resource, L0.InstanceOf)) { valueProvider = processor.getProvider(resource, L0.InstanceOf); } else if (hasStatement(resource, L0.Inherits)) { valueProvider = processor.getProvider(resource, L0.Inherits); } else if (hasStatement(resource, L0.SubrelationOf)) { valueProvider = processor.getProvider(resource, L0.SubrelationOf); } } if (valueProvider != null) writeSupport.denyValue(valueProvider, resource); } } } @Override public void denyValue(Resource resource, Resource predicate) throws ManyObjectsForFunctionalRelationException, ServiceException { Statement valueStatement = getPossibleStatement(resource, predicate); if (valueStatement != null && !valueStatement.isAsserted(resource)) { Resource value = valueStatement.getObject(); Resource inverse = getPossibleInverse(predicate); if (provider != null) { // Can only remove from the specified provider deny(resource, predicate, inverse, value, provider); writeSupport.denyValue(provider, value); } else { // Can remove from any provider VirtualGraph statementProvider = processor.getProvider(resource, valueStatement.getPredicate(), value); deny(resource, predicate, inverse, value, statementProvider); denyValue0(resource, statementProvider); } } } @Override public void flushCluster() { writeSupport.flushCluster(); } @Override public void flushCluster(Resource r) { writeSupport.flushCluster(r); } Object serialize(Object value) { Class clazz = value.getClass(); if(clazz.isArray()) return value; if(Double.class == clazz) return new double[] { (Double)value }; else if(String.class == clazz) return new String[] { (String)value }; else if(Integer.class == clazz) return new int[] { (Integer)value }; else if(Boolean.class == clazz) return new boolean[] { (Boolean)value }; else if(Long.class == clazz) return new long[] { (Long)value }; else if(Byte.class == clazz) return new byte[] { (Byte)value }; else if(Float.class == clazz) return new float[] { (Float)value }; else return value; } @Override public void addMetadata(Metadata data) throws ServiceException { writeSupport.addMetadata(data); } @Override public T getMetadata(Class clazz) throws ServiceException { return writeSupport.getMetadata(clazz); } @Override public TreeMap getMetadata() { return writeSupport.getMetadata(); } @Override public String toString() { return "WriteGraphImpl[thread=" + Thread.currentThread() + "]"; } public void commitAccessorChanges(WriteTraits writeTraits) { try { RandomAccessValueSupport ravs = getSession().peekService(RandomAccessValueSupport.class); if (ravs == null) return; for(Pair entry : ravs.entries()) { ResourceData rd = entry.second; if(!rd.isChanged()) continue; Resource resource = entry.first; try { ExternalValueSupport evs = getService(ExternalValueSupport.class); long vsize = rd.oldExternalValue ? evs.getValueSize(this, resource) : -1; long bsize = rd.binaryFile.length(); final int N = 1<<20; final int LIMIT = 10 * 1000 * 1000; long left = bsize; long offset = 0; byte[] bytes = new byte[N]; rd.binaryFile.reset(); rd.binaryFile.position(0); int count = 0; // if (LIMIT < left) // evs.moveValue(this, resource); while (left > 0) { int length = N < left ? N : (int)left; rd.binaryFile.readFully(bytes, 0, length); evs.writeValue(this, resource, offset, length, bytes); offset += length; left -= length; count += length; if (count > LIMIT) { count = 0; // evs.commitAndContinue(this, writeTraits, resource); // evs.moveValue(this, resource); // This is needed so we don't create too many requests and run out of heap. evs.wait4RequestsLess(1); } } if (bsize < vsize) // truncate evs.writeValue(this, resource, bsize, 0, new byte[0]); } catch (DatabaseException e) { Logger.defaultLogError(e); } } } catch(IOException e) { Logger.defaultLogError(e); } catch (RuntimeException e) { Logger.defaultLogError(e); } } @Override public VirtualGraph getProvider() { return provider; } @Override public void clearUndoList(WriteTraits writeTraits) { writeSupport.clearUndoList(writeTraits); } public void markUndoPoint() { writeSupport.startUndo(); } }