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