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