1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.db.layer0.genericrelation;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashSet;
19 import java.util.List;
22 import java.util.UUID;
24 import org.simantics.databoard.Bindings;
25 import org.simantics.databoard.util.ObjectUtils;
26 import org.simantics.datatypes.literal.GUID;
27 import org.simantics.db.ChangeSet;
28 import org.simantics.db.ChangeSet.StatementChange;
29 import org.simantics.db.MetadataI;
30 import org.simantics.db.ReadGraph;
31 import org.simantics.db.RequestProcessor;
32 import org.simantics.db.Resource;
33 import org.simantics.db.Session;
34 import org.simantics.db.Statement;
35 import org.simantics.db.WriteGraph;
36 import org.simantics.db.common.Indexing;
37 import org.simantics.db.common.changeset.GenericChangeListener;
38 import org.simantics.db.common.request.IndexRoot;
39 import org.simantics.db.common.request.ReadRequest;
40 import org.simantics.db.common.request.SuperTypeString;
41 import org.simantics.db.common.request.TypeString;
42 import org.simantics.db.common.request.UnaryRead;
43 import org.simantics.db.common.utils.NameUtils;
44 import org.simantics.db.event.ChangeListener;
45 import org.simantics.db.exception.DatabaseException;
46 import org.simantics.db.exception.NoSingleResultException;
47 import org.simantics.db.layer0.adapter.GenericRelation;
48 import org.simantics.db.layer0.adapter.GenericRelationIndex;
49 import org.simantics.db.layer0.genericrelation.DependencyChanges.Change;
50 import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentAddition;
51 import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentModification;
52 import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentRemoval;
53 import org.simantics.db.layer0.genericrelation.DependencyChanges.LinkChange;
54 import org.simantics.db.procedure.SyncContextMultiProcedure;
55 import org.simantics.db.procedure.SyncContextProcedure;
56 import org.simantics.db.service.CollectionSupport;
57 import org.simantics.db.service.DirectQuerySupport;
58 import org.simantics.db.service.GraphChangeListenerSupport;
59 import org.simantics.db.service.ManagementSupport;
60 import org.simantics.db.service.SerialisationSupport;
61 import org.simantics.layer0.Layer0;
62 import org.simantics.operation.Layer0X;
63 import org.simantics.utils.datastructures.Pair;
64 import org.simantics.utils.logging.TimeLogger;
65 import org.slf4j.LoggerFactory;
67 public class DependenciesRelation extends UnsupportedRelation implements GenericRelationIndex {
69 private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DependenciesRelation.class);
70 private static final boolean DEBUG = false;
71 static final boolean DEBUG_LISTENERS = false;
72 private static final boolean PROFILE = false;
74 @SuppressWarnings("unchecked")
75 private final static Pair<String, String>[] fields = new Pair[] {
76 Pair.make(Dependencies.FIELD_MODEL, "Long"),
77 Pair.make(Dependencies.FIELD_PARENT, "Long"),
78 Pair.make(Dependencies.FIELD_RESOURCE, "Long"),
79 Pair.make(Dependencies.FIELD_NAME, "String"),
80 Pair.make(Dependencies.FIELD_TYPES, "Text"),
81 Pair.make(Dependencies.FIELD_GUID, "Text"),
82 Pair.make(Dependencies.FIELD_NAME_SEARCH, "Text"),
83 Pair.make(Dependencies.FIELD_TYPES_SEARCH, "Text"),
84 Pair.make(Dependencies.FIELD_TYPE_RESOURCE, "Text")
87 final Resource resource;
89 public DependenciesRelation(ReadGraph graph, Resource resource) {
90 this.resource = resource;
92 Session session = graph.getSession();
93 DependenciesListenerStore store = session.peekService(DependenciesListenerStore.class);
94 if(store == null) session.registerService(DependenciesListenerStore.class, new DependenciesListenerStore());
100 final ArrayList<Entry> result = new ArrayList<Entry>();
101 final SyncContextMultiProcedure<Resource, Resource> structure;
102 final SyncContextProcedure<Entry, String> names;
103 final SyncContextProcedure<Entry, Resource> type;
105 Process(ReadGraph graph, final Resource resource) throws DatabaseException {
107 final Layer0 L0 = Layer0.getInstance(graph);
108 final DirectQuerySupport dqs = graph.getService(DirectQuerySupport.class);
109 final CollectionSupport cs = graph.getService(CollectionSupport.class);
111 names = dqs.compilePossibleRelatedValue(graph, L0.HasName, new SyncContextProcedure<Entry, String>() {
114 public void execute(ReadGraph graph, Entry entry, String name) {
119 public void exception(ReadGraph graph, Throwable throwable) {
120 LOGGER.error("Could not compile possible related value for resource {}", resource, throwable);
125 type = new SyncContextProcedure<Entry, Resource>() {
128 public void execute(ReadGraph graph, Entry entry, Resource type) {
129 entry.principalType = type;
133 public void exception(ReadGraph graph, Throwable throwable) {
134 LOGGER.error("Could not find type for resource {}", resource, throwable);
139 structure = dqs.compileForEachObject(graph, L0.ConsistsOf, new SyncContextMultiProcedure<Resource, Resource>() {
142 public void execute(ReadGraph graph, Resource parent, Resource child) {
143 // WORKAROUND: don't browse virtual child resources
144 if(!child.isPersistent()) return;
145 Entry entry = new Entry(parent, child, "", "", "");
147 dqs.forEachObjectCompiled(graph, child, child, structure);
148 dqs.forPossibleRelatedValueCompiled(graph, child, entry, names);
149 dqs.forPossibleDirectType(graph, child, entry, type);
153 public void finished(ReadGraph graph, Resource parent) {
157 public void exception(ReadGraph graph, Throwable throwable) {
158 if (throwable instanceof NoSingleResultException) {
160 if (LOGGER.isDebugEnabled())
161 LOGGER.debug("Could not compile for resource {}", resource, throwable);
163 LOGGER.error("Could not compile for resource {}", resource, throwable);
169 result.add(new Entry(graph, resource));
171 graph.syncRequest(new ReadRequest() {
174 public void run(ReadGraph graph) throws DatabaseException {
175 dqs.forEachObjectCompiled(graph, resource, resource, structure);
180 Map<Resource, Pair<String, String>> typeStrings = cs.createMap(String.class);
181 for(Entry e : result) {
182 if(e.principalType != null) {
183 Pair<String, String> typeString = typeStrings.get(e.principalType);
184 if(typeString == null) {
185 String superTypeString = graph.syncRequest(new SuperTypeString(e.principalType));
186 if (superTypeString.isEmpty()) {
187 LOGGER.error("No name for type", new DatabaseException("No name for type " + NameUtils.getURIOrSafeNameInternal(graph, e.resource) + " (" + e.resource + ")"));
189 String superTypeIds = IndexQueries.toResourceIdString(e.principalType, graph.getSupertypes(e.principalType));
190 typeString = Pair.make(superTypeString, superTypeIds);
191 typeStrings.put(e.principalType, typeString);
193 e.types = typeString.first;
194 e.typeId = typeString.second;
196 Set<Resource> typeSet = graph.getTypes(e.resource);
197 e.types = graph.syncRequest(new TypeString(L0, typeSet));
198 e.typeId = IndexQueries.toResourceIdString(typeSet);
200 e.id = IndexQueries.idFromGUID( graph.getPossibleRelatedValue(e.resource, L0.identifier, GUID.BINDING) );
203 //SessionGarbageCollection.gc(null, graph.getSession(), false, null);
209 public ArrayList<Entry> find(ReadGraph graph, final Resource model) throws DatabaseException {
210 return new Process(graph, model).result;
214 public GenericRelation select(String bindingPattern, Object[] constants) {
215 checkSelectionArguments(bindingPattern, constants, new String[] { Dependencies.getBindingPattern() });
216 final long subjectId = (Long)constants[0];
217 return new UnsupportedRelation() {
220 public boolean isRealizable() {
225 final public List<Object[]> realize(ReadGraph graph) throws DatabaseException {
227 long time = System.nanoTime();
229 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
231 Resource subject = ss.getResource(subjectId);
233 Collection<Entry> entries = find(graph, subject);
235 long time2 = System.nanoTime();
238 LOGGER.info("Found " + entries.size() + " dependencies in " + 1e-6 * (time2 - time) + "ms for " + graph.getPossibleURI(subject) + ".");
240 ArrayList<Object[]> result = new ArrayList<>();
241 for (Entry entry : entries) {
242 if(entry.name == null) continue;
243 result.add(new Object[] { ss.getRandomAccessId(entry.parent), ss.getRandomAccessId(entry.resource), entry.name, entry.types, entry.id, entry.name, entry.types, entry.typeId });
253 public Pair<String, String>[] getFields() {
258 public List<Map<String, Object>> query(RequestProcessor session, String search, String bindingPattern, Object[] constants, int maxResultCount) {
259 if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffffff'");
260 IndexedRelations indexer = session.getService(IndexedRelations.class);
261 return indexer.query(null, search, session, resource, (Resource)constants[0], maxResultCount);
265 public List<Resource> queryResources(RequestProcessor session, String search, String bindingPattern, Object[] constants, int maxResultCount) {
266 if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffffff'");
267 IndexedRelations indexer = session.getService(IndexedRelations.class);
268 return indexer.queryResources(null, search, session, resource, (Resource)constants[0], maxResultCount);
272 public List<Map<String, Object>> list(RequestProcessor session, String bindingPattern, Object[] constants, int maxResultCount) {
273 if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffffff'");
274 IndexedRelations indexer = session.getService(IndexedRelations.class);
275 return indexer.query(null, null, session, resource, (Resource)constants[0], maxResultCount);
278 public static class DependencyChangesRequest extends UnaryRead<ChangeSet, DependencyChanges> {
280 @SuppressWarnings("unused")
281 final private static boolean LOG = false;
283 public DependencyChangesRequest(ChangeSet parameter) {
288 public DependencyChanges perform(ReadGraph graph) throws DatabaseException {
290 DependencyChangesWriter w = new DependencyChangesWriter(graph);
292 Resource changeInformation = graph.getPossibleResource("http://www.simantics.org/Modeling-1.2/changeInformation/Inverse");
294 for (Resource value : parameter.changedValues()) {
295 if(!value.isPersistent()) continue;
296 Statement modifiedComponent = graph.getPossibleStatement(value, l0.PropertyOf);
297 if (modifiedComponent == null
298 || modifiedComponent.getPredicate().equals(changeInformation))
301 LOGGER.info("+comp modi " + NameUtils.getSafeName(graph, modifiedComponent.getObject(), true));
302 LOGGER.info(" +value " + NameUtils.getSafeName(graph, value, true));
304 w.addComponentModification(modifiedComponent.getObject());
306 for (Resource value : parameter.changedResources()) {
307 // No more info => need to check further
308 if(!graph.isImmutable(value))
309 w.addComponentModification(value);
311 for (StatementChange change : parameter.changedStatements()) {
313 LOGGER.info("-stm " + NameUtils.getSafeName(graph, change.getSubject(), true) + " " + NameUtils.getSafeName(graph, change.getPredicate(), true) + " " + NameUtils.getSafeName(graph, change.getObject(), true));
314 Resource subject = change.getSubject();
315 Resource predicate = change.getPredicate();
316 Resource object = change.getObject();
317 if(!object.isPersistent()) continue;
318 if (predicate.equals(l0.ConsistsOf)) {
319 if (change.isClaim())
320 w.addComponentAddition(subject, object);
322 w.addComponentRemoval(subject, object);
323 } else if (predicate.equals(l0.IsLinkedTo)) {
324 w.addLinkChange(subject);
325 } else /*if (graph.isSubrelationOf(predicate, l0.DependsOn))*/ {
327 LOGGER.info("-modi " + NameUtils.getSafeName(graph, subject, true));
328 w.addComponentModification(subject);
331 return w.getResult();
336 private static int trackers = 0;
338 private static ChangeListener listener;
340 public static void assertFinishedTracking() {
341 if(trackers != 0) throw new IllegalStateException("Trackers should be 0 (was " + trackers + ")");
345 public synchronized void untrack(RequestProcessor processor, final Resource model) {
349 if(trackers < 0) throw new IllegalStateException("Dependency tracking reference count is broken");
353 if(listener == null) throw new IllegalStateException("Dependency tracking was not active");
355 GraphChangeListenerSupport changeSupport = processor.getService(GraphChangeListenerSupport.class);
356 changeSupport.removeMetadataListener(listener);
364 public synchronized void trackAndIndex(RequestProcessor processor, Resource model__) {
368 if(listener != null) throw new IllegalStateException("Dependency tracking was active");
370 listener = new GenericChangeListener<DependencyChangesRequest, DependencyChanges>() {
373 public boolean preEventRequest() {
374 return !Indexing.isDependenciesIndexingDisabled();
378 public void onEvent(ReadGraph graph, MetadataI metadata, DependencyChanges event) throws DatabaseException {
380 TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: starting index update processing");
383 LOGGER.info("Adding metadata " + event + " in revision " + graph.getService(ManagementSupport.class).getHeadRevisionId());
385 WriteGraph w = (WriteGraph)graph;
387 w.addMetadata(event);
389 final Session session = graph.getSession();
390 final IndexedRelations indexer = session.getService(IndexedRelations.class);
391 Layer0 L0 = Layer0.getInstance(graph);
392 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
394 for(Map.Entry<Resource, Change[]> modelEntry : event.get().entrySet()) {
396 final Resource model = modelEntry.getKey();
397 final Change[] changes = modelEntry.getValue();
399 boolean linkChange = false;
401 Collection<Object[]> _additions = Collections.emptyList();
402 Collection<Object> _removals = Collections.emptyList();
403 Collection<Object> _replacementKeys = Collections.emptyList();
404 Collection<Object[]> _replacementObjects = Collections.emptyList();
405 Collection<Pair<String, String>> _typeChanges = Collections.emptyList();
407 if(DEBUG) LOGGER.info("MODEL: " + NameUtils.getSafeLabel(graph, model));
408 if (changes != null) {
410 LOGGER.info(" CHANGE COUNT: " + changes.length);
411 for (Change c : changes)
412 LOGGER.info(" CHANGE: " + c.toString(graph));
414 _additions = new ArrayList<>();
415 _removals = new ArrayList<>();
416 _replacementKeys = new ArrayList<>();
417 _replacementObjects = new ArrayList<>();
418 _typeChanges = new HashSet<>();
420 for (Change _entry : changes) {
421 if (_entry instanceof ComponentAddition) {
422 ComponentAddition entry = (ComponentAddition)_entry;
423 final String name = graph.getPossibleRelatedValue(entry.component, L0.HasName, Bindings.STRING);
424 Set<Resource> typeSet = graph.getTypes(entry.component);
425 if (name != null && typeSet != null) {
426 if (!entry.isValid(graph))
428 Resource parent = graph.getPossibleObject(entry.component, L0.PartOf);
429 if (parent != null) {
430 final GUID id = graph.getPossibleRelatedValue(entry.component, L0.identifier, GUID.BINDING);
431 final String types = graph.syncRequest(new TypeString(L0, typeSet));
432 final String typeIds = IndexQueries.toResourceIdString(typeSet);
433 _additions.add(new Object[] { ss.getRandomAccessId(parent), ss.getRandomAccessId(entry.component), name, types, IndexQueries.idFromGUID(id), name, types, typeIds});
435 //LOGGER.info("resource " + entry.component + ": no parent for entry " + name + " " + types);
438 //LOGGER.info("resource " + entry.component + ": " + name + " " + types);
440 } else if(_entry instanceof ComponentModification) {
441 ComponentModification entry = (ComponentModification)_entry;
442 final String name = graph.getPossibleRelatedValue(entry.component, L0.HasName, Bindings.STRING);
443 if(graph.isInstanceOf(entry.component, L0.Type)) {
444 SerialisationSupport support = session.getService(SerialisationSupport.class);
445 _typeChanges.add(new Pair<String, String>(name, String.valueOf(support.getRandomAccessId((Resource) entry.component))));
447 Set<Resource> typeSet = graph.getTypes(entry.component);
448 if (name != null && !typeSet.isEmpty()) {
449 Resource part = graph.getPossibleObject(entry.component, L0.PartOf);
451 final GUID id = graph.getPossibleRelatedValue(entry.component, L0.identifier, GUID.BINDING);
452 final String types = graph.syncRequest(new TypeString(L0, typeSet));
453 final String typeIds = IndexQueries.toResourceIdString(typeSet);
454 _replacementKeys.add(ss.getRandomAccessId(entry.component));
455 _replacementObjects.add(new Object[] { ss.getRandomAccessId(part),
456 ss.getRandomAccessId(entry.component), name, types, IndexQueries.idFromGUID(id), name, types, typeIds});
460 } else if (_entry instanceof ComponentRemoval) {
461 ComponentRemoval entry = (ComponentRemoval)_entry;
462 if(!entry.isValid(graph)) continue;
463 _removals.add(ss.getRandomAccessId(((ComponentRemoval)_entry).component));
464 } else if (_entry instanceof LinkChange) {
470 final boolean reset = linkChange || event.hasUnresolved;
471 //LOGGER.info("dependencies(" + NameUtils.getSafeLabel(graph, model) + "): reset=" + reset + " linkChange=" + linkChange + " unresolved=" + event.hasUnresolved );
473 if (reset || !_additions.isEmpty() || !_removals.isEmpty() || !_replacementKeys.isEmpty() || !_typeChanges.isEmpty()) {
475 TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: starting index update");
477 final Collection<Object[]> additions = _additions;
478 final Collection<Object> removals = _removals;
479 final Collection<Object> replacementKeys = _replacementKeys;
480 final Collection<Object[]> replacementObjects = _replacementObjects;
481 final boolean typeNameChanges = typeNameChanges(graph, indexer, model, _typeChanges);
483 final UUID pending = Indexing.makeIndexPending();
485 boolean didChange = false;
486 // Unresolved and linkChanges are not relevant any more
487 boolean doReset = typeNameChanges;
492 LOGGER.info("resetIndex " + reset + " " + typeNameChanges);
495 indexer.removeAll(null, graph, DependenciesRelation.this, resource, model);
500 if (!replacementKeys.isEmpty() && (replacementKeys.size() == replacementObjects.size())) {
502 LOGGER.info(replacementKeys.size() + " index replacements: " + replacementKeys);
504 didChange |= indexer.replace(null, graph, DependenciesRelation.this, resource, model, Dependencies.FIELD_RESOURCE, replacementKeys, replacementObjects);
506 if (!removals.isEmpty()) {
508 LOGGER.info(removals.size() + " index removals: " + removals);
510 indexer.remove(null, graph, DependenciesRelation.this, resource, model, Dependencies.FIELD_RESOURCE, removals);
513 if (!additions.isEmpty()) {
515 for(Object[] os : additions) LOGGER.info("Adding to index " + model + ": " + Arrays.toString(os));
517 //LOGGER.info(additions.size() + " index insertions");
518 indexer.insert(null, graph, DependenciesRelation.this, resource, model, additions);
525 // TODO: because this data is ran with
526 // ThreadUtils.getBlockingWorkExecutor()
527 // fireListeners needs to use peekService,
528 // not getService since there is no
529 // guarantee that the session isn't being
530 // disposed while this method is executing.
531 fireListeners(graph, model);
533 } catch (Throwable t) {
534 // Just to know if something unexpected happens here.
535 LOGGER.error("Dependencies index update failed for model "
536 + model + " and relation " + resource + ".", t);
538 // NOTE: Last resort: failure to update index
539 // properly results in removal of the whole index.
540 // This is the only thing that can be done
541 // at this point to ensure that the index will
542 // return correct results in the future, through
543 // complete reinitialization.
544 //indexer.removeAll(null, session, DependenciesRelation.this, resource, model);
546 Indexing.releaseIndexPending(pending);
547 Indexing.clearCaches(model);
550 TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: index update done");
558 GraphChangeListenerSupport changeSupport = processor.getService(GraphChangeListenerSupport.class);
559 changeSupport.addMetadataListener(listener);
567 private boolean typeNameChanges(ReadGraph graph, IndexedRelations indexer,
568 Resource model, final Collection<Pair<String, String>> typeChanges)
569 throws DatabaseException {
570 if (typeChanges.isEmpty())
573 for (Pair<String, String> nr : typeChanges) {
574 String query = Dependencies.FIELD_RESOURCE + ":[" + nr.second + " TO " + nr.second + "]";
575 //LOGGER.info("query: " + query);
576 List<Map<String, Object>> results = indexer.query(null, query, graph, resource, model, Integer.MAX_VALUE);
577 if (results.size() != 1) {
580 Map<String, Object> result = results.get(0);
581 if (!ObjectUtils.objectEquals(result.get(Dependencies.FIELD_NAME), nr.first)) {
585 // LOGGER.info("Type " + nr.first + " was unchanged.");
591 public void addListener(RequestProcessor processor, Resource model, Runnable observer) {
592 DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);
593 store.addListener(model, observer);
597 public void removeListener(RequestProcessor processor, Resource model, Runnable observer) {
598 DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);
599 store.removeListener(model, observer);
602 void fireListeners(RequestProcessor processor, Resource model) {
603 DependenciesListenerStore store = processor.getSession().peekService(DependenciesListenerStore.class);
605 store.fireListeners(model);
609 public void reset(RequestProcessor processor, Resource input) {
611 LOGGER.info("DependenciesRelation.reset: " + input);
612 new Exception("DependenciesRelation.reset(" + listener + ")").printStackTrace(System.out);
614 DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);
615 store.fireListeners(input);
618 public static void addSubtree(ReadGraph graph, Resource root) throws DatabaseException {
620 Resource indexRoot = graph.syncRequest(new IndexRoot(root));
621 addSubtree(graph, indexRoot, root);
625 public static void addSubtree(ReadGraph graph, Resource indexRoot, Resource subtreeRoot) throws DatabaseException {
627 DependenciesRelation dr = new DependenciesRelation(graph, indexRoot);
628 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
630 ArrayList<Entry> entries = dr.find(graph, subtreeRoot);
631 entries.add(new Entry(graph, subtreeRoot));
633 ArrayList<Object[]> result = new ArrayList<Object[]>(entries.size());
634 for (Entry entry : entries) {
635 result.add(new Object[] { ss.getRandomAccessId(entry.parent), ss.getRandomAccessId(entry.resource), entry.name, entry.types, entry.id, entry.name, entry.types, entry.typeId });
638 Layer0X L0X = Layer0X.getInstance(graph);
639 IndexedRelations indexer = graph.getService(IndexedRelations.class);
640 indexer.insert(null, graph, dr, L0X.DependenciesRelation, indexRoot, result);