KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cayenne > access > DataDomainQueryAction


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20 package org.apache.cayenne.access;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Collections JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import org.apache.cayenne.CayenneException;
31 import org.apache.cayenne.CayenneRuntimeException;
32 import org.apache.cayenne.DataRow;
33 import org.apache.cayenne.ObjectContext;
34 import org.apache.cayenne.ObjectId;
35 import org.apache.cayenne.Persistent;
36 import org.apache.cayenne.QueryResponse;
37 import org.apache.cayenne.cache.QueryCache;
38 import org.apache.cayenne.cache.QueryCacheEntryFactory;
39 import org.apache.cayenne.map.DataMap;
40 import org.apache.cayenne.map.DbRelationship;
41 import org.apache.cayenne.map.ObjEntity;
42 import org.apache.cayenne.map.ObjRelationship;
43 import org.apache.cayenne.query.ObjectIdQuery;
44 import org.apache.cayenne.query.PrefetchSelectQuery;
45 import org.apache.cayenne.query.PrefetchTreeNode;
46 import org.apache.cayenne.query.Query;
47 import org.apache.cayenne.query.QueryMetadata;
48 import org.apache.cayenne.query.QueryRouter;
49 import org.apache.cayenne.query.RefreshQuery;
50 import org.apache.cayenne.query.RelationshipQuery;
51 import org.apache.cayenne.query.SQLResultSetMapping;
52 import org.apache.cayenne.reflect.ClassDescriptor;
53 import org.apache.cayenne.util.GenericResponse;
54 import org.apache.cayenne.util.ListResponse;
55 import org.apache.cayenne.util.Util;
56 import org.apache.commons.collections.Transformer;
57
58 /**
59  * Performs query routing and execution. During execution phase intercepts callbacks to
60  * the OperationObserver, remapping results to the original pre-routed queries.
61  *
62  * @since 1.2
63  * @author Andrus Adamchik
64  */

65 class DataDomainQueryAction implements QueryRouter, OperationObserver {
66
67     static final boolean DONE = true;
68
69     DataContext context;
70     DataDomain domain;
71     DataRowStore cache;
72     Query query;
73     QueryMetadata metadata;
74
75     QueryResponse response;
76     GenericResponse fullResponse;
77     Map JavaDoc prefetchResultsByPath;
78     Map JavaDoc queriesByNode;
79     Map JavaDoc queriesByExecutedQueries;
80     boolean noObjectConversion;
81
82     /*
83      * A constructor for the "new" way of performing a query via 'execute' with
84      * QueryResponse created internally.
85      */

86     DataDomainQueryAction(ObjectContext context, DataDomain domain, Query query) {
87         if (context != null && !(context instanceof DataContext)) {
88             throw new IllegalArgumentException JavaDoc(
89                     "DataDomain can only work with DataContext. "
90                             + "Unsupported context type: "
91                             + context);
92         }
93
94         this.domain = domain;
95         this.query = query;
96         this.metadata = query.getMetaData(domain.getEntityResolver());
97         this.context = (DataContext) context;
98
99         // cache may be shared or unique for the ObjectContext
100
if (context != null) {
101             this.cache = this.context.getObjectStore().getDataRowCache();
102         }
103
104         if (this.cache == null) {
105             this.cache = domain.getSharedSnapshotCache();
106         }
107     }
108
109     QueryResponse execute() {
110
111         // run chain...
112
if (interceptOIDQuery() != DONE) {
113             if (interceptRelationshipQuery() != DONE) {
114                 if (interceptRefreshQuery() != DONE) {
115                     if (interceptSharedCache() != DONE) {
116                         if (interceptDataDomainQuery() != DONE) {
117                             runQueryInTransaction();
118                         }
119                     }
120                 }
121             }
122         }
123
124         if (!noObjectConversion) {
125
126             if (interceptMappedConversion() != DONE) {
127                 interceptObjectConversion();
128             }
129         }
130
131         return response;
132     }
133
134     private boolean interceptDataDomainQuery() {
135         if (query instanceof DataDomainQuery) {
136             response = new ListResponse(domain);
137             return DONE;
138         }
139
140         return !DONE;
141     }
142
143     private boolean interceptOIDQuery() {
144         if (query instanceof ObjectIdQuery) {
145
146             ObjectIdQuery oidQuery = (ObjectIdQuery) query;
147
148             DataRow row = null;
149
150             if (!oidQuery.isFetchMandatory()) {
151                 row = cache.getCachedSnapshot(oidQuery.getObjectId());
152             }
153
154             // refresh is forced or not found in cache
155
if (row == null) {
156
157                 if (oidQuery.isFetchAllowed()) {
158
159                     runQueryInTransaction();
160                 }
161                 else {
162                     response = new ListResponse();
163                 }
164             }
165             else {
166                 response = new ListResponse(row);
167             }
168
169             return DONE;
170         }
171
172         return !DONE;
173     }
174
175     private boolean interceptRelationshipQuery() {
176
177         if (query instanceof RelationshipQuery) {
178
179             RelationshipQuery relationshipQuery = (RelationshipQuery) query;
180             if (relationshipQuery.isRefreshing()) {
181                 return !DONE;
182             }
183
184             ObjRelationship relationship = relationshipQuery.getRelationship(domain
185                     .getEntityResolver());
186
187             // check if we can derive target PK from FK... this implies that the
188
// relationship is to-one
189
if (relationship.isSourceIndependentFromTargetChange()) {
190                 return !DONE;
191             }
192
193             DataRow sourceRow = cache.getCachedSnapshot(relationshipQuery.getObjectId());
194
195             if (sourceRow == null) {
196                 return !DONE;
197             }
198
199             // we can assume that there is one and only one DbRelationship as
200
// we previously checked that
201
// "!isSourceIndependentFromTargetChange"
202
DbRelationship dbRelationship = (DbRelationship) relationship
203                     .getDbRelationships()
204                     .get(0);
205
206             ObjectId targetId = sourceRow.createTargetObjectId(relationship
207                     .getTargetEntityName(), dbRelationship);
208
209             // null id means that FK is null...
210
if (targetId == null) {
211                 this.response = new GenericResponse(Collections.EMPTY_LIST);
212                 return DONE;
213             }
214
215             DataRow targetRow = cache.getCachedSnapshot(targetId);
216
217             if (targetRow != null) {
218                 this.response = new GenericResponse(Collections.singletonList(targetRow));
219                 return DONE;
220             }
221             // a hack to prevent passing partial snapshots to ObjectResolver ... See
222
// CAY-724 for details.
223
else if (context != null
224                     && domain.getEntityResolver().lookupInheritanceTree(
225                             (ObjEntity) relationship.getTargetEntity()) == null) {
226
227                 this.noObjectConversion = true;
228                 Object JavaDoc object = context.localObject(targetId, null);
229                 this.response = new GenericResponse(Collections.singletonList(object));
230                 return DONE;
231             }
232         }
233
234         return !DONE;
235     }
236
237     /**
238      * @since 3.0
239      */

240     private boolean interceptRefreshQuery() {
241
242         if (query instanceof RefreshQuery) {
243             RefreshQuery refreshQuery = (RefreshQuery) query;
244
245             if (refreshQuery.isRefreshAll()) {
246
247                 // not sending any events - peer contexts will not get refreshed
248
domain.getSharedSnapshotCache().clear();
249                 context.getQueryCache().clear();
250
251                 GenericResponse response = new GenericResponse();
252                 response.addUpdateCount(1);
253                 this.response = response;
254                 return DONE;
255             }
256
257             Collection JavaDoc objects = refreshQuery.getObjects();
258             if (objects != null && !objects.isEmpty()) {
259
260                 Collection JavaDoc ids = new ArrayList JavaDoc(objects.size());
261                 Iterator JavaDoc it = objects.iterator();
262                 while (it.hasNext()) {
263                     Persistent object = (Persistent) it.next();
264                     ids.add(object.getObjectId());
265                 }
266
267                 if (domain.getSharedSnapshotCache() != null) {
268                     // send an event for removed snapshots
269
domain.getSharedSnapshotCache().processSnapshotChanges(
270                             context.getObjectStore(),
271                             Collections.EMPTY_MAP,
272                             Collections.EMPTY_LIST,
273                             ids,
274                             Collections.EMPTY_LIST);
275                 }
276
277                 GenericResponse response = new GenericResponse();
278                 response.addUpdateCount(1);
279                 this.response = response;
280                 return DONE;
281             }
282
283             // 3. refresh query - this shouldn't normally happen as child datacontext
284
// usually does a cascading refresh
285
if (refreshQuery.getQuery() != null) {
286                 Query cachedQuery = refreshQuery.getQuery();
287
288                 String JavaDoc cacheKey = cachedQuery
289                         .getMetaData(context.getEntityResolver())
290                         .getCacheKey();
291                 context.getQueryCache().remove(cacheKey);
292
293                 this.response = domain.onQuery(context, cachedQuery);
294                 return DONE;
295             }
296
297             // 4. refresh groups...
298
if (refreshQuery.getGroupKeys() != null
299                     && refreshQuery.getGroupKeys().length > 0) {
300
301                 String JavaDoc[] groups = refreshQuery.getGroupKeys();
302                 for (int i = 0; i < groups.length; i++) {
303                     domain.getQueryCache().removeGroup(groups[i]);
304                 }
305
306                 GenericResponse response = new GenericResponse();
307                 response.addUpdateCount(1);
308                 this.response = response;
309                 return DONE;
310             }
311         }
312
313         return !DONE;
314     }
315
316     /*
317      * Wraps execution in shared cache checks
318      */

319     private final boolean interceptSharedCache() {
320
321         if (metadata.getCacheKey() == null) {
322             return !DONE;
323         }
324
325         boolean cache = QueryMetadata.SHARED_CACHE.equals(metadata.getCachePolicy());
326         boolean cacheOrCacheRefresh = cache
327                 || QueryMetadata.SHARED_CACHE_REFRESH.equals(metadata.getCachePolicy());
328
329         if (!cacheOrCacheRefresh) {
330             return !DONE;
331         }
332
333         QueryCache queryCache = domain.getQueryCache();
334         QueryCacheEntryFactory factory = getCacheObjectFactory();
335
336         if (cache) {
337             List JavaDoc cachedResults = queryCache.get(metadata, factory);
338
339             // response may already be initialized by the factory above ... it is null if
340
// there was a preexisting cache entry
341
if (response == null) {
342                 response = new ListResponse(cachedResults);
343             }
344
345             if (cachedResults instanceof ListWithPrefetches) {
346                 this.prefetchResultsByPath = ((ListWithPrefetches) cachedResults)
347                         .getPrefetchResultsByPath();
348             }
349         }
350         else {
351             // on cache-refresh request, fetch without blocking and fill the cache
352
queryCache.put(metadata, (List JavaDoc) factory.createObject());
353         }
354
355         return DONE;
356     }
357
358     private QueryCacheEntryFactory getCacheObjectFactory() {
359         return new QueryCacheEntryFactory() {
360
361             public Object JavaDoc createObject() {
362                 runQueryInTransaction();
363
364                 List JavaDoc list = response.firstList();
365                 if (list != null) {
366
367                     // make an immutable list to make sure callers don't mess it up
368
list = Collections.unmodifiableList(list);
369
370                     // include prefetches in the cached result
371
if (prefetchResultsByPath != null) {
372                         list = new ListWithPrefetches(list, prefetchResultsByPath);
373                     }
374                 }
375
376                 return list;
377             }
378         };
379     }
380
381     /*
382      * Gets response from the underlying DataNodes.
383      */

384     void runQueryInTransaction() {
385
386         domain.runInTransaction(new Transformer() {
387
388             public Object JavaDoc transform(Object JavaDoc input) {
389                 runQuery();
390                 return null;
391             }
392         });
393     }
394
395     private void runQuery() {
396         // reset
397
this.fullResponse = new GenericResponse();
398         this.response = this.fullResponse;
399         this.queriesByNode = null;
400         this.queriesByExecutedQueries = null;
401
402         // whether this is null or not will driver further decisions on how to process
403
// prefetched rows
404
this.prefetchResultsByPath = metadata.getPrefetchTree() != null
405                 && !metadata.isFetchingDataRows() ? new HashMap JavaDoc() : null;
406
407         // categorize queries by node and by "executable" query...
408
query.route(this, domain.getEntityResolver(), null);
409
410         // run categorized queries
411
if (queriesByNode != null) {
412             Iterator JavaDoc nodeIt = queriesByNode.entrySet().iterator();
413             while (nodeIt.hasNext()) {
414                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) nodeIt.next();
415                 QueryEngine nextNode = (QueryEngine) entry.getKey();
416                 Collection JavaDoc nodeQueries = (Collection JavaDoc) entry.getValue();
417                 nextNode.performQueries(nodeQueries, this);
418             }
419         }
420     }
421
422     private void interceptObjectConversion() {
423
424         if (context != null && !metadata.isFetchingDataRows()) {
425
426             List JavaDoc mainRows = response.firstList();
427             if (mainRows != null && !mainRows.isEmpty()) {
428
429                 List JavaDoc objects;
430                 ClassDescriptor descriptor = metadata.getClassDescriptor();
431                 PrefetchTreeNode prefetchTree = metadata.getPrefetchTree();
432
433                 // take a shortcut when no prefetches exist...
434
if (prefetchTree == null) {
435                     objects = new ObjectResolver(context, descriptor, metadata
436                             .isRefreshingObjects(), metadata.isResolvingInherited())
437                             .synchronizedObjectsFromDataRows(mainRows);
438                 }
439                 else {
440
441                     ObjectTreeResolver resolver = new ObjectTreeResolver(
442                             context,
443                             metadata);
444                     objects = resolver.synchronizedObjectsFromDataRows(
445                             prefetchTree,
446                             mainRows,
447                             prefetchResultsByPath);
448                 }
449
450                 if (response instanceof GenericResponse) {
451                     ((GenericResponse) response).replaceResult(mainRows, objects);
452                 }
453                 else if (response instanceof ListResponse) {
454                     this.response = new ListResponse(objects);
455                 }
456                 else {
457                     throw new IllegalStateException JavaDoc("Unknown response object: "
458                             + this.response);
459                 }
460             }
461         }
462     }
463
464     private boolean interceptMappedConversion() {
465         SQLResultSetMapping rsMapping = metadata.getResultSetMapping();
466
467         if (rsMapping == null) {
468             return !DONE;
469         }
470
471         List JavaDoc mainRows = response.firstList();
472         if (mainRows != null && !mainRows.isEmpty()) {
473
474             Collection JavaDoc columns = rsMapping.getColumnResults();
475             if (columns.isEmpty()) {
476                 throw new CayenneRuntimeException(
477                         "Invalid result set mapping, no columns mapped.");
478             }
479
480             Object JavaDoc[] columnsArray = columns.toArray();
481             int rowsLen = mainRows.size();
482             int rowWidth = columnsArray.length;
483
484             List JavaDoc objects = new ArrayList JavaDoc(rowsLen);
485
486             // add scalars to the result
487
if (rowWidth == 1) {
488
489                 for (int i = 0; i < rowsLen; i++) {
490                     Map JavaDoc row = (Map JavaDoc) mainRows.get(i);
491                     objects.add(row.get(columnsArray[0]));
492                 }
493             }
494             // add Object[]'s to the result
495
else {
496                 for (int i = 0; i < rowsLen; i++) {
497                     Map JavaDoc row = (Map JavaDoc) mainRows.get(i);
498                     Object JavaDoc[] rowDecoded = new Object JavaDoc[rowWidth];
499
500                     for (int j = 0; j < rowWidth; j++) {
501                         rowDecoded[j] = row.get(columnsArray[j]);
502                     }
503
504                     objects.add(rowDecoded);
505                 }
506             }
507
508             if (response instanceof GenericResponse) {
509                 ((GenericResponse) response).replaceResult(mainRows, objects);
510             }
511             else if (response instanceof ListResponse) {
512                 this.response = new ListResponse(objects);
513             }
514             else {
515                 throw new IllegalStateException JavaDoc("Unknown response object: "
516                         + this.response);
517             }
518
519         }
520
521         return DONE;
522     }
523
524     public void route(QueryEngine engine, Query query, Query substitutedQuery) {
525
526         List JavaDoc queries = null;
527         if (queriesByNode == null) {
528             queriesByNode = new HashMap JavaDoc();
529         }
530         else {
531             queries = (List JavaDoc) queriesByNode.get(engine);
532         }
533
534         if (queries == null) {
535             queries = new ArrayList JavaDoc(5);
536             queriesByNode.put(engine, queries);
537         }
538
539         queries.add(query);
540
541         // handle case when routing resuled in an "exectable" query different from the
542
// original query.
543
if (substitutedQuery != null && substitutedQuery != query) {
544
545             if (queriesByExecutedQueries == null) {
546                 queriesByExecutedQueries = new HashMap JavaDoc();
547             }
548
549             queriesByExecutedQueries.put(query, substitutedQuery);
550         }
551     }
552
553     public QueryEngine engineForDataMap(DataMap map) {
554         if (map == null) {
555             throw new NullPointerException JavaDoc("Null DataMap, can't determine DataNode.");
556         }
557
558         QueryEngine node = domain.lookupDataNode(map);
559
560         if (node == null) {
561             throw new CayenneRuntimeException("No DataNode exists for DataMap " + map);
562         }
563
564         return node;
565     }
566
567     public void nextCount(Query query, int resultCount) {
568         fullResponse.addUpdateCount(resultCount);
569     }
570
571     public void nextBatchCount(Query query, int[] resultCount) {
572         fullResponse.addBatchUpdateCount(resultCount);
573     }
574
575     public void nextDataRows(Query query, List JavaDoc dataRows) {
576
577         // exclude prefetched rows in the main result
578
if (prefetchResultsByPath != null && query instanceof PrefetchSelectQuery) {
579             PrefetchSelectQuery prefetchQuery = (PrefetchSelectQuery) query;
580             prefetchResultsByPath.put(prefetchQuery.getPrefetchPath(), dataRows);
581         }
582         else {
583             fullResponse.addResultList(dataRows);
584         }
585     }
586
587     public void nextDataRows(Query q, ResultIterator it) {
588         throw new CayenneRuntimeException("Invalid attempt to fetch a cursor.");
589     }
590
591     public void nextGeneratedDataRows(Query query, ResultIterator keysIterator) {
592         if (keysIterator != null) {
593             try {
594                 nextDataRows(query, keysIterator.dataRows(true));
595             }
596             catch (CayenneException ex) {
597                 // don't throw here....
598
nextQueryException(query, ex);
599             }
600         }
601     }
602
603     public void nextQueryException(Query query, Exception JavaDoc ex) {
604         throw new CayenneRuntimeException("Query exception.", Util.unwindException(ex));
605     }
606
607     public void nextGlobalException(Exception JavaDoc e) {
608         throw new CayenneRuntimeException("Global exception.", Util.unwindException(e));
609     }
610
611     public boolean isIteratedResult() {
612         return false;
613     }
614 }
615
Popular Tags