+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.db.layer0.genericrelation;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.UUID;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.util.ObjectUtils;\r
+import org.simantics.datatypes.literal.GUID;\r
+import org.simantics.db.AsyncReadGraph;\r
+import org.simantics.db.ChangeSet;\r
+import org.simantics.db.ChangeSet.StatementChange;\r
+import org.simantics.db.MetadataI;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.RequestProcessor;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.Indexing;\r
+import org.simantics.db.common.changeset.GenericChangeListener;\r
+import org.simantics.db.common.request.IndexRoot;\r
+import org.simantics.db.common.request.ReadRequest;\r
+import org.simantics.db.common.request.SuperTypeString;\r
+import org.simantics.db.common.request.TypeString;\r
+import org.simantics.db.common.request.UnaryRead;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.event.ChangeListener;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.adapter.GenericRelation;\r
+import org.simantics.db.layer0.adapter.GenericRelationIndex;\r
+import org.simantics.db.layer0.genericrelation.DependencyChanges.Change;\r
+import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentAddition;\r
+import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentModification;\r
+import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentRemoval;\r
+import org.simantics.db.layer0.genericrelation.DependencyChanges.LinkChange;\r
+import org.simantics.db.procedure.AsyncContextMultiProcedure;\r
+import org.simantics.db.procedure.AsyncContextProcedure;\r
+import org.simantics.db.service.CollectionSupport;\r
+import org.simantics.db.service.DirectQuerySupport;\r
+import org.simantics.db.service.GraphChangeListenerSupport;\r
+import org.simantics.db.service.ManagementSupport;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.operation.Layer0X;\r
+import org.simantics.utils.datastructures.Pair;\r
+import org.simantics.utils.logging.TimeLogger;\r
+\r
+public class DependenciesRelation extends UnsupportedRelation implements GenericRelationIndex {\r
+\r
+ private static final boolean DEBUG = false;\r
+ static final boolean DEBUG_LISTENERS = false;\r
+ private static final boolean PROFILE = false;\r
+\r
+ @SuppressWarnings("unchecked")\r
+ private final static Pair<String, String>[] fields = new Pair[] {\r
+ Pair.make(Dependencies.FIELD_MODEL, "Long"),\r
+ Pair.make(Dependencies.FIELD_PARENT, "Long"),\r
+ Pair.make(Dependencies.FIELD_RESOURCE, "Long"),\r
+ Pair.make(Dependencies.FIELD_NAME, "String"),\r
+ Pair.make(Dependencies.FIELD_TYPES, "Text"),\r
+ Pair.make(Dependencies.FIELD_GUID, "Text")\r
+ };\r
+\r
+ final Resource resource;\r
+\r
+ public DependenciesRelation(ReadGraph graph, Resource resource) {\r
+ this.resource = resource;\r
+ synchronized(this) {\r
+ Session session = graph.getSession();\r
+ DependenciesListenerStore store = session.peekService(DependenciesListenerStore.class);\r
+ if(store == null) session.registerService(DependenciesListenerStore.class, new DependenciesListenerStore());\r
+ }\r
+ }\r
+\r
+ class Process {\r
+\r
+ final ArrayList<Entry> result = new ArrayList<Entry>();\r
+ final AsyncContextMultiProcedure<Resource, Resource> structure;\r
+ final AsyncContextProcedure<Entry, String> names;\r
+ final AsyncContextProcedure<Entry, Resource> type;\r
+\r
+ Process(ReadGraph graph, final Resource resource) throws DatabaseException {\r
+\r
+ final Layer0 L0 = Layer0.getInstance(graph);\r
+ final DirectQuerySupport dqs = graph.getService(DirectQuerySupport.class);\r
+ final CollectionSupport cs = graph.getService(CollectionSupport.class);\r
+\r
+ names = dqs.compilePossibleRelatedValue(graph, L0.HasName, new AsyncContextProcedure<Entry, String>() {\r
+\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, Entry entry, String name) {\r
+ entry.name = name;\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ Logger.defaultLogError(throwable);\r
+ }\r
+\r
+ });\r
+\r
+ type = new AsyncContextProcedure<Entry, Resource>() {\r
+\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, Entry entry, Resource type) {\r
+ entry.principalType = type;\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ Logger.defaultLogError(throwable);\r
+ }\r
+\r
+ };\r
+\r
+ structure = dqs.compileForEachObject(graph, L0.ConsistsOf, new AsyncContextMultiProcedure<Resource, Resource>() {\r
+\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, Resource parent, Resource child) {\r
+ // WORKAROUND: don't browse virtual child resources\r
+ if(!child.isPersistent()) return;\r
+ Entry entry = new Entry(parent, child, "", "", "");\r
+ result.add(entry);\r
+ dqs.forEachObjectCompiled(graph, child, child, structure);\r
+ dqs.forPossibleRelatedValueCompiled(graph, child, entry, names);\r
+ dqs.forPossibleDirectType(graph, child, entry, type);\r
+ }\r
+\r
+ @Override\r
+ public void finished(AsyncReadGraph graph) {\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ Logger.defaultLogError(throwable);\r
+ }\r
+\r
+ });\r
+\r
+ graph.syncRequest(new ReadRequest() {\r
+\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ dqs.forEachObjectCompiled(graph, resource, resource, structure);\r
+ }\r
+\r
+ });\r
+\r
+ Map<Resource, String> typeStrings = cs.createMap(String.class);\r
+ for(Entry e : result) {\r
+ if(e.principalType != null) {\r
+ String typeString = typeStrings.get(e.principalType);\r
+ if(typeString == null) {\r
+ typeString = graph.syncRequest(new SuperTypeString(e.principalType));\r
+ if (typeString.isEmpty()) {\r
+ Logger.defaultLogError(new DatabaseException("No name for type " + NameUtils.getURIOrSafeNameInternal(graph, e.resource) + " (" + e.resource + ")"));\r
+ }\r
+ typeStrings.put(e.principalType, typeString);\r
+ }\r
+ e.types = typeString;\r
+ } else {\r
+ e.types = graph.syncRequest(new TypeString(L0, graph.getTypes(e.resource)));\r
+ }\r
+ GUID id = graph.getPossibleRelatedValue(e.resource, L0.identifier, GUID.BINDING);\r
+ if(id != null)\r
+ e.id = id.indexString();\r
+ else \r
+ e.id = "";\r
+ }\r
+\r
+ //SessionGarbageCollection.gc(null, graph.getSession(), false, null);\r
+ \r
+ }\r
+\r
+ }\r
+\r
+ public ArrayList<Entry> find(ReadGraph graph, final Resource model) throws DatabaseException {\r
+ return new Process(graph, model).result;\r
+ }\r
+\r
+ @Override\r
+ public GenericRelation select(String bindingPattern, Object[] constants) {\r
+ checkSelectionArguments(bindingPattern, constants, new String[] { Dependencies.getBindingPattern() });\r
+ final long subjectId = (Long)constants[0];\r
+ return new UnsupportedRelation() {\r
+\r
+ @Override\r
+ public boolean isRealizable() {\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ final public List<Object[]> realize(ReadGraph graph) throws DatabaseException {\r
+\r
+ long time = System.nanoTime();\r
+\r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+\r
+ Resource subject = ss.getResource(subjectId); \r
+ \r
+ Collection<Entry> entries = find(graph, subject);\r
+\r
+ long time2 = System.nanoTime();\r
+\r
+ if (PROFILE)\r
+ System.out.println("Found " + entries.size() + " dependencies in " + 1e-6 * (time2 - time) + "ms for " + graph.getPossibleURI(subject) + ".");\r
+\r
+ ArrayList<Object[]> result = new ArrayList<Object[]>();\r
+ for (Entry entry : entries) {\r
+ result.add(new Object[] { ss.getRandomAccessId(entry.parent), ss.getRandomAccessId(entry.resource), entry.name, entry.types, entry.id });\r
+ }\r
+ return result;\r
+\r
+ }\r
+\r
+ };\r
+ }\r
+\r
+ @Override\r
+ public Pair<String, String>[] getFields() {\r
+ return fields;\r
+ }\r
+\r
+ @Override\r
+ public List<Map<String, Object>> query(RequestProcessor session, String search, String bindingPattern, Object[] constants, int maxResultCount) {\r
+ if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffff'");\r
+ IndexedRelations indexer = session.getService(IndexedRelations.class);\r
+ return indexer.query(null, search, session, resource, (Resource)constants[0], maxResultCount);\r
+ }\r
+ \r
+ @Override\r
+ public List<Resource> queryResources(RequestProcessor session, String search, String bindingPattern, Object[] constants, int maxResultCount) {\r
+ if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffff'");\r
+ IndexedRelations indexer = session.getService(IndexedRelations.class);\r
+ return indexer.queryResources(null, search, session, resource, (Resource)constants[0], maxResultCount);\r
+ }\r
+\r
+ @Override\r
+ public List<Map<String, Object>> list(RequestProcessor session, String bindingPattern, Object[] constants, int maxResultCount) {\r
+ if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffff'");\r
+ IndexedRelations indexer = session.getService(IndexedRelations.class);\r
+ return indexer.query(null, null, session, resource, (Resource)constants[0], maxResultCount);\r
+ }\r
+\r
+ public static class DependencyChangesRequest extends UnaryRead<ChangeSet, DependencyChanges> {\r
+\r
+ @SuppressWarnings("unused")\r
+ final private static boolean LOG = false;\r
+\r
+ public DependencyChangesRequest(ChangeSet parameter) {\r
+ super(parameter);\r
+ }\r
+\r
+ @Override\r
+ public DependencyChanges perform(ReadGraph graph) throws DatabaseException {\r
+\r
+ DependencyChangesWriter w = new DependencyChangesWriter(graph);\r
+ Layer0 l0 = w.l0;\r
+ Resource changeInformation = graph.getPossibleResource("http://www.simantics.org/Modeling-1.2/changeInformation/Inverse");\r
+\r
+ for (Resource value : parameter.changedValues()) {\r
+ Statement modifiedComponent = graph.getPossibleStatement(value, l0.PropertyOf);\r
+ if (modifiedComponent == null\r
+ || modifiedComponent.getPredicate().equals(changeInformation))\r
+ continue;\r
+ //System.err.println("+comp modi " + NameUtils.getSafeName(graph, renamedComponent, true));\r
+ w.addComponentModification(modifiedComponent.getObject());\r
+ }\r
+ for (Resource value : parameter.changedResources()) {\r
+ // No more info => need to check further\r
+ if(!graph.isImmutable(value))\r
+ w.addComponentModification(value);\r
+ }\r
+ for (StatementChange change : parameter.changedStatements()) {\r
+ //System.err.println("-stm " + NameUtils.getSafeName(graph, change.getSubject(), true) + " " + NameUtils.getSafeName(graph, change.getPredicate(), true) + " " + NameUtils.getSafeName(graph, change.getObject(), true));\r
+ Resource subject = change.getSubject();\r
+ Resource predicate = change.getPredicate();\r
+ Resource object = change.getObject();\r
+ if(!object.isPersistent()) continue;\r
+ if (predicate.equals(l0.ConsistsOf)) {\r
+ if (change.isClaim())\r
+ w.addComponentAddition(subject, object);\r
+ else \r
+ w.addComponentRemoval(subject, object);\r
+ } else if (predicate.equals(l0.IsLinkedTo)) {\r
+ w.addLinkChange(subject);\r
+ } else /*if (graph.isSubrelationOf(predicate, l0.DependsOn))*/ {\r
+ //System.err.println("-modi " + NameUtils.getSafeName(graph, subject, true));\r
+ w.addComponentModification(subject);\r
+ } \r
+ }\r
+ return w.getResult();\r
+ }\r
+\r
+ };\r
+\r
+ private static int trackers = 0;\r
+ \r
+ private static ChangeListener listener;\r
+\r
+ public static void assertFinishedTracking() {\r
+ if(trackers != 0) throw new IllegalStateException("Trackers should be 0 (was " + trackers + ")");\r
+ }\r
+ \r
+ @Override\r
+ public synchronized void untrack(RequestProcessor processor, final Resource model) {\r
+\r
+ trackers--;\r
+ \r
+ if(trackers < 0) throw new IllegalStateException("Dependency tracking reference count is broken");\r
+ \r
+ if(trackers == 0) {\r
+ \r
+ if(listener == null) throw new IllegalStateException("Dependency tracking was not active");\r
+ \r
+ GraphChangeListenerSupport changeSupport = processor.getService(GraphChangeListenerSupport.class);\r
+ changeSupport.removeMetadataListener(listener);\r
+ listener = null;\r
+ \r
+ }\r
+ \r
+ }\r
+\r
+ @Override\r
+ public synchronized void trackAndIndex(RequestProcessor processor, Resource model__) {\r
+\r
+ if(trackers == 0) {\r
+\r
+ if(listener != null) throw new IllegalStateException("Dependency tracking was active");\r
+\r
+ listener = new GenericChangeListener<DependencyChangesRequest, DependencyChanges>() {\r
+\r
+ @Override\r
+ public boolean preEventRequest() {\r
+ return !Indexing.isDependenciesIndexingDisabled();\r
+ }\r
+\r
+ @Override\r
+ public void onEvent(ReadGraph graph, MetadataI metadata, DependencyChanges event) throws DatabaseException {\r
+\r
+ TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: starting index update processing");\r
+\r
+ if(DEBUG)\r
+ System.err.println("Adding metadata " + event + " in revision " + graph.getService(ManagementSupport.class).getHeadRevisionId());\r
+\r
+ WriteGraph w = (WriteGraph)graph;\r
+ if(!event.isEmpty())\r
+ w.addMetadata(event);\r
+\r
+ final Session session = graph.getSession();\r
+ final IndexedRelations indexer = session.getService(IndexedRelations.class);\r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+\r
+ for(Map.Entry<Resource, Change[]> modelEntry : event.get().entrySet()) {\r
+\r
+ final Resource model = modelEntry.getKey();\r
+ final Change[] changes = modelEntry.getValue();\r
+\r
+ boolean linkChange = false;\r
+\r
+ Collection<Object[]> _additions = Collections.emptyList();\r
+ Collection<Object> _removals = Collections.emptyList();\r
+ Collection<Object> _replacementKeys = Collections.emptyList();\r
+ Collection<Object[]> _replacementObjects = Collections.emptyList();\r
+ Collection<Pair<String, String>> _typeChanges = Collections.emptyList();\r
+\r
+ if(DEBUG) System.out.println("MODEL: " + NameUtils.getSafeLabel(graph, model));\r
+ // final Change[] changes = event.get(model);\r
+ if(DEBUG) System.out.println(" CHANGES: " + Arrays.toString(changes));\r
+ if (changes != null) {\r
+ _additions = new ArrayList<Object[]>();\r
+ _removals = new ArrayList<Object>();\r
+ _replacementKeys = new ArrayList<Object>();\r
+ _replacementObjects = new ArrayList<Object[]>();\r
+ _typeChanges = new HashSet<Pair<String, String>>();\r
+\r
+ for (Change _entry : changes) {\r
+ if (_entry instanceof ComponentAddition) {\r
+ ComponentAddition entry = (ComponentAddition)_entry;\r
+ final String name = graph.getPossibleRelatedValue(entry.component, L0.HasName, Bindings.STRING);\r
+ final GUID id = graph.getPossibleRelatedValue(entry.component, L0.identifier, GUID.BINDING);\r
+ final String types = graph.syncRequest(new TypeString(L0, graph.getTypes(entry.component)));\r
+ if (name != null && types != null) {\r
+ if(!entry.isValid(graph)) continue;\r
+ Resource parent = graph.getPossibleObject(entry.component, L0.PartOf);\r
+ if (parent != null) {\r
+ _additions.add(new Object[] { ss.getRandomAccessId(parent), ss.getRandomAccessId(entry.component), name, types, id != null ? id.indexString() : "" });\r
+ } else {\r
+ System.err.println("resource " + entry.component + ": no parent for entry " + name + " " + types);\r
+ }\r
+ } else {\r
+ System.err.println("resource " + entry.component + ": " + name + " " + types);\r
+ }\r
+ } else if(_entry instanceof ComponentModification) {\r
+ ComponentModification entry = (ComponentModification)_entry;\r
+ final String name = graph.getPossibleRelatedValue(entry.component, L0.HasName, Bindings.STRING);\r
+ final GUID id = graph.getPossibleRelatedValue(entry.component, L0.identifier, GUID.BINDING);\r
+ if(graph.isInstanceOf(entry.component, L0.Type)) {\r
+ SerialisationSupport support = session.getService(SerialisationSupport.class);\r
+ _typeChanges.add(new Pair<String, String>(name, String.valueOf(support.getRandomAccessId((Resource) entry.component))));\r
+ } else {\r
+ final String types = graph.syncRequest(new TypeString(L0, graph.getTypes(entry.component)));\r
+ if (name != null && types != null) {\r
+ Resource part = graph.getPossibleObject(entry.component, L0.PartOf);\r
+ if(part != null) {\r
+ _replacementKeys.add(ss.getRandomAccessId(entry.component));\r
+ _replacementObjects.add(new Object[] { ss.getRandomAccessId(part), \r
+ ss.getRandomAccessId(entry.component), name, types, id != null ? id.indexString() : "" });\r
+ }\r
+ }\r
+ }\r
+ } else if (_entry instanceof ComponentRemoval) {\r
+ ComponentRemoval entry = (ComponentRemoval)_entry;\r
+ if(!entry.isValid(graph)) continue;\r
+ _removals.add(ss.getRandomAccessId(((ComponentRemoval)_entry).component));\r
+ } else if (_entry instanceof LinkChange) {\r
+ linkChange = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ final boolean reset = linkChange || event.hasUnresolved;\r
+ //System.err.println("dependencies(" + NameUtils.getSafeLabel(graph, model) + "): reset=" + reset + " linkChange=" + linkChange + " unresolved=" + event.hasUnresolved );\r
+\r
+ if (reset || !_additions.isEmpty() || !_removals.isEmpty() || !_replacementKeys.isEmpty() || !_typeChanges.isEmpty()) {\r
+\r
+ TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: starting index update");\r
+\r
+ final Collection<Object[]> additions = _additions;\r
+ final Collection<Object> removals = _removals;\r
+ final Collection<Object> replacementKeys = _replacementKeys;\r
+ final Collection<Object[]> replacementObjects = _replacementObjects; \r
+ final boolean typeNameChanges = typeNameChanges(graph, indexer, model, _typeChanges);\r
+\r
+ final UUID pending = Indexing.makeIndexPending();\r
+\r
+ {\r
+ {\r
+ try {\r
+ boolean didChange = false;\r
+ // Unresolved and linkChanges are not relevant any more\r
+ boolean doReset = typeNameChanges;\r
+\r
+ if (doReset) {\r
+\r
+ if(DEBUG) {\r
+ System.err.println("resetIndex " + reset + " " + typeNameChanges);\r
+ }\r
+\r
+ indexer.removeAll(null, graph, DependenciesRelation.this, resource, model);\r
+ didChange = true;\r
+\r
+ } else {\r
+\r
+ if (!replacementKeys.isEmpty() && (replacementKeys.size() == replacementObjects.size())) {\r
+ if(DEBUG) {\r
+ System.out.println(replacementKeys.size() + " index replacements: " + replacementKeys);\r
+ }\r
+ didChange |= indexer.replace(null, graph, DependenciesRelation.this, resource, model, Dependencies.FIELD_RESOURCE, replacementKeys, replacementObjects);\r
+ }\r
+ if (!removals.isEmpty()) {\r
+ if(DEBUG) {\r
+ System.out.println(removals.size() + " index removals: " + removals);\r
+ }\r
+ indexer.remove(null, graph, DependenciesRelation.this, resource, model, Dependencies.FIELD_RESOURCE, removals);\r
+ didChange = true;\r
+ }\r
+ if (!additions.isEmpty()) {\r
+ if(DEBUG) {\r
+ for(Object[] os : additions) System.err.println("Adding to index " + model + ": " + Arrays.toString(os));\r
+ }\r
+ //System.out.println(additions.size() + " index insertions");\r
+ indexer.insert(null, graph, DependenciesRelation.this, resource, model, additions);\r
+ didChange = true;\r
+ }\r
+\r
+ }\r
+\r
+ if (didChange)\r
+ // TODO: because this data is ran with\r
+ // ThreadUtils.getBlockingWorkExecutor()\r
+ // fireListeners needs to use peekService,\r
+ // not getService since there is no\r
+ // guarantee that the session isn't being\r
+ // disposed while this method is executing.\r
+ fireListeners(graph, model);\r
+\r
+ } catch (Throwable t) {\r
+ // Just to know if something unexpected happens here.\r
+ Logger.defaultLogError("Dependencies index update failed for model "\r
+ + model + " and relation " + resource + ".", t);\r
+ t.printStackTrace();\r
+\r
+ // NOTE: Last resort: failure to update index\r
+ // properly results in removal of the whole index.\r
+ // This is the only thing that can be done\r
+ // at this point to ensure that the index will\r
+ // return correct results in the future, through\r
+ // complete reinitialization. \r
+ //indexer.removeAll(null, session, DependenciesRelation.this, resource, model);\r
+ } finally {\r
+ Indexing.releaseIndexPending(pending);\r
+ Indexing.clearCaches(model);\r
+ }\r
+ }\r
+ }\r
+\r
+ TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: index update done");\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ };\r
+\r
+ GraphChangeListenerSupport changeSupport = processor.getService(GraphChangeListenerSupport.class);\r
+ changeSupport.addMetadataListener(listener);\r
+\r
+ }\r
+\r
+ trackers++;\r
+\r
+ }\r
+\r
+ private boolean typeNameChanges(ReadGraph graph, IndexedRelations indexer,\r
+ Resource model, final Collection<Pair<String, String>> typeChanges)\r
+ throws DatabaseException {\r
+ if (typeChanges.isEmpty())\r
+ return false;\r
+\r
+ for (Pair<String, String> nr : typeChanges) {\r
+ String query = Dependencies.FIELD_RESOURCE + ":[" + nr.second + " TO " + nr.second + "]";\r
+ //System.out.println("query: " + query);\r
+ List<Map<String, Object>> results = indexer.query(null, query, graph, resource, model, Integer.MAX_VALUE);\r
+ if (results.size() != 1) {\r
+ return true;\r
+ } else {\r
+ Map<String, Object> result = results.get(0);\r
+ if (!ObjectUtils.objectEquals(result.get(Dependencies.FIELD_NAME), nr.first)) {\r
+ return true;\r
+ }\r
+ }\r
+// System.err.println("Type " + nr.first + " was unchanged.");\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public void addListener(RequestProcessor processor, Resource model, Runnable observer) {\r
+ DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);\r
+ store.addListener(model, observer);\r
+ }\r
+\r
+ @Override\r
+ public void removeListener(RequestProcessor processor, Resource model, Runnable observer) {\r
+ DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);\r
+ store.removeListener(model, observer);\r
+ }\r
+\r
+ void fireListeners(RequestProcessor processor, Resource model) {\r
+ DependenciesListenerStore store = processor.getSession().peekService(DependenciesListenerStore.class);\r
+ if (store != null)\r
+ store.fireListeners(model);\r
+ }\r
+\r
+ @Override\r
+ public void reset(RequestProcessor processor, Resource input) {\r
+ if (DEBUG) {\r
+ System.out.println("DependenciesRelation.reset: " + input);\r
+ new Exception("DependenciesRelation.reset(" + listener + ")").printStackTrace(System.out);\r
+ }\r
+ DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);\r
+ store.fireListeners(input);\r
+ }\r
+\r
+ public static void addSubtree(ReadGraph graph, Resource root) throws DatabaseException {\r
+\r
+ Resource indexRoot = graph.syncRequest(new IndexRoot(root));\r
+ addSubtree(graph, indexRoot, root);\r
+\r
+ }\r
+\r
+ public static void addSubtree(ReadGraph graph, Resource indexRoot, Resource subtreeRoot) throws DatabaseException {\r
+ \r
+ DependenciesRelation dr = new DependenciesRelation(graph, indexRoot);\r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+\r
+ ArrayList<Entry> entries = dr.find(graph, subtreeRoot);\r
+ entries.add(new Entry(graph, subtreeRoot));\r
+\r
+ ArrayList<Object[]> result = new ArrayList<Object[]>(entries.size());\r
+ for (Entry entry : entries) {\r
+ result.add(new Object[] { ss.getRandomAccessId(entry.parent), ss.getRandomAccessId(entry.resource), entry.name, entry.types, entry.id });\r
+ }\r
+\r
+ Layer0X L0X = Layer0X.getInstance(graph);\r
+ IndexedRelations indexer = graph.getService(IndexedRelations.class);\r
+ indexer.insert(null, graph, dr, L0X.DependenciesRelation, indexRoot, result);\r
+ \r
+ }\r
+ \r
+}\r