KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > access > DataDomain


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.access;
57
58 import java.util.ArrayList JavaDoc;
59 import java.util.Collection JavaDoc;
60 import java.util.Collections JavaDoc;
61 import java.util.HashMap JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.List JavaDoc;
64 import java.util.Map JavaDoc;
65 import java.util.TreeMap JavaDoc;
66
67 import org.apache.commons.lang.builder.ToStringBuilder;
68 import org.apache.log4j.Logger;
69 import org.objectstyle.cayenne.CayenneRuntimeException;
70 import org.objectstyle.cayenne.ObjectContext;
71 import org.objectstyle.cayenne.access.util.PrimaryKeyHelper;
72 import org.objectstyle.cayenne.graph.GraphChangeHandler;
73 import org.objectstyle.cayenne.map.DataMap;
74 import org.objectstyle.cayenne.map.EntityResolver;
75 import org.objectstyle.cayenne.query.Query;
76 import org.objectstyle.cayenne.query.QueryChain;
77 import org.objectstyle.cayenne.query.QueryExecutionPlan;
78 import org.objectstyle.cayenne.query.QueryRouter;
79
80 /**
81  * DataDomain performs query routing functions in Cayenne. DataDomain creates single data
82  * source abstraction hiding multiple physical data sources from the user. When a child
83  * DataContext sends a query to the DataDomain, it is transparently routed to an
84  * appropriate DataNode.
85  * <p>
86  * <i>For more information see <a HREF="../../../../../../userguide/index.html"
87  * target="_top">Cayenne User Guide. </a> </i>
88  * </p>
89  *
90  * @author Andrei Adamchik
91  */

