Source code: org/objectstyle/cayenne/access/IncrementalFaultList.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56 package org.objectstyle.cayenne.access;
57
58 import java.util.ArrayList;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.ListIterator;
64 import java.util.Map;
65
66 import org.objectstyle.cayenne.CayenneException;
67 import org.objectstyle.cayenne.CayenneRuntimeException;
68 import org.objectstyle.cayenne.DataObject;
69 import org.objectstyle.cayenne.exp.Expression;
70 import org.objectstyle.cayenne.exp.ExpressionFactory;
71 import org.objectstyle.cayenne.map.ObjEntity;
72 import org.objectstyle.cayenne.query.GenericSelectQuery;
73 import org.objectstyle.cayenne.query.SelectQuery;
74
75 /**
76 * A synchronized list that serves as a container of DataObjects. It is returned
77 * when a paged query is performed by DataContext. On creation, only the first
78 * "page" is fully resolved, for the rest of the objects only their ObjectIds
79 * are read. Pages following the first page are resolved on demand only. On
80 * access to an element, the list would ensure that this element as well as all
81 * its siblings on the same page are fully resolved.
82 *
83 * <p>Note that this list would only allow addition of DataObjects. Attempts to
84 * add any other object types will result in an exception.</p>
85 *
86 * <p>Performance note: certain operations like <code>toArray</code> would
87 * trigger full list fetch.</p>
88 *
89 * @author Andrei Adamchik
90 */
91 public class IncrementalFaultList implements List {
92
93 protected int pageSize;
94 protected List elements;
95 protected DataContext dataContext;
96 protected ObjEntity rootEntity;
97 protected SelectQuery internalQuery;
98 protected int unfetchedObjects;
99
100 /**
101 * Creates a new list copying settings from another list.
102 * Elements WILL NOT be copied or fetched.
103 */
104 public IncrementalFaultList(IncrementalFaultList list) {
105 this.pageSize = list.pageSize;
106 this.internalQuery = list.internalQuery;
107 this.dataContext = list.dataContext;
108 this.rootEntity = list.rootEntity;
109 elements = Collections.synchronizedList(new ArrayList());
110 }
111
112 public IncrementalFaultList(
113 DataContext dataContext,
114 GenericSelectQuery query) {
115 if (query.getPageSize() <= 0) {
116 throw new CayenneRuntimeException(
117 "IncrementalFaultList does not support unpaged queries. Query page size is "
118 + query.getPageSize());
119 }
120
121 this.elements = Collections.synchronizedList(new ArrayList());
122 this.dataContext = dataContext;
123 this.pageSize = query.getPageSize();
124 this.rootEntity =
125 dataContext.getEntityResolver().lookupObjEntity(query);
126
127 // create an internal query, it is a partial replica of
128 // the original query and will serve as a value holder for
129 // various parameters
130 this.internalQuery = new SelectQuery();
131 this.internalQuery.setRoot(query.getRoot());
132 this.internalQuery.setLoggingLevel(query.getLoggingLevel());
133 if (query instanceof SelectQuery) {
134 this.internalQuery.addPrefetches(((SelectQuery) query).getPrefetches());
135 }
136
137 fillIn(query);
138 }
139
140 /**
141 * Performs initialization of the internal list of objects.
142 * Only the first page is fully resolved. For the rest of
143 * the list, only ObjectIds are read.
144 */
145 protected void fillIn(GenericSelectQuery query) {
146 synchronized (elements) {
147
148 // start fresh
149 elements.clear();
150
151 try {
152 long t1 = System.currentTimeMillis();
153 ResultIterator it = dataContext.performIteratedQuery(query);
154 try {
155 // read first page completely, the rest as ObjectIds
156 for (int i = 0; i < pageSize && it.hasNextRow(); i++) {
157 Map row = it.nextDataRow();
158 elements.add(
159 dataContext.objectFromDataRow(
160 rootEntity,
161 row,
162 true));
163 }
164
165 // continue reading ids
166 while (it.hasNextRow()) {
167 elements.add(it.nextObjectId());
168 }
169
170 QueryLogger.logSelectCount(
171 query.getLoggingLevel(),
172 elements.size(),
173 System.currentTimeMillis() - t1);
174
175 } finally {
176 it.close();
177 }
178 } catch (CayenneException e) {
179 throw new CayenneRuntimeException("Error performing query.", e);
180 }
181
182 // process prefetching
183 if (internalQuery.getPrefetches().size() > 0) {
184 int endOfPage =
185 (elements.size() < pageSize) ? elements.size() : pageSize;
186 dataContext.prefetchRelationships(
187 internalQuery,
188 elements.subList(0, endOfPage));
189 }
190
191 unfetchedObjects = elements.size() - pageSize;
192 }
193 }
194
195 /**
196 * Will resolve all unread objects.
197 */
198 public void resolveAll() {
199 resolveInterval(0, size());
200 }
201
202 /**
203 * Resolves a sublist of objects starting at <code>fromIndex</code>
204 * up to but not including <code>toIndex</code>. Internally performs
205 * bound checking and trims indexes accordingly.
206 */
207 protected void resolveInterval(int fromIndex, int toIndex) {
208 if (fromIndex >= toIndex) {
209 return;
210 }
211
212 synchronized (elements) {
213 if (elements.size() == 0) {
214 return;
215 }
216
217 // perform bound checking
218 if (fromIndex < 0) {
219 fromIndex = 0;
220 }
221
222 if (toIndex > elements.size()) {
223 toIndex = elements.size();
224 }
225
226 List quals = new ArrayList(pageSize);
227 List ids = new ArrayList(pageSize);
228 for (int i = fromIndex; i < toIndex; i++) {
229 Object obj = elements.get(i);
230 if (obj instanceof Map) {
231 ids.add(obj);
232 quals.add(
233 ExpressionFactory.matchAllDbExp(
234 (Map) obj,
235 Expression.EQUAL_TO));
236 }
237 }
238
239 if (quals.size() == 0) {
240 return;
241 }
242
243 SelectQuery query =
244 new SelectQuery(
245 rootEntity.getName(),
246 ExpressionFactory.joinExp(Expression.OR, quals));
247
248 List objects = dataContext.performQuery(query);
249
250 // sanity check - database data may have changed
251 if (objects.size() < ids.size()) {
252 // find missing ids
253 StringBuffer buf = new StringBuffer();
254 buf.append("Some ObjectIds are missing from the database. ");
255 buf.append("Expected ").append(ids.size()).append(
256 ", fetched ").append(
257 objects.size());
258
259 Iterator idsIt = ids.iterator();
260 boolean first = true;
261 while (idsIt.hasNext()) {
262 boolean found = false;
263 Object id = idsIt.next();
264 Iterator oIt = objects.iterator();
265 while (oIt.hasNext()) {
266 if (((DataObject) oIt.next())
267 .getObjectId()
268 .getIdSnapshot()
269 .equals(id)) {
270 found = true;
271 break;
272 }
273 }
274
275 if (!found) {
276 if (first) {
277 first = false;
278 } else {
279 buf.append(", ");
280 }
281
282 buf.append(id.toString());
283 }
284 }
285
286 throw new CayenneRuntimeException(buf.toString());
287 } else if (objects.size() > ids.size()) {
288 throw new CayenneRuntimeException(
289 "Expected "
290 + ids.size()
291 + " objects, retrieved "
292 + objects.size());
293 }
294
295 // replace ids in the list with objects
296 Iterator it = objects.iterator();
297 while (it.hasNext()) {
298 DataObject obj = (DataObject) it.next();
299 Map idMap = obj.getObjectId().getIdSnapshot();
300
301 boolean found = false;
302 for (int i = fromIndex; i < toIndex; i++) {
303 if (idMap.equals(elements.get(i))) {
304 elements.set(i, obj);
305 found = true;
306 break;
307 }
308 }
309
310 if (!found) {
311 throw new CayenneRuntimeException(
312 "Can't find id for " + idMap);
313 }
314 }
315
316 unfetchedObjects -= objects.size();
317 }
318
319 // process prefetching
320 if (internalQuery.getPrefetches().size() > 0) {
321 int endOfPage =
322 (elements.size() < toIndex) ? elements.size() : toIndex;
323 dataContext.prefetchRelationships(
324 internalQuery,
325 elements.subList(fromIndex, endOfPage));
326 }
327 }
328
329 public int pageIndex(int elementIndex) {
330 if (pageSize <= 0 || elementIndex < 0) {
331 return -1;
332 }
333
334 return elementIndex / pageSize;
335 }
336
337 /**
338 * Returns the dataContext.
339 * @return DataContext
340 */
341 public DataContext getDataContext() {
342 return dataContext;
343 }
344
345 /**
346 * Returns the pageSize.
347 * @return int
348 */
349 public int getPageSize() {
350 return pageSize;
351 }
352
353 /**
354 * This method would resolve all unresolved objects and then return
355 * a list iterator over an internal list.
356 */
357 public ListIterator listIterator() {
358 resolveAll();
359 return elements.listIterator();
360 }
361
362 /**
363 * This method would resolve all unresolved objects and then return
364 * a list iterator over an internal list.
365 */
366 public ListIterator listIterator(int index) {
367 resolveAll();
368 return elements.listIterator(index);
369 }
370
371 /**
372 * This method would resolve all unresolved objects and then return
373 * an iterator over an internal list.
374 */
375 public Iterator iterator() {
376 resolveAll();
377 return elements.iterator();
378 }
379
380 /**
381 * @see java.util.List#add(int, Object)
382 */
383 public void add(int index, Object element) {
384 if (!(element instanceof DataObject)) {
385 throw new IllegalArgumentException("Only DataObjects can be stored in this list.");
386 }
387
388 synchronized (elements) {
389 elements.add(index, element);
390 }
391 }
392
393 /**
394 * @see java.util.Collection#add(Object)
395 */
396 public boolean add(Object o) {
397 if (!(o instanceof DataObject)) {
398 throw new IllegalArgumentException("Only DataObjects can be stored in this list.");
399 }
400
401 synchronized (elements) {
402 return elements.add(o);
403 }
404 }
405
406 /**
407 * @see java.util.Collection#addAll(Collection)
408 */
409 public boolean addAll(Collection c) {
410 synchronized (elements) {
411 return elements.addAll(c);
412 }
413 }
414
415 /**
416 * @see java.util.List#addAll(int, Collection)
417 */
418 public boolean addAll(int index, Collection c) {
419 synchronized (elements) {
420 return elements.addAll(index, c);
421 }
422 }
423
424 /**
425 * @see java.util.Collection#clear()
426 */
427 public void clear() {
428 synchronized (elements) {
429 elements.clear();
430 }
431 }
432
433 /**
434 * @see java.util.Collection#contains(Object)
435 */
436 public boolean contains(Object o) {
437 synchronized (elements) {
438 return elements.contains(o);
439 }
440 }
441
442 /**
443 * @see java.util.Collection#containsAll(Collection)
444 */
445 public boolean containsAll(Collection c) {
446 synchronized (elements) {
447 return elements.containsAll(c);
448 }
449 }
450
451 /**
452 * @see java.util.List#get(int)
453 */
454 public Object get(int index) {
455 synchronized (elements) {
456 Object o = elements.get(index);
457
458 if (o instanceof Map) {
459 // read this page
460 int pageStart = pageIndex(index) * pageSize;
461 resolveInterval(pageStart, pageStart + pageSize);
462
463 return elements.get(index);
464 } else {
465 return o;
466 }
467 }
468 }
469
470 /**
471 * @see java.util.List#indexOf(Object)
472 */
473 public int indexOf(Object o) {
474 if (!(o instanceof DataObject)) {
475 return -1;
476 }
477
478 DataObject dataObj = (DataObject) o;
479 if (dataObj.getDataContext() != dataContext) {
480 return -1;
481 }
482
483 if (!dataObj
484 .getObjectId()
485 .getObjClass()
486 .getName()
487 .equals(rootEntity.getClassName())) {
488 return -1;
489 }
490
491 Map idMap = dataObj.getObjectId().getIdSnapshot();
492
493 synchronized (elements) {
494 for (int i = 0; i < elements.size(); i++) {
495 // objects are in the same context,
496 // just comparing ids should be enough
497 Object obj = elements.get(i);
498 if (obj == dataObj) {
499 return i;
500 }
501
502 Map otherIdMap =
503 (obj instanceof DataObject)
504 ? ((DataObject) obj).getObjectId().getIdSnapshot()
505 : (Map) obj;
506 if (idMap.equals(otherIdMap)) {
507 return i;
508 }
509 }
510 }
511 return -1;
512 }
513
514 /**
515 * @see java.util.Collection#isEmpty()
516 */
517 public boolean isEmpty() {
518 synchronized (elements) {
519 return elements.isEmpty();
520 }
521 }
522
523 /**
524 * @see java.util.List#lastIndexOf(Object)
525 */
526 public int lastIndexOf(Object o) {
527 if (!(o instanceof DataObject)) {
528 return -1;
529 }
530
531 DataObject dataObj = (DataObject) o;
532 if (dataObj.getDataContext() != dataContext) {
533 return -1;
534 }
535
536 if (!dataObj
537 .getObjectId()
538 .getObjClass()
539 .getName()
540 .equals(rootEntity.getClassName())) {
541 return -1;
542 }
543
544 Map idMap = dataObj.getObjectId().getIdSnapshot();
545
546 synchronized (elements) {
547 for (int i = elements.size() - 1; i <= 0; i--) {
548 // objects are in the same context,
549 // just comparing ids should be enough
550 Object obj = elements.get(i);
551 if (obj == dataObj) {
552 return i;
553 }
554
555 Map otherIdMap =
556 (obj instanceof DataObject)
557 ? ((DataObject) obj).getObjectId().getIdSnapshot()
558 : (Map) obj;
559 if (idMap.equals(otherIdMap)) {
560 return i;
561 }
562 }
563 }
564 return -1;
565 }
566
567 /**
568 * @see java.util.List#remove(int)
569 */
570 public Object remove(int index) {
571 synchronized (elements) {
572 return elements.remove(index);
573 }
574 }
575
576 /**
577 * @see java.util.Collection#remove(Object)
578 */
579 public boolean remove(Object o) {
580 synchronized (elements) {
581 return elements.remove(o);
582 }
583 }
584
585 /**
586 * @see java.util.Collection#removeAll(Collection)
587 */
588 public boolean removeAll(Collection c) {
589 synchronized (elements) {
590 return elements.removeAll(c);
591 }
592 }
593
594 /**
595 * @see java.util.Collection#retainAll(Collection)
596 */
597 public boolean retainAll(Collection c) {
598 synchronized (elements) {
599 return elements.retainAll(c);
600 }
601 }
602
603 /**
604 * @see java.util.List#set(int, Object)
605 */
606 public Object set(int index, Object element) {
607 if (!(element instanceof DataObject)) {
608 throw new IllegalArgumentException("Only DataObjects can be stored in this list.");
609 }
610
611 synchronized (elements) {
612 return elements.set(index, element);
613 }
614 }
615
616 /**
617 * @see java.util.Collection#size()
618 */
619 public int size() {
620 synchronized (elements) {
621 return elements.size();
622 }
623 }
624
625 public List subList(int fromIndex, int toIndex) {
626 synchronized (elements) {
627 resolveInterval(fromIndex, toIndex);
628 return elements.subList(fromIndex, toIndex);
629 }
630 }
631
632 public Object[] toArray() {
633 resolveAll();
634
635 return elements.toArray();
636 }
637
638 /**
639 * @see java.util.Collection#toArray(Object[])
640 */
641 public Object[] toArray(Object[] a) {
642 resolveAll();
643
644 return elements.toArray(a);
645 }
646
647 /**
648 * Returns a total number of objects that are not resolved yet.
649 */
650 public int getUnfetchedObjects() {
651 return unfetchedObjects;
652 }
653 }