KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cayenne > remote > RemoteIncrementalFaultList


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.remote;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.ListIterator JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.NoSuchElementException JavaDoc;
29
30 import org.apache.cayenne.CayenneRuntimeException;
31 import org.apache.cayenne.ObjectContext;
32 import org.apache.cayenne.Persistent;
33 import org.apache.cayenne.QueryResponse;
34 import org.apache.cayenne.query.Query;
35 import org.apache.cayenne.query.QueryMetadata;
36 import org.apache.cayenne.query.SelectQuery;
37 import org.apache.cayenne.util.IDUtil;
38 import org.apache.cayenne.util.IncrementalListResponse;
39 import org.apache.cayenne.util.Util;
40
41 /**
42  * A list that serves as a container of Persistent objects. It is usually returned by an
43  * ObjectContext when a paginated query is performed. Initially only the first "page" of
44  * objects is fully resolved. Pages following the first page are resolved on demand. When
45  * a list element is accessed, the list would ensure that this element as well as all its
46  * siblings on the same page are fully resolved.
47  * <p>
48  * The list can hold DataRows or Persistent objects. Attempts to add any other object
49  * types will result in an exception.
50  * </p>
51  * <p>
52  * Certain operations like <code>toArray</code> would trigger full list fetch.
53  * </p>
54  * <p>
55  * Synchronization Note: this list is not synchronized. All access to it should follow
56  * synchronization rules applicable for ArrayList.
57  * </p>
58  *
59  * @since 1.2
60  * @author Andrus Adamchik
61  */

62 public class RemoteIncrementalFaultList implements List JavaDoc {
63
64     static final Object JavaDoc PLACEHOLDER = new Object JavaDoc();
65
66     protected List JavaDoc elements;
67
68     protected String JavaDoc cacheKey;
69     protected int pageSize;
70     protected int unfetchedObjects;
71     protected QueryMetadata metadata;
72
73     protected transient ObjectContext context;
74
75     /**
76      * Stores a hint allowing to distinguish data rows from unfetched ids when the query
77      * fetches data rows.
78      */

79     protected int rowWidth;
80
81     private ListHelper helper;
82
83     public RemoteIncrementalFaultList(ObjectContext context, Query paginatedQuery) {
84
85         this.metadata = paginatedQuery.getMetaData(context.getEntityResolver());
86
87         if (metadata.getPageSize() <= 0) {
88             throw new IllegalArgumentException JavaDoc("Page size must be positive: "
89                     + metadata.getPageSize());
90         }
91
92         this.pageSize = metadata.getPageSize();
93         this.helper = (metadata.isFetchingDataRows())
94                 ? (ListHelper) new DataRowListHelper()
95                 : new PersistentListHelper();
96         this.context = context;
97
98         // use provided cache key if possible; this would allow clients to
99
// address the same server-side list from multiple queries.
100
this.cacheKey = metadata.getCacheKey();
101         if (cacheKey == null) {
102             cacheKey = generateCacheKey();
103         }
104
105         Query query = paginatedQuery;
106         if (metadata.getCacheKey() == null) {
107
108             // there are some serious pagination optimizations for SelectQuery on the
109
// server-side, so use a special wrapper that is itself a subclass of
110
// SelectQuery
111
if (query instanceof SelectQuery) {
112                 query = new IncrementalSelectQuery((SelectQuery) paginatedQuery, cacheKey);
113             }
114             else {
115                 query = new IncrementalQuery(paginatedQuery, cacheKey);
116             }
117         }
118
119         // select directly from the channel, bypassing the context. Otherwise our query
120
// wrapper can be intercepted incorrectly
121
QueryResponse response = context.getChannel().onQuery(context, query);
122
123         List JavaDoc firstPage = response.firstList();
124
125         // sanity check
126
if (firstPage.size() > pageSize) {
127             throw new IllegalArgumentException JavaDoc("Returned page size ("
128                     + firstPage.size()
129                     + ") exceeds requested page size ("
130                     + pageSize
131                     + ")");
132         }
133         // result is smaller than a page
134
else if (firstPage.size() < pageSize) {
135             this.elements = new ArrayList JavaDoc(firstPage);
136             unfetchedObjects = 0;
137         }
138         else {
139
140             if (response instanceof IncrementalListResponse) {
141                 int fullListSize = ((IncrementalListResponse) response).getFullSize();
142
143                 this.unfetchedObjects = fullListSize - firstPage.size();
144                 this.elements = new ArrayList JavaDoc(fullListSize);
145                 elements.addAll(firstPage);
146
147                 // fill the rest with placeholder...
148
for (int i = pageSize; i < fullListSize; i++) {
149                     elements.add(PLACEHOLDER);
150                 }
151             }
152             // this happens when full size equals page size
153
else {
154                 this.elements = new ArrayList JavaDoc(firstPage);
155                 unfetchedObjects = 0;
156             }
157         }
158     }
159
160     private String JavaDoc generateCacheKey() {
161         byte[] bytes = IDUtil.pseudoUniqueByteSequence8();
162         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(17);
163         buffer.append("I");
164         for (int i = 0; i < bytes.length; i++) {
165             IDUtil.appendFormattedByte(buffer, bytes[i]);
166         }
167
168         return buffer.toString();
169     }
170
171     /**
172      * Will resolve all unread objects.
173      */

174     public void resolveAll() {
175         resolveInterval(0, size());
176     }
177
178     /**
179      * @param object
180      * @return <code>true</code> if the object corresponds to an unresolved state and
181      * does require a fetch before being returned to the user.
182      */

183     private boolean isUnresolved(Object JavaDoc object) {
184         return object == PLACEHOLDER;
185     }
186
187     /**
188      * Resolves a sublist of objects starting at <code>fromIndex</code> up to but not
189      * including <code>toIndex</code>. Internally performs bound checking and trims
190      * indexes accordingly.
191      */

192     protected void resolveInterval(int fromIndex, int toIndex) {
193         if (fromIndex >= toIndex || elements.isEmpty()) {
194             return;
195         }
196
197         if (context == null) {
198             throw new CayenneRuntimeException(
199                     "No ObjectContext set, can't resolve objects.");
200         }
201
202         // bounds checking
203

204         if (fromIndex < 0) {
205             fromIndex = 0;
206         }
207
208         if (toIndex > elements.size()) {
209             toIndex = elements.size();
210         }
211
212         // find disjoint ranges and resolve them individually...
213

214         int fromPage = pageIndex(fromIndex);
215         int toPage = pageIndex(toIndex - 1);
216
217         int rangeStartIndex = -1;
218         for (int i = fromPage; i <= toPage; i++) {
219
220             int pageStartIndex = i * pageSize;
221             Object JavaDoc firstPageObject = elements.get(pageStartIndex);
222             if (isUnresolved(firstPageObject)) {
223
224                 // start range
225
if (rangeStartIndex < 0) {
226                     rangeStartIndex = pageStartIndex;
227                 }
228             }
229             else {
230
231                 // finish range...
232
if (rangeStartIndex >= 0) {
233                     forceResolveInterval(rangeStartIndex, pageStartIndex);
234                     rangeStartIndex = -1;
235                 }
236             }
237         }
238
239         // load last page
240
if (rangeStartIndex >= 0) {
241             forceResolveInterval(rangeStartIndex, toIndex);
242         }
243     }
244
245     void forceResolveInterval(int fromIndex, int toIndex) {
246
247         int pastEnd = toIndex - size();
248         if (pastEnd > 0) {
249             toIndex = size();
250         }
251
252         int fetchLimit = toIndex - fromIndex;
253
254         RangeQuery query = new RangeQuery(cacheKey, fromIndex, fetchLimit, metadata);
255
256         List JavaDoc sublist = context.performQuery(query);
257
258         // sanity check
259
if (sublist.size() != fetchLimit) {
260             throw new CayenneRuntimeException("Resolved range size '"
261                     + sublist.size()
262                     + "' is not the same as expected: "
263                     + fetchLimit);
264         }
265
266         for (int i = 0; i < fetchLimit; i++) {
267             elements.set(fromIndex + i, sublist.get(i));
268         }
269
270         unfetchedObjects -= sublist.size();
271     }
272
273     /**
274      * Returns zero-based index of the virtual "page" for a given array element index.
275      */

276     int pageIndex(int elementIndex) {
277         if (elementIndex < 0 || elementIndex > size()) {
278             throw new IndexOutOfBoundsException JavaDoc("Index: " + elementIndex);
279         }
280
281         if (pageSize <= 0 || elementIndex < 0) {
282             return -1;
283         }
284
285         return elementIndex / pageSize;
286     }
287
288     /**
289      * Returns ObjectContext associated with this list.
290      */

291     public ObjectContext getContext() {
292         return context;
293     }
294
295     /**
296      * Returns the pageSize.
297      *
298      * @return int
299      */

300     public int getPageSize() {
301         return pageSize;
302     }
303
304     /**
305      * Returns a list iterator for this list. DataObjects are resolved a page (according
306      * to getPageSize()) at a time as necessary - when retrieved with next() or
307      * previous().
308      */

309     public ListIterator JavaDoc listIterator() {
310         return new ListIteratorHelper(0);
311     }
312
313     /**
314      * Returns a list iterator of the elements in this list (in proper sequence), starting
315      * at the specified position in this list. The specified index indicates the first
316      * element that would be returned by an initial call to the next method. An initial
317      * call to the previous method would return the element with the specified index minus
318      * one. DataObjects are resolved a page at a time (according to getPageSize()) as
319      * necessary - when retrieved with next() or previous().
320      */

321     public ListIterator JavaDoc listIterator(int index) {
322         if (index < 0 || index > size()) {
323             throw new IndexOutOfBoundsException JavaDoc("Index: " + index);
324         }
325
326         return new ListIteratorHelper(index);
327     }
328
329     /**
330      * Return an iterator for this list. DataObjects are resolved a page (according to
331      * getPageSize()) at a time as necessary - when retrieved with next().
332      */

333     public Iterator JavaDoc iterator() {
334         // by virtue of get(index)'s implementation, resolution of ids into
335
// objects will occur on pageSize boundaries as necessary.
336
return new Iterator JavaDoc() {
337
338             int listIndex = 0;
339
340             public boolean hasNext() {
341                 return (listIndex < elements.size());
342             }
343
344             public Object JavaDoc next() {
345                 if (listIndex >= elements.size())
346                     throw new NoSuchElementException JavaDoc("no more elements");
347
348                 return get(listIndex++);
349             }
350
351             public void remove() {
352                 throw new UnsupportedOperationException JavaDoc("remove not supported.");
353             }
354         };
355     }
356
357     /**
358      * @see java.util.List#add(int, Object)
359      */

360     public void add(int index, Object JavaDoc element) {
361         helper.validateListObject(element);
362         elements.add(index, element);
363
364     }
365
366     /**
367      * @see java.util.Collection#add(Object)
368      */

369     public boolean add(Object JavaDoc o) {
370         helper.validateListObject(o);
371         return elements.add(o);
372     }
373
374     /**
375      * @see java.util.Collection#addAll(Collection)
376      */

377     public boolean addAll(Collection JavaDoc c) {
378
379         return elements.addAll(c);
380
381     }
382
383     /**
384      * @see java.util.List#addAll(int, Collection)
385      */

386     public boolean addAll(int index, Collection JavaDoc c) {
387
388         return elements.addAll(index, c);
389
390     }
391
392     /**
393      * @see java.util.Collection#clear()
394      */

395     public void clear() {
396         elements.clear();
397     }
398
399     /**
400      * @see java.util.Collection#contains(Object)
401      */

402     public boolean contains(Object JavaDoc o) {
403         return indexOf(o) >= 0;
404     }
405
406     /**
407      * @see java.util.Collection#containsAll(Collection)
408      */

409     public boolean containsAll(Collection JavaDoc c) {
410         Iterator JavaDoc it = c.iterator();
411         while (it.hasNext()) {
412             if (!contains(it.next())) {
413                 return false;
414             }
415         }
416
417         return true;
418     }
419
420     /**
421      * @see java.util.List#get(int)
422      */

423     public Object JavaDoc get(int index) {
424
425         Object JavaDoc o = elements.get(index);
426
427         if (isUnresolved(o)) {
428             // read this page
429
int pageStart = pageIndex(index) * pageSize;
430             resolveInterval(pageStart, pageStart + pageSize);
431
432             return elements.get(index);
433         }
434         else {
435             return o;
436         }
437
438     }
439
440     /**
441      * @see java.util.List#indexOf(Object)
442      */

443     public int indexOf(Object JavaDoc o) {
444         return helper.indexOfObject(o);
445     }
446
447     /**
448      * @see java.util.Collection#isEmpty()
449      */

450     public boolean isEmpty() {
451
452         return elements.isEmpty();
453
454     }
455
456     /**
457      * @see java.util.List#lastIndexOf(Object)
458      */

459     public int lastIndexOf(Object JavaDoc o) {
460         return helper.lastIndexOfObject(o);
461     }
462
463     /**
464      * @see java.util.List#remove(int)
465      */

466     public Object JavaDoc remove(int index) {
467
468         return elements.remove(index);
469
470     }
471
472     /**
473      * @see java.util.Collection#remove(Object)
474      */

475     public boolean remove(Object JavaDoc o) {
476
477         return elements.remove(o);
478
479     }
480
481     /**
482      * @see java.util.Collection#removeAll(Collection)
483      */

484     public boolean removeAll(Collection JavaDoc c) {
485
486         return elements.removeAll(c);
487
488     }
489
490     /**
491      * @see java.util.Collection#retainAll(Collection)
492      */

493     public boolean retainAll(Collection JavaDoc c) {
494
495         return elements.retainAll(c);
496
497     }
498
499     /**
500      * @see java.util.List#set(int, Object)
501      */

502     public Object JavaDoc set(int index, Object JavaDoc element) {
503         helper.validateListObject(element);
504
505         return elements.set(index, element);
506
507     }
508
509     /**
510      * @see java.util.Collection#size()
511      */

512     public int size() {
513         return elements.size();
514     }
515
516     public List JavaDoc subList(int fromIndex, int toIndex) {
517         resolveInterval(fromIndex, toIndex);
518         return elements.subList(fromIndex, toIndex);
519     }
520
521     public Object JavaDoc[] toArray() {
522         resolveAll();
523
524         return elements.toArray();
525     }
526
527     /**
528      * @see java.util.Collection#toArray(Object[])
529      */

530     public Object JavaDoc[] toArray(Object JavaDoc[] a) {
531         resolveAll();
532
533         return elements.toArray(a);
534     }
535
536     /**
537      * Returns a total number of objects that are not resolved yet.
538      */

539     public int getUnfetchedObjects() {
540         return unfetchedObjects;
541     }
542
543     abstract class ListHelper {
544
545         int indexOfObject(Object JavaDoc object) {
546             if (incorrectObjectType(object)) {
547                 return -1;
548             }
549
550             for (int i = 0; i < elements.size(); i++) {
551
552                 if (Util.nullSafeEquals(object, get(i))) {
553                     return i;
554                 }
555             }
556
557             return -1;
558         }
559
560         int lastIndexOfObject(Object JavaDoc object) {
561             if (incorrectObjectType(object)) {
562                 return -1;
563             }
564
565             for (int i = elements.size() - 1; i >= 0; i--) {
566                 if (Util.nullSafeEquals(object, get(i))) {
567                     return i;
568                 }
569             }
570
571             return -1;
572         }
573
574         abstract boolean incorrectObjectType(Object JavaDoc object);
575
576         void validateListObject(Object JavaDoc object) throws IllegalArgumentException JavaDoc {
577             if (incorrectObjectType(object)) {
578                 throw new IllegalArgumentException JavaDoc("Can't store this object: " + object);
579             }
580         }
581     }
582
583     class PersistentListHelper extends ListHelper {
584
585         boolean incorrectObjectType(Object JavaDoc object) {
586             if (!(object instanceof Persistent)) {
587                 return true;
588             }
589
590             Persistent persistent = (Persistent) object;
591             if (persistent.getObjectContext() != context) {
592                 return true;
593             }
594
595             return false;
596         }
597
598     }
599
600     class DataRowListHelper extends ListHelper {
601
602         boolean incorrectObjectType(Object JavaDoc object) {
603             if (!(object instanceof Map JavaDoc)) {
604                 return true;
605             }
606
607             Map JavaDoc map = (Map JavaDoc) object;
608             return map.size() != rowWidth;
609         }
610     }
611
612     class ListIteratorHelper implements ListIterator JavaDoc {
613
614         // by virtue of get(index)'s implementation, resolution of ids into
615
// objects will occur on pageSize boundaries as necessary.
616

617         int listIndex;
618
619         public ListIteratorHelper(int startIndex) {
620             this.listIndex = startIndex;
621         }
622
623         public void add(Object JavaDoc o) {
624             throw new UnsupportedOperationException JavaDoc("add operation not supported");
625         }
626
627         public boolean hasNext() {
628             return (listIndex < elements.size());
629         }
630
631         public boolean hasPrevious() {
632             return (listIndex > 0);
633         }
634
635         public Object JavaDoc next() {
636             if (listIndex >= elements.size())
637                 throw new NoSuchElementException JavaDoc("at the end of the list");
638
639             return get(listIndex++);
640         }
641
642         public int nextIndex() {
643             return listIndex;
644         }
645
646         public Object JavaDoc previous() {
647             if (listIndex < 1)
648                 throw new NoSuchElementException JavaDoc("at the beginning of the list");
649
650             return get(--listIndex);
651         }
652
653         public int previousIndex() {
654             return (listIndex - 1);
655         }
656
657         public void remove() {
658             throw new UnsupportedOperationException JavaDoc("remove operation not supported");
659         }
660
661         public void set(Object JavaDoc o) {
662             throw new UnsupportedOperationException JavaDoc("set operation not supported");
663         }
664     }
665 }
666
Popular Tags