92 public class DataDomain implements QueryEngine, PersistenceContext {
93
94     private static Logger logObj = Logger.getLogger(DataDomain.class);
95
96     public static final String JavaDoc SHARED_CACHE_ENABLED_PROPERTY = "cayenne.DataDomain.sharedCache";
97     public static final boolean SHARED_CACHE_ENABLED_DEFAULT = true;
98
99     public static final String JavaDoc VALIDATING_OBJECTS_ON_COMMIT_PROPERTY = "cayenne.DataDomain.validatingObjectsOnCommit";
100     public static final boolean VALIDATING_OBJECTS_ON_COMMIT_DEFAULT = true;
101
102     public static final String JavaDoc USING_EXTERNAL_TRANSACTIONS_PROPERTY = "cayenne.DataDomain.usingExternalTransactions";
103     public static final boolean USING_EXTERNAL_TRANSACTIONS_DEFAULT = false;
104
105     /** Stores mapping of data nodes to DataNode name keys. */
106     protected Map JavaDoc nodes = Collections.synchronizedMap(new TreeMap JavaDoc());
107     protected Map JavaDoc nodesByDataMapName = Collections.synchronizedMap(new HashMap JavaDoc());
108     protected Collection JavaDoc nodesRef = Collections.unmodifiableCollection(nodes.values());
109
110     /**
111      * Properties configured for DataDomain. These include properties of the DataRowStore
112      * and remote notifications.
113      */

114     protected Map JavaDoc properties = Collections.synchronizedMap(new TreeMap JavaDoc());
115
116     protected org.objectstyle.cayenne.map.EntityResolver entityResolver;
117     protected PrimaryKeyHelper primaryKeyHelper;
118     protected DataRowStore sharedSnapshotCache;
119     protected TransactionDelegate transactionDelegate;
120     protected String JavaDoc name;
121
122     // these are initializable from properties...
123
protected boolean sharedCacheEnabled;
124     protected boolean validatingObjectsOnCommit;
125     protected boolean usingExternalTransactions;
126
127     /**
128      * Creates a DataDomain and assigns it a name.
129      */

130     public DataDomain(String JavaDoc name) {
131         setName(name);
132         resetProperties();
133     }
134
135     /**
136      * Creates new DataDomain.
137      *
138      * @param name DataDomain name. Domain can be located using its name in the
139      * Configuration object.
140      * @param properties A Map containing domain configuration properties.
141      */

142     public DataDomain(String JavaDoc name, Map JavaDoc properties) {
143         setName(name);
144         initWithProperties(properties);
145     }
146
147     /**
148      * @since 1.1
149      */

150     protected void resetProperties() {
151         if (properties != null) {
152             properties.clear();
153         }
154
155         sharedCacheEnabled = SHARED_CACHE_ENABLED_DEFAULT;
156         validatingObjectsOnCommit = VALIDATING_OBJECTS_ON_COMMIT_DEFAULT;
157         usingExternalTransactions = USING_EXTERNAL_TRANSACTIONS_DEFAULT;
158     }
159
160     /**
161      * Reinitializes domain state with a new set of properties.
162      *
163      * @since 1.1
164      */

165     public void initWithProperties(Map JavaDoc properties) {
166         // create map with predictable modification and synchronization behavior
167
Map JavaDoc localMap = new HashMap JavaDoc();
168         if (properties != null) {
169             localMap.putAll(properties);
170         }
171
172         this.properties = localMap;
173
174         Object JavaDoc sharedCacheEnabled = localMap.get(SHARED_CACHE_ENABLED_PROPERTY);
175         Object JavaDoc validatingObjectsOnCommit = localMap
176                 .get(VALIDATING_OBJECTS_ON_COMMIT_PROPERTY);
177         Object JavaDoc usingExternalTransactions = localMap
178                 .get(USING_EXTERNAL_TRANSACTIONS_PROPERTY);
179
180         if (logObj.isDebugEnabled()) {
181             logObj.debug("DataDomain property "
182                     + SHARED_CACHE_ENABLED_PROPERTY
183                     + " = "
184                     + sharedCacheEnabled);
185             logObj.debug("DataDomain property "
186                     + VALIDATING_OBJECTS_ON_COMMIT_PROPERTY
187                     + " = "
188                     + validatingObjectsOnCommit);
189             logObj.debug("DataDomain property "
190                     + USING_EXTERNAL_TRANSACTIONS_PROPERTY
191                     + " = "
192                     + usingExternalTransactions);
193         }
194
195         // init ivars from properties
196
this.sharedCacheEnabled = (sharedCacheEnabled != null)
197                 ? "true".equalsIgnoreCase(sharedCacheEnabled.toString())
198                 : SHARED_CACHE_ENABLED_DEFAULT;
199         this.validatingObjectsOnCommit = (validatingObjectsOnCommit != null)
200                 ? "true".equalsIgnoreCase(validatingObjectsOnCommit.toString())
201                 : VALIDATING_OBJECTS_ON_COMMIT_DEFAULT;
202         this.usingExternalTransactions = (usingExternalTransactions != null)
203                 ? "true".equalsIgnoreCase(usingExternalTransactions.toString())
204                 : USING_EXTERNAL_TRANSACTIONS_DEFAULT;
205     }
206
207     /** Returns "name" property value. */
208     public String JavaDoc getName() {
209         return name;
210     }
211
212     /** Sets "name" property to a new value. */
213     public synchronized void setName(String JavaDoc name) {
214         this.name = name;
215         if (sharedSnapshotCache != null) {
216             this.sharedSnapshotCache.setName(name);
217         }
218     }
219
220     /**
221      * Returns <code>true</code> if DataContexts produced by this DataDomain are using
222      * shared DataRowStore. Returns <code>false</code> if each DataContext would work
223      * with its own DataRowStore.
224      */

225     public boolean isSharedCacheEnabled() {
226         return sharedCacheEnabled;
227     }
228
229     public void setSharedCacheEnabled(boolean sharedCacheEnabled) {
230         this.sharedCacheEnabled = sharedCacheEnabled;
231     }
232
233     /**
234      * Returns whether child DataContexts default behavior is to perform object validation
235      * before commit is executed.
236      *
237      * @since 1.1
238      */

239     public boolean isValidatingObjectsOnCommit() {
240         return validatingObjectsOnCommit;
241     }
242
243     /**
244      * Sets the property defining whether child DataContexts should perform object
245      * validation before commit is executed.
246      *
247      * @since 1.1
248      */

249     public void setValidatingObjectsOnCommit(boolean flag) {
250         this.validatingObjectsOnCommit = flag;
251     }
252
253     /**
254      * Returns whether this DataDomain should internally commit all transactions, or let
255      * container do that.
256      *
257      * @since 1.1
258      */

259     public boolean isUsingExternalTransactions() {
260         return usingExternalTransactions;
261     }
262
263     /**
264      * Sets a property defining whether this DataDomain should internally commit all
265      * transactions, or let container do that.
266      *
267      * @since 1.1
268      */

269     public void setUsingExternalTransactions(boolean flag) {
270         this.usingExternalTransactions = flag;
271     }
272
273     /**
274      * @since 1.1
275      * @return a Map of properties for this DataDomain. There is no guarantees of specific
276      * synchronization behavior of this map.
277      */

278     public Map JavaDoc getProperties() {
279         return properties;
280     }
281
282     /**
283      * @since 1.1
284      * @return TransactionDelegate associated with this DataDomain, or null if no delegate
285      * exist.
286      */

287     public TransactionDelegate getTransactionDelegate() {
288         return transactionDelegate;
289     }
290
291     /**
292      * Initializes TransactionDelegate used by all DataContexts associated with this
293      * DataDomain.
294      *
295      * @since 1.1
296      */

297     public void setTransactionDelegate(TransactionDelegate transactionDelegate) {
298         this.transactionDelegate = transactionDelegate;
299     }
300
301     /**
302      * Returns snapshots cache for this DataDomain, lazily initializing it on the first
303      * call.
304      */

305     public synchronized DataRowStore getSharedSnapshotCache() {
306         if (sharedSnapshotCache == null) {
307             this.sharedSnapshotCache = new DataRowStore(name, properties);
308         }
309
310         return sharedSnapshotCache;
311     }
312
313     public synchronized void setSharedSnapshotCache(DataRowStore snapshotCache) {
314         if (this.sharedSnapshotCache != snapshotCache) {
315             if (this.sharedSnapshotCache != null) {
316                 this.sharedSnapshotCache.shutdown();
317             }
318             this.sharedSnapshotCache = snapshotCache;
319         }
320     }
321
322     /** Registers new DataMap with this domain. */
323     public void addMap(DataMap map) {
324         getEntityResolver().addDataMap(map);
325     }
326
327     /** Returns DataMap matching <code>name</code> parameter. */
328     public DataMap getMap(String JavaDoc mapName) {
329         return getEntityResolver().getDataMap(mapName);
330     }
331
332     /**
333      * Removes named DataMap from this DataDomain and any underlying DataNodes that
334      * include it.
335      */

336     public synchronized void removeMap(String JavaDoc mapName) {
337         DataMap map = getMap(mapName);
338         if (map == null) {
339             return;
340         }
341
342         // remove from data nodes
343
Iterator JavaDoc it = nodes.values().iterator();
344         while (it.hasNext()) {
345             DataNode node = (DataNode) it.next();
346             node.removeDataMap(mapName);
347         }
348
349         // remove from EntityResolver
350
getEntityResolver().removeDataMap(map);
351
352         // reindex nodes to remove references on removed map entities
353
reindexNodes();
354     }
355
356     /**
357      * Removes a DataNode from DataDomain. Any maps previously associated with this node
358      * within domain will still be kept around, however they wan't be mapped to any node.
359      */

360     public synchronized void removeDataNode(String JavaDoc nodeName) {
361         DataNode removed = (DataNode) nodes.remove(nodeName);
362         if (removed != null) {
363
364             removed.setEntityResolver(null);
365
366             Iterator JavaDoc it = nodesByDataMapName.values().iterator();
367             while (it.hasNext()) {
368                 if (it.next() == removed) {
369                     it.remove();
370                 }
371             }
372         }
373     }
374
375     /**
376      * Returns a collection of registered DataMaps.
377      */

378     public Collection JavaDoc getDataMaps() {
379         return getEntityResolver().getDataMaps();
380     }
381
382     /**
383      * Returns an unmodifiable collection of DataNodes associated with this domain.
384      */

385     public Collection JavaDoc getDataNodes() {
386         return nodesRef;
387     }
388
389     /**
390      * Closes all data nodes, removes them from the list of available nodes.
391      */

392     public void reset() {
393         synchronized (nodes) {
394             nodes.clear();
395             nodesByDataMapName.clear();
396
397             if (entityResolver != null) {
398                 entityResolver.clearCache();
399                 entityResolver = null;
400             }
401         }
402     }
403
404     /**
405      * Clears the list of internal DataMaps. In most cases it is wise to call "reset"
406      * before doing that.
407      */

408     public void clearDataMaps() {
409         getEntityResolver().setDataMaps(Collections.EMPTY_LIST);
410     }
411
412     /**
413      * Adds new DataNode.
414      */

415     public synchronized void addNode(DataNode node) {
416
417         // add node to name->node map
418
nodes.put(node.getName(), node);
419         node.setEntityResolver(this.getEntityResolver());
420
421         // add node to "ent name->node" map
422
Iterator JavaDoc nodeMaps = node.getDataMaps().iterator();
423         while (nodeMaps.hasNext()) {
424             DataMap map = (DataMap) nodeMaps.next();
425             this.addMap(map);
426             this.nodesByDataMapName.put(map.getName(), node);
427         }
428     }
429
430     /**
431      * Creates and returns a new DataContext. If this DataDomain is configured to use
432      * shared cache, returned DataContext will use shared cache as well. Otherwise a new
433      * instance of DataRowStore will be used as its local cache.
434      */

435     public DataContext createDataContext() {
436         return createDataContext(isSharedCacheEnabled());
437     }
438
439     /**
440      * Creates a new DataContext.
441      *
442      * @param useSharedCache determines whether resulting DataContext should use shared
443      * vs. local cache. This setting overrides default behavior configured for
444      * this DataDomain via {@link #SHARED_CACHE_ENABLED_PROPERTY}.
445      * @since 1.1
446      */

447     public DataContext createDataContext(boolean useSharedCache) {
448         // for new dataRowStores use the same name for all stores
449
// it makes it easier to track the event subject
450
DataRowStore snapshotCache = (useSharedCache)
451                 ? getSharedSnapshotCache()
452                 : new DataRowStore(name, properties);
453
454         DataContext context = new DataContext(this, new ObjectStore(snapshotCache));
455         context.setValidatingObjectsOnCommit(isValidatingObjectsOnCommit());
456         return context;
457     }
458
459     /**
460      * Creates and returns a new inactive transaction. If there is a TransactionDelegate,
461      * adds the delegate to the newly created Transaction. Behavior of the returned
462      * Transaction depends on "usingInternalTransactions" property setting.
463      *
464      * @since 1.1
465      */

466     public Transaction createTransaction() {
467         return (isUsingExternalTransactions()) ? Transaction
468                 .externalTransaction(getTransactionDelegate()) : Transaction
469                 .internalTransaction(getTransactionDelegate());
470     }
471
472     /**
473      * Returns registered DataNode whose name matches <code>name</code> parameter.
474      */

475     public DataNode getNode(String JavaDoc nodeName) {
476         return (DataNode) nodes.get(nodeName);
477     }
478
479     /**
480      * Updates internal index of DataNodes stored by the entity name.
481      */

482     public synchronized void reindexNodes() {
483         nodesByDataMapName.clear();
484
485         Iterator JavaDoc nodes = this.getDataNodes().iterator();
486         while (nodes.hasNext()) {
487             DataNode node = (DataNode) nodes.next();
488             Iterator JavaDoc nodeMaps = node.getDataMaps().iterator();
489             while (nodeMaps.hasNext()) {
490                 DataMap map = (DataMap) nodeMaps.next();
491                 addMap(map);
492                 nodesByDataMapName.put(map.getName(), node);
493             }
494         }
495     }
496
497     /**
498      * Returns a DataNode that should handle queries for all entities in a DataMap.
499      *
500      * @since 1.1
501      */

502     public DataNode lookupDataNode(DataMap map) {
503         synchronized (nodesByDataMapName) {
504             DataNode node = (DataNode) nodesByDataMapName.get(map.getName());
505             if (node == null) {
506                 reindexNodes();
507                 return (DataNode) nodesByDataMapName.get(map.getName());
508             }
509             else {
510                 return node;
511             }
512         }
513     }
514
515     /**
516      * Inspects the queries, sending them to appropriate DataNodes for execution. May
517      * modify transaction settings on the OperationObserver.
518      *
519      * @since 1.1
520      */

521     public void performQueries(
522             Collection JavaDoc queries,
523             OperationObserver resultConsumer,
524             Transaction transaction) {
525
526         if (queries.isEmpty()) {
527             return;
528         }
529
530         // transaction is passed to us, so assume we are already wrapped in it...
531
performQuery(new QueryChain(queries), resultConsumer, transaction);
532     }
533
534     /**
535      * Wraps queries in an internal transaction and sends them to appropriate DataNodes
536      * for execution.
537      */

538     public void performQueries(Collection JavaDoc queries, OperationObserver observer) {
539         if (queries.isEmpty()) {
540             return;
541         }
542
543         performQuery(new QueryChain(queries), observer);
544     }
545
546     public EntityResolver getEntityResolver() {
547         if (entityResolver == null) {
548             createEntityResolver();
549         }
550
551         return entityResolver;
552     }
553
554     /**
555      * Sets EntityResolver. If not set explicitly, DataDomain creates a default
556      * EntityResolver internally on demand.
557      *
558      * @since 1.1
559      */

560     public void setEntityResolver(EntityResolver entityResolver) {
561         this.entityResolver = entityResolver;
562     }
563
564     // creates default entity resolver if there is none set yet
565
private synchronized void createEntityResolver() {
566         if (entityResolver == null) {
567             // entity resolver will be self-indexing as we add all our maps
568
// to it as they are added to the DataDomain
569
entityResolver = new org.objectstyle.cayenne.map.EntityResolver();
570         }
571     }
572
573     // creates default PrimaryKeyHelper
574
private void createKeyGenerator() {
575         primaryKeyHelper = new PrimaryKeyHelper(this);
576     }
577
578     /**
579      * @return PrimaryKeyHelper
580      */

581     public synchronized PrimaryKeyHelper getPrimaryKeyHelper() {
582         // TODO instead of on the spot generation, we can
583
// use lazy initialization features similar to DefaultSorter
584
if (primaryKeyHelper == null) {
585             createKeyGenerator();
586         }
587
588         return primaryKeyHelper;
589     }
590
591     /**
592      * Shutdowns all owned data nodes. Invokes DataNode.shutdown().
593      */

594     public void shutdown() {
595         this.sharedSnapshotCache.shutdown();
596
597         Collection JavaDoc dataNodes = getDataNodes();
598         for (Iterator JavaDoc i = dataNodes.iterator(); i.hasNext();) {
599             DataNode node = (DataNode) i.next();
600             try {
601                 node.shutdown();
602             }
603             catch (Exception JavaDoc ex) {
604             }
605         }
606     }
607
608     public String JavaDoc toString() {
609         return new ToStringBuilder(this).append("name", name).toString();
610     }
611
612     // **** new 1.2 PersistenceContext methods:
613
// =======================================
614

615     /**
616      * Commits changes in an ObjectContext.
617      *
618      * @since 1.2
619      */

620     public void commitChangesInContext(ObjectContext context, GraphChangeHandler commitChangeCallback) {
621         new DataDomainCommitAction(this).commit(context, commitChangeCallback);
622     }
623
624     /**
625      * Routes and executes a given query, wrapping it in a transaction. Note that query
626      * resolution phase is not done at this level and is a responsibility of the caller.
627      *
628      * @since 1.2
629      */

630     public void performQuery(QueryExecutionPlan query, OperationObserver resultConsumer) {
631
632         Transaction transaction = (resultConsumer.isIteratedResult()) ? Transaction
633                 .noTransaction() : createTransaction();
634
635         // we created a transaction, so it is this method's responsibility to
636
// wrap the execution in it
637
transaction.performQuery(this, query, resultConsumer);
638     }
639
640     /**
641      * Routes and executes a given query. Note that query resolution phase is not done at
642      * this level and is a responsibility of the caller.
643      *
644      * @since 1.2
645      */

646     public void performQuery(
647             QueryExecutionPlan query,
648             OperationObserver resultConsumer,
649             Transaction transaction) {
650
651         final Map JavaDoc queryMap = new HashMap JavaDoc();
652
653         // TODO: optimize for single engine - the most common case...
654

655         // QueryRouter to organize queries by engine
656
QueryRouter router = new QueryRouter() {
657
658             public QueryEngine engineForDataMap(DataMap map) {
659                 if (map == null) {
660                     throw new NullPointerException JavaDoc(
661                             "Null DataMap, can't determine DataNode.");
662                 }
663
664                 QueryEngine node = lookupDataNode(map);
665
666                 if (node == null) {
667                     throw new CayenneRuntimeException("No DataNode exists for DataMap "
668                             + map);
669                 }
670
671                 return node;
672             }
673
674             public void useEngineForQuery(QueryEngine engine, Query query) {
675
676                 List JavaDoc queriesByEngine = (List JavaDoc) queryMap.get(engine);
677                 if (queriesByEngine == null) {
678                     queriesByEngine = new ArrayList JavaDoc();
679                     queryMap.put(engine, queriesByEngine);
680                 }
681
682                 queriesByEngine.add(query);
683             }
684         };
685
686         query.route(router, getEntityResolver());
687
688         // perform queries on each node
689
Iterator JavaDoc nodeIt = queryMap.entrySet().iterator();
690         while (nodeIt.hasNext()) {
691             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) nodeIt.next();
692             QueryEngine nextNode = (QueryEngine) entry.getKey();
693             Collection JavaDoc nodeQueries = (Collection JavaDoc) entry.getValue();
694
695             nextNode.performQueries(nodeQueries, resultConsumer, transaction);
696         }
697     }
698 }
Popular Tags