033efc9cc007d859a4ceb458f88284082cc9d302
[simantics/platform.git] / bundles / org.simantics.db.layer0 / src / org / simantics / db / layer0 / genericrelation / DependenciesRelation.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.db.layer0.genericrelation;
13
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;
20 import java.util.Map;
21 import java.util.UUID;
22
23 import org.simantics.databoard.Bindings;
24 import org.simantics.databoard.util.ObjectUtils;
25 import org.simantics.datatypes.literal.GUID;
26 import org.simantics.db.ChangeSet;
27 import org.simantics.db.ChangeSet.StatementChange;
28 import org.simantics.db.MetadataI;
29 import org.simantics.db.ReadGraph;
30 import org.simantics.db.RequestProcessor;
31 import org.simantics.db.Resource;
32 import org.simantics.db.Session;
33 import org.simantics.db.Statement;
34 import org.simantics.db.WriteGraph;
35 import org.simantics.db.common.Indexing;
36 import org.simantics.db.common.changeset.GenericChangeListener;
37 import org.simantics.db.common.request.IndexRoot;
38 import org.simantics.db.common.request.ReadRequest;
39 import org.simantics.db.common.request.SuperTypeString;
40 import org.simantics.db.common.request.TypeString;
41 import org.simantics.db.common.request.UnaryRead;
42 import org.simantics.db.common.utils.NameUtils;
43 import org.simantics.db.event.ChangeListener;
44 import org.simantics.db.exception.DatabaseException;
45 import org.simantics.db.exception.NoSingleResultException;
46 import org.simantics.db.layer0.adapter.GenericRelation;
47 import org.simantics.db.layer0.adapter.GenericRelationIndex;
48 import org.simantics.db.layer0.genericrelation.DependencyChanges.Change;
49 import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentAddition;
50 import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentModification;
51 import org.simantics.db.layer0.genericrelation.DependencyChanges.ComponentRemoval;
52 import org.simantics.db.layer0.genericrelation.DependencyChanges.LinkChange;
53 import org.simantics.db.procedure.SyncContextMultiProcedure;
54 import org.simantics.db.procedure.SyncContextProcedure;
55 import org.simantics.db.service.CollectionSupport;
56 import org.simantics.db.service.DirectQuerySupport;
57 import org.simantics.db.service.GraphChangeListenerSupport;
58 import org.simantics.db.service.ManagementSupport;
59 import org.simantics.db.service.SerialisationSupport;
60 import org.simantics.layer0.Layer0;
61 import org.simantics.operation.Layer0X;
62 import org.simantics.utils.datastructures.Pair;
63 import org.simantics.utils.logging.TimeLogger;
64 import org.slf4j.LoggerFactory;
65
66 public class DependenciesRelation extends UnsupportedRelation implements GenericRelationIndex {
67
68     private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DependenciesRelation.class);
69         private static final boolean DEBUG = false;
70         static final boolean DEBUG_LISTENERS = false;
71         private static final boolean PROFILE = false;
72
73         @SuppressWarnings("unchecked")
74         private final static Pair<String, String>[] fields = new Pair[] {
75                 Pair.make(Dependencies.FIELD_MODEL, "Long"),
76                 Pair.make(Dependencies.FIELD_PARENT, "Long"),
77                 Pair.make(Dependencies.FIELD_RESOURCE, "Long"),
78                 Pair.make(Dependencies.FIELD_NAME, "String"),
79                 Pair.make(Dependencies.FIELD_TYPES, "Text"),
80                 Pair.make(Dependencies.FIELD_GUID, "Text")
81         };
82
83         final Resource resource;
84
85         public DependenciesRelation(ReadGraph graph, Resource resource) {
86                 this.resource = resource;
87                 synchronized(this) {
88                         Session session = graph.getSession();
89                         DependenciesListenerStore store = session.peekService(DependenciesListenerStore.class);
90                         if(store == null) session.registerService(DependenciesListenerStore.class, new DependenciesListenerStore());
91                 }
92         }
93
94         class Process {
95
96                 final ArrayList<Entry> result = new ArrayList<Entry>();
97                 final SyncContextMultiProcedure<Resource, Resource> structure;
98                 final SyncContextProcedure<Entry, String> names;
99                 final SyncContextProcedure<Entry, Resource> type;
100
101                 Process(ReadGraph graph, final Resource resource) throws DatabaseException {
102
103                         final Layer0 L0 = Layer0.getInstance(graph);
104                         final DirectQuerySupport dqs = graph.getService(DirectQuerySupport.class);
105                         final CollectionSupport cs = graph.getService(CollectionSupport.class);
106
107                         names = dqs.compilePossibleRelatedValue(graph, L0.HasName, new SyncContextProcedure<Entry, String>() {
108
109                                 @Override
110                                 public void execute(ReadGraph graph, Entry entry, String name) {
111                                         entry.name = name;
112                                 }
113
114                                 @Override
115                                 public void exception(ReadGraph graph, Throwable throwable) {
116                                         LOGGER.error("Could not compile possible related value for resource {}", resource, throwable);
117                                 }
118
119                         });
120
121                         type = new SyncContextProcedure<Entry, Resource>() {
122
123                                 @Override
124                                 public void execute(ReadGraph graph, Entry entry, Resource type) {
125                                         entry.principalType = type;
126                                 }
127
128                                 @Override
129                                 public void exception(ReadGraph graph, Throwable throwable) {
130                                         LOGGER.error("Could not find type for resource {}", resource, throwable);
131                                 }
132
133                         };
134
135                         structure = dqs.compileForEachObject(graph, L0.ConsistsOf, new SyncContextMultiProcedure<Resource, Resource>() {
136
137                                 @Override
138                                 public void execute(ReadGraph graph, Resource parent, Resource child) {
139                                         // WORKAROUND: don't browse virtual child resources
140                                         if(!child.isPersistent()) return;
141                                         Entry entry = new Entry(parent, child, "", "", "");
142                                         result.add(entry);
143                                         dqs.forEachObjectCompiled(graph, child, child, structure);
144                                         dqs.forPossibleRelatedValueCompiled(graph, child, entry, names);
145                                         dqs.forPossibleDirectType(graph, child, entry, type);
146                                 }
147
148                                 @Override
149                                 public void finished(ReadGraph graph, Resource parent) {
150                                 }
151
152                                 @Override
153                                 public void exception(ReadGraph graph, Throwable throwable) {
154                                     if (throwable instanceof NoSingleResultException) {
155                                         // Ignore
156                                         if (LOGGER.isDebugEnabled())
157                                             LOGGER.debug("Could not compile for resource {}", resource, throwable);
158                                     } else {
159                                         LOGGER.error("Could not compile for resource {}", resource, throwable);
160                                     }
161                                 }
162
163                         });
164
165                         graph.syncRequest(new ReadRequest() {
166
167                                 @Override
168                                 public void run(ReadGraph graph) throws DatabaseException {
169                                         dqs.forEachObjectCompiled(graph, resource, resource, structure);
170                                 }
171
172                         });
173
174             Map<Resource, String> typeStrings = cs.createMap(String.class);
175                         for(Entry e : result) {
176                                 if(e.principalType != null) {
177                                     String typeString = typeStrings.get(e.principalType);
178                                     if(typeString == null) {
179                                         typeString = graph.syncRequest(new SuperTypeString(e.principalType));
180                                         if (typeString.isEmpty()) {
181                                             LOGGER.error("No name for type", new DatabaseException("No name for type " + NameUtils.getURIOrSafeNameInternal(graph, e.resource) + " (" + e.resource + ")"));
182                                         }
183                                         typeStrings.put(e.principalType, typeString);
184                                     }
185                                     e.types = typeString;
186                                 } else {
187                                     e.types = graph.syncRequest(new TypeString(L0, graph.getTypes(e.resource)));
188                                 }
189                                 GUID id = graph.getPossibleRelatedValue(e.resource, L0.identifier, GUID.BINDING);
190                                 if(id != null)
191                                         e.id = id.indexString();
192                                 else 
193                                         e.id = "";
194                         }
195
196                         //SessionGarbageCollection.gc(null, graph.getSession(), false, null);
197                         
198                 }
199
200         }
201
202         public ArrayList<Entry> find(ReadGraph graph, final Resource model) throws DatabaseException {
203                 return new Process(graph, model).result;
204         }
205
206         @Override
207         public GenericRelation select(String bindingPattern, Object[] constants) {
208                 checkSelectionArguments(bindingPattern, constants, new String[] { Dependencies.getBindingPattern() });
209                 final long subjectId = (Long)constants[0];
210                 return new UnsupportedRelation() {
211
212                         @Override
213                         public boolean isRealizable() {
214                                 return true;
215                         }
216
217                         @Override
218                         final public List<Object[]> realize(ReadGraph graph) throws DatabaseException {
219
220                                 long time = System.nanoTime();
221
222                 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
223
224                                 Resource subject = ss.getResource(subjectId); 
225                                 
226                                 Collection<Entry> entries = find(graph, subject);
227
228                                 long time2 = System.nanoTime();
229
230                                 if (PROFILE)
231                                         LOGGER.info("Found " + entries.size() + " dependencies in " + 1e-6 * (time2 - time) + "ms for " + graph.getPossibleURI(subject) + ".");
232
233                                 ArrayList<Object[]> result = new ArrayList<Object[]>();
234                                 for (Entry entry : entries) {
235                                         if(entry.name == null) continue;
236                                         result.add(new Object[] { ss.getRandomAccessId(entry.parent), ss.getRandomAccessId(entry.resource), entry.name, entry.types, entry.id });
237                                 }
238                                 return result;
239
240                         }
241
242                 };
243         }
244
245         @Override
246         public Pair<String, String>[] getFields() {
247                 return fields;
248         }
249
250         @Override
251         public List<Map<String, Object>> query(RequestProcessor session, String search, String bindingPattern, Object[] constants, int maxResultCount) {
252                 if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffff'");
253                 IndexedRelations indexer = session.getService(IndexedRelations.class);
254                 return indexer.query(null, search, session, resource, (Resource)constants[0], maxResultCount);
255         }
256         
257         @Override
258         public List<Resource> queryResources(RequestProcessor session, String search, String bindingPattern, Object[] constants, int maxResultCount) {
259                 if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffff'");
260                 IndexedRelations indexer = session.getService(IndexedRelations.class);
261                 return indexer.queryResources(null, search, session, resource, (Resource)constants[0], maxResultCount);
262         }
263
264         @Override
265         public List<Map<String, Object>> list(RequestProcessor session, String bindingPattern, Object[] constants, int maxResultCount) {
266                 if(!Dependencies.getBindingPattern().equals(bindingPattern)) throw new IllegalArgumentException("DependenciesRelation supports indexing only with 'bfffff'");
267                 IndexedRelations indexer = session.getService(IndexedRelations.class);
268                 return indexer.query(null, null, session, resource, (Resource)constants[0], maxResultCount);
269         }
270
271         public static class DependencyChangesRequest extends UnaryRead<ChangeSet, DependencyChanges> {
272
273                 @SuppressWarnings("unused")
274                 final private static boolean LOG = false;
275
276                 public DependencyChangesRequest(ChangeSet parameter) {
277                         super(parameter);
278                 }
279
280                 @Override
281                 public DependencyChanges perform(ReadGraph graph) throws DatabaseException {
282
283                         DependencyChangesWriter w = new DependencyChangesWriter(graph);
284                         Layer0 l0 = w.l0;
285                         Resource changeInformation = graph.getPossibleResource("http://www.simantics.org/Modeling-1.2/changeInformation/Inverse");
286
287                         for (Resource value : parameter.changedValues()) {
288                                 if(!value.isPersistent()) continue;
289                                 Statement modifiedComponent = graph.getPossibleStatement(value, l0.PropertyOf);
290                                 if (modifiedComponent == null
291                                                 || modifiedComponent.getPredicate().equals(changeInformation))
292                                         continue;
293                                 if (DEBUG) {
294                                     LOGGER.info("+comp modi " + NameUtils.getSafeName(graph, modifiedComponent.getObject(), true));
295                                     LOGGER.info("    +value " + NameUtils.getSafeName(graph, value, true));
296                                 }
297                                 w.addComponentModification(modifiedComponent.getObject());
298                         }
299                         for (Resource value : parameter.changedResources()) {
300                                 // No more info => need to check further
301                                 if(!graph.isImmutable(value))
302                                         w.addComponentModification(value);
303                         }
304                         for (StatementChange change : parameter.changedStatements()) {
305                             if (DEBUG)
306                                 LOGGER.info("-stm " + NameUtils.getSafeName(graph, change.getSubject(), true) + " " + NameUtils.getSafeName(graph, change.getPredicate(), true) + " " + NameUtils.getSafeName(graph, change.getObject(), true));
307                                 Resource subject = change.getSubject();
308                                 Resource predicate = change.getPredicate();
309                                 Resource object = change.getObject();
310                                 if(!object.isPersistent()) continue;
311                                 if (predicate.equals(l0.ConsistsOf)) {
312                                         if (change.isClaim())
313                                                 w.addComponentAddition(subject, object);
314                                         else 
315                                                 w.addComponentRemoval(subject, object);
316                                 } else if (predicate.equals(l0.IsLinkedTo)) {
317                                         w.addLinkChange(subject);
318                                 } else /*if (graph.isSubrelationOf(predicate, l0.DependsOn))*/ {
319                                     if (DEBUG)
320                                         LOGGER.info("-modi " + NameUtils.getSafeName(graph, subject, true));
321                                         w.addComponentModification(subject);
322                                 } 
323                         }
324                         return w.getResult();
325                 }
326
327         };
328
329         private static int trackers = 0;
330         
331         private static ChangeListener listener;
332
333         public static void assertFinishedTracking() {
334             if(trackers != 0) throw new IllegalStateException("Trackers should be 0 (was " + trackers + ")");
335         }
336         
337         @Override
338         public synchronized void untrack(RequestProcessor processor, final Resource model) {
339
340             trackers--;
341             
342             if(trackers < 0) throw new IllegalStateException("Dependency tracking reference count is broken");
343             
344             if(trackers == 0) {
345                 
346                 if(listener == null) throw new IllegalStateException("Dependency tracking was not active");
347             
348                 GraphChangeListenerSupport changeSupport = processor.getService(GraphChangeListenerSupport.class);
349                 changeSupport.removeMetadataListener(listener);
350                 listener = null;
351                         
352             }
353             
354         }
355
356         @Override
357         public synchronized void trackAndIndex(RequestProcessor processor, Resource model__) {
358
359             if(trackers == 0) {
360
361                 if(listener != null) throw new IllegalStateException("Dependency tracking was active");
362
363                 listener = new GenericChangeListener<DependencyChangesRequest, DependencyChanges>() {
364
365                     @Override
366                     public boolean preEventRequest() {
367                         return !Indexing.isDependenciesIndexingDisabled();
368                     }
369
370                     @Override
371                     public void onEvent(ReadGraph graph, MetadataI metadata, DependencyChanges event) throws DatabaseException {
372
373                         TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: starting index update processing");
374
375                         if(DEBUG)
376                             LOGGER.info("Adding metadata " + event + " in revision " + graph.getService(ManagementSupport.class).getHeadRevisionId());
377
378                         WriteGraph w = (WriteGraph)graph;
379                         if(!event.isEmpty())
380                                 w.addMetadata(event);
381
382                         final Session session = graph.getSession();
383                         final IndexedRelations indexer = session.getService(IndexedRelations.class);
384                         Layer0 L0 = Layer0.getInstance(graph);
385                         SerialisationSupport ss = graph.getService(SerialisationSupport.class);
386
387                         for(Map.Entry<Resource, Change[]>  modelEntry : event.get().entrySet()) {
388
389                             final Resource model = modelEntry.getKey();
390                             final Change[] changes = modelEntry.getValue();
391
392                             boolean linkChange = false;
393
394                             Collection<Object[]> _additions = Collections.emptyList();
395                             Collection<Object> _removals = Collections.emptyList();
396                             Collection<Object> _replacementKeys = Collections.emptyList();
397                             Collection<Object[]> _replacementObjects = Collections.emptyList();
398                             Collection<Pair<String, String>> _typeChanges = Collections.emptyList();
399
400                             if(DEBUG) LOGGER.info("MODEL: " + NameUtils.getSafeLabel(graph, model));
401                             //                final Change[] changes = event.get(model);
402                             if(DEBUG) LOGGER.info("  CHANGES: " + Arrays.toString(changes));
403                             if (changes != null) {
404                                 _additions = new ArrayList<Object[]>();
405                                 _removals = new ArrayList<Object>();
406                                 _replacementKeys = new ArrayList<Object>();
407                                 _replacementObjects = new ArrayList<Object[]>();
408                                 _typeChanges = new HashSet<Pair<String, String>>();
409
410                                 for (Change _entry : changes) {
411                                     if (_entry instanceof ComponentAddition) {
412                                         ComponentAddition entry = (ComponentAddition)_entry;
413                                         final String name = graph.getPossibleRelatedValue(entry.component, L0.HasName, Bindings.STRING);
414                                         final GUID id = graph.getPossibleRelatedValue(entry.component, L0.identifier, GUID.BINDING);
415                                         final String types = graph.syncRequest(new TypeString(L0, graph.getTypes(entry.component)));
416                                         if (name != null && types != null) {
417                                                 if(!entry.isValid(graph)) continue;
418                                             Resource parent = graph.getPossibleObject(entry.component, L0.PartOf);
419                                             if (parent != null) {
420                                                 _additions.add(new Object[] { ss.getRandomAccessId(parent), ss.getRandomAccessId(entry.component), name, types, id != null ? id.indexString() : "" });
421                                             } else {
422                                                     //LOGGER.info("resource " + entry.component + ": no parent for entry " + name + " " + types);
423                                             }
424                                         } else {
425                                             //LOGGER.info("resource " + entry.component + ": " + name + " " + types);
426                                         }
427                                     } else if(_entry instanceof ComponentModification) {
428                                         ComponentModification entry = (ComponentModification)_entry;
429                                         final String name = graph.getPossibleRelatedValue(entry.component, L0.HasName, Bindings.STRING);
430                                         final GUID id = graph.getPossibleRelatedValue(entry.component, L0.identifier, GUID.BINDING);
431                                         if(graph.isInstanceOf(entry.component, L0.Type)) {
432                                             SerialisationSupport support = session.getService(SerialisationSupport.class);
433                                             _typeChanges.add(new Pair<String, String>(name, String.valueOf(support.getRandomAccessId((Resource) entry.component))));
434                                         } else {
435                                             final String types = graph.syncRequest(new TypeString(L0, graph.getTypes(entry.component)));
436                                             if (name != null && types != null) {
437                                                 Resource part = graph.getPossibleObject(entry.component, L0.PartOf);
438                                                 if(part != null) {
439                                                     _replacementKeys.add(ss.getRandomAccessId(entry.component));
440                                                     _replacementObjects.add(new Object[] { ss.getRandomAccessId(part), 
441                                                             ss.getRandomAccessId(entry.component), name, types, id != null ? id.indexString() : "" });
442                                                 }
443                                             }
444                                         }
445                                     } else if (_entry instanceof ComponentRemoval) {
446                                         ComponentRemoval entry = (ComponentRemoval)_entry;
447                                         if(!entry.isValid(graph)) continue;
448                                         _removals.add(ss.getRandomAccessId(((ComponentRemoval)_entry).component));
449                                     } else if (_entry instanceof LinkChange) {
450                                         linkChange = true;
451                                     }
452                                 }
453                             }
454
455                             final boolean reset = linkChange || event.hasUnresolved;
456                             //LOGGER.info("dependencies(" + NameUtils.getSafeLabel(graph, model) + "): reset=" + reset + " linkChange=" + linkChange + " unresolved=" + event.hasUnresolved );
457
458                             if (reset || !_additions.isEmpty() || !_removals.isEmpty() || !_replacementKeys.isEmpty() || !_typeChanges.isEmpty()) {
459
460                                 TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: starting index update");
461
462                                 final Collection<Object[]> additions = _additions;
463                                 final Collection<Object> removals = _removals;
464                                 final Collection<Object> replacementKeys = _replacementKeys;
465                                 final Collection<Object[]> replacementObjects = _replacementObjects; 
466                                 final boolean typeNameChanges = typeNameChanges(graph, indexer, model, _typeChanges);
467
468                             final UUID pending = Indexing.makeIndexPending();
469
470                             {
471                                 {
472                                         try {
473                                             boolean didChange = false;
474                                             // Unresolved and linkChanges are not relevant any more
475                                             boolean doReset = typeNameChanges;
476
477                                             if (doReset) {
478
479                                             if(DEBUG) {
480                                                 LOGGER.info("resetIndex " + reset + " " + typeNameChanges);
481                                             }
482
483                                                 indexer.removeAll(null, graph, DependenciesRelation.this, resource, model);
484                                                 didChange = true;
485
486                                             } else {
487
488                                                 if (!replacementKeys.isEmpty() && (replacementKeys.size() == replacementObjects.size())) {
489                                                     if(DEBUG) {
490                                                         LOGGER.info(replacementKeys.size() + " index replacements: " + replacementKeys);
491                                                     }
492                                                     didChange |= indexer.replace(null, graph, DependenciesRelation.this, resource, model, Dependencies.FIELD_RESOURCE, replacementKeys, replacementObjects);
493                                                 }
494                                                 if (!removals.isEmpty()) {
495                                                     if(DEBUG) {
496                                                         LOGGER.info(removals.size() + " index removals: " + removals);
497                                                     }
498                                                     indexer.remove(null, graph, DependenciesRelation.this, resource, model, Dependencies.FIELD_RESOURCE, removals);
499                                                     didChange = true;
500                                                 }
501                                                 if (!additions.isEmpty()) {
502                                                     if(DEBUG) {
503                                                         for(Object[] os : additions) LOGGER.info("Adding to index " + model + ": " + Arrays.toString(os));
504                                                     }
505                                                     //LOGGER.info(additions.size() + " index insertions");
506                                                     indexer.insert(null, graph, DependenciesRelation.this, resource, model, additions);
507                                                     didChange = true;
508                                                 }
509
510                                             }
511
512                                             if (didChange)
513                                                 // TODO: because this data is ran with
514                                                 // ThreadUtils.getBlockingWorkExecutor()
515                                                 // fireListeners needs to use peekService,
516                                                 // not getService since there is no
517                                                 // guarantee that the session isn't being
518                                                 // disposed while this method is executing.
519                                                 fireListeners(graph, model);
520
521                                         } catch (Throwable t) {
522                                             // Just to know if something unexpected happens here.
523                                             LOGGER.error("Dependencies index update failed for model "
524                                                 + model + " and relation " + resource + ".", t);
525
526                                             // NOTE: Last resort: failure to update index
527                                             // properly results in removal of the whole index.
528                                             // This is the only thing that can be done
529                                             // at this point to ensure that the index will
530                                             // return correct results in the future, through
531                                             // complete reinitialization. 
532                                             //indexer.removeAll(null, session, DependenciesRelation.this, resource, model);
533                                         } finally {
534                                             Indexing.releaseIndexPending(pending);
535                                             Indexing.clearCaches(model);
536                                         }
537                                 }
538                             }
539
540                                 TimeLogger.log(DependenciesRelation.class, "trackAndIndex.onEvent: index update done");
541                             }
542                         }
543
544                     }
545
546                 };
547
548                 GraphChangeListenerSupport changeSupport = processor.getService(GraphChangeListenerSupport.class);
549                 changeSupport.addMetadataListener(listener);
550
551             }
552
553             trackers++;
554
555         }
556
557         private boolean typeNameChanges(ReadGraph graph, IndexedRelations indexer,
558                         Resource model, final Collection<Pair<String, String>> typeChanges)
559                         throws DatabaseException {
560                 if (typeChanges.isEmpty())
561                         return false;
562
563                 for (Pair<String, String> nr : typeChanges) {
564                         String query = Dependencies.FIELD_RESOURCE + ":[" + nr.second + " TO " + nr.second + "]";
565                         //LOGGER.info("query: " + query);
566                         List<Map<String, Object>> results = indexer.query(null, query, graph, resource, model, Integer.MAX_VALUE);
567                         if (results.size() != 1) {
568                                 return true;
569                         } else {
570                                 Map<String, Object> result = results.get(0);
571                                 if (!ObjectUtils.objectEquals(result.get(Dependencies.FIELD_NAME), nr.first)) {
572                                         return true;
573                                 }
574                         }
575 //                      LOGGER.info("Type " + nr.first + " was unchanged.");
576                 }
577                 return false;
578         }
579
580         @Override
581         public void addListener(RequestProcessor processor, Resource model, Runnable observer) {
582                 DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);
583                 store.addListener(model, observer);
584         }
585
586         @Override
587         public void removeListener(RequestProcessor processor, Resource model, Runnable observer) {
588                 DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);
589                 store.removeListener(model, observer);
590         }
591
592         void fireListeners(RequestProcessor processor, Resource model) {
593                 DependenciesListenerStore store = processor.getSession().peekService(DependenciesListenerStore.class);
594                 if (store != null)
595                         store.fireListeners(model);
596         }
597
598         @Override
599         public void reset(RequestProcessor processor, Resource input) {
600                 if (DEBUG) {
601                         LOGGER.info("DependenciesRelation.reset: " + input);
602                         new Exception("DependenciesRelation.reset(" + listener + ")").printStackTrace(System.out);
603                 }
604                 DependenciesListenerStore store = processor.getSession().getService(DependenciesListenerStore.class);
605                 store.fireListeners(input);
606         }
607
608         public static void addSubtree(ReadGraph graph, Resource root) throws DatabaseException {
609
610                 Resource indexRoot = graph.syncRequest(new IndexRoot(root));
611                 addSubtree(graph, indexRoot, root);
612
613         }
614
615         public static void addSubtree(ReadGraph graph, Resource indexRoot, Resource subtreeRoot) throws DatabaseException {
616                 
617                 DependenciesRelation dr = new DependenciesRelation(graph, indexRoot);
618         SerialisationSupport ss = graph.getService(SerialisationSupport.class);
619
620         ArrayList<Entry> entries = dr.find(graph, subtreeRoot);
621         entries.add(new Entry(graph, subtreeRoot));
622
623                 ArrayList<Object[]> result = new ArrayList<Object[]>(entries.size());
624                 for (Entry entry : entries) {
625                         result.add(new Object[] { ss.getRandomAccessId(entry.parent), ss.getRandomAccessId(entry.resource), entry.name, entry.types, entry.id });
626                 }
627
628                 Layer0X L0X = Layer0X.getInstance(graph);
629         IndexedRelations indexer = graph.getService(IndexedRelations.class);
630         indexer.insert(null, graph, dr, L0X.DependenciesRelation, indexRoot, result);
631                 
632         }
633         
634 }