001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.ejb;
012:
013: import com.versant.core.common.PCList;
014: import com.versant.core.common.QueryResultContainer;
015: import com.versant.core.server.CompiledQuery;
016: import com.versant.core.server.QueryResultWrapper;
017:
018: import java.io.Serializable;
019: import java.util.*;
020:
021: import com.versant.core.common.BindingSupportImpl;
022: import com.versant.core.jdo.*;
023:
024: /**
025: * Forward only access to the results of a Query. This will rerieve all results
026: * if any method requiring access to all results is called (e.g. size).
027: * Otherwise it retrieves elements in batches.
028: *
029: * @see com.versant.core.jdo.QueryResult
030: */
031: public final class ForwardEJBQueryResult extends QueryResultBase
032: implements Serializable {
033:
034: /**
035: * Nothing has happened as such and hence anything is allowed at this stage.
036: */
037: private static final int STATUS_NON_INITIALISED = 0;
038: /**
039: * Implies that the results have been resolved fully and get's and iterators is allowed.
040: */
041: private static final int STATUS_RESOLVED = 1;
042: /**
043: * Implies that a get was called and hence no iterators may be created
044: */
045: private static final int STATUS_SPARSE = 2;
046: /**
047: * Implies that the query has been closed.
048: */
049: private static final int STATUS_CLOSED = 3;
050: /**
051: * The current status.
052: */
053: private int status;
054:
055: /**
056: * The size if it has been computed. This depends on the state.
057: */
058: private int size;
059: /**
060: * If this is a countOnSize query? We only do a count once and then clear
061: * the flag so more size() calls will resolve the results.
062: */
063: private boolean countOnSize;
064: private PCList backingArray;
065:
066: private final EMProxy pm;
067: /**
068: * The params that was passed for query execution.
069: */
070: private Object[] params;
071: private final List openWrapperIters = new ArrayList();
072:
073: private final QueryDetails queryDetails;
074: private final CompiledQuery compiledQuery;
075:
076: /**
077: * Fields used to implement a window in the resultset
078: */
079: private Object[] window;
080: private int windowSize;
081: private boolean noMoreDataInResultSet;
082:
083: private int index;
084: private int maxAvailableIndex = -1;
085: private QueryResultWrapper qrw;
086:
087: public ForwardEJBQueryResult(EMProxy pmProxy,
088: QueryDetails queryDetails, CompiledQuery compiledQuery,
089: Object[] params) {
090: this .pm = pmProxy;
091: this .queryDetails = queryDetails;
092: this .compiledQuery = compiledQuery;
093: this .setParams(params);
094: countOnSize = queryDetails.isCountOnSize();
095: }
096:
097: /**
098: * Is our compiledQuery the same as the one for qc?
099: */
100: public boolean isCompiledQueryEqual(ForwardEJBQueryResult qc) {
101: return compiledQuery.equals(qc.compiledQuery);
102: }
103:
104: public void setParams(Object[] params) {
105: this .params = params;
106: }
107:
108: /**
109: * Close all the open iterator's.
110: */
111: public void close() {
112: if (status == STATUS_CLOSED)
113: return;
114:
115: if (qrw != null) {
116: pm.closeQuery(qrw);
117: qrw = null;
118: }
119: window = null;
120:
121: backingArray = null;
122: for (int i = 0; i < openWrapperIters.size(); i++) {
123: ((JDOListIterator) openWrapperIters.get(i)).close();
124: }
125: openWrapperIters.clear();
126: status = STATUS_CLOSED;
127: }
128:
129: /**
130: * All the results will be resolved and the size returned. If this is already been called the the size will just
131: * be returned.
132: */
133: public int size() {
134: if (status != STATUS_RESOLVED) {
135: if (countOnSize) {
136: countOnSize = false;
137: return countRows();
138: } else {
139: resolve();
140: }
141: }
142: return size;
143: }
144:
145: public boolean isEmpty() {
146: if (status != STATUS_RESOLVED) {
147: resolve();
148: }
149: return backingArray.isEmpty();
150: }
151:
152: public boolean contains(Object o) {
153: if (status != STATUS_RESOLVED) {
154: resolve();
155: }
156: return backingArray.contains(o);
157: }
158:
159: public Iterator iterator() {
160: checkClosed();
161: Iterator result = null;
162: if (status == STATUS_RESOLVED) {
163: result = getLocalIter();
164: } else {
165: result = createInternalIter();
166: }
167: return result;
168: }
169:
170: /**
171: * This is an iterator over the already fully resolved list.
172: *
173: * @see #backingArray
174: */
175: private ListIterator getLocalIter() {
176: ListIterator lIter = backingArray.listIterator();
177: openWrapperIters.add(lIter);
178: return lIter;
179: }
180:
181: /**
182: * This executes a new server side query.
183: *
184: * @see com.versant.core.jdo.QueryResultIterator
185: */
186: private ListIterator createInternalIter() {
187: return createInternalIterImp(false);
188: }
189:
190: private ListIterator createInternalIterImp(boolean doNotFlush) {
191: checkClosed();
192: EJBQueryIterator queryIterator = new EJBQueryIterator(pm,
193: compiledQuery, params, doNotFlush);
194: openWrapperIters.add(queryIterator);
195: return queryIterator;
196: }
197:
198: public Iterator createInternalIterNoFlush() {
199: return createInternalIterImp(true);
200: }
201:
202: public ListIterator listIterator() {
203: return (ListIterator) iterator();
204: }
205:
206: public ListIterator listIterator(int index) {
207: throw BindingSupportImpl.getInstance().notImplemented("");
208: }
209:
210: /**
211: * Resolves all the data and add it to array.
212: */
213: public Object[] toArray() {
214: toArrayImp();
215: return backingArray.toArray();
216: }
217:
218: public Object[] toArray(Object[] a) {
219: toArrayImp();
220: return backingArray.toArray(a);
221: }
222:
223: private void toArrayImp() {
224: if (status != STATUS_RESOLVED) {
225: resolve();
226: }
227: }
228:
229: private void resolve() {
230: if (status == STATUS_RESOLVED)
231: return;
232: checkClosed();
233: if (status == STATUS_SPARSE) {
234: throw BindingSupportImpl
235: .getInstance()
236: .invalidOperation(
237: "Any operation that will fully resolve"
238: + " the query may not be called once a 'get' operation was performed");
239: }
240:
241: if (!queryDetails.isIgnoreCache()) {
242: pm.flushIfDepOn(compiledQuery.getEvictionClassBits());
243: }
244:
245: QueryResultContainer qContainer = pm.getAllQueryResults(
246: compiledQuery, params);
247: pm.addToCache(qContainer.container);
248:
249: backingArray = new PCList(qContainer.toResolvedObject(pm), 0,
250: qContainer.size());
251: size = backingArray.size();
252: status = STATUS_RESOLVED;
253: qContainer.reset();
254: pm.processLocalCacheReferenceQueue();
255: }
256:
257: private void checkClosed() {
258: if (status == STATUS_CLOSED) {
259: throw BindingSupportImpl.getInstance().invalidOperation(
260: "Query result has been closed");
261: }
262: }
263:
264: /**
265: * Execute the query in count(*) mode (countOnSize option).
266: */
267: private int countRows() {
268: checkClosed();
269: if (!queryDetails.isIgnoreCache()) {
270: pm.flushIfDepOn(compiledQuery.getEvictionClassBits());
271: }
272: return pm.getQueryRowCount(compiledQuery, params);
273: }
274:
275: /**
276: * If the backingArray exist then the get should operate on it.
277: */
278: public Object get(int index) {
279: if (index < 0) {
280: throw BindingSupportImpl.getInstance().illegalArgument(
281: "Index smaller than zero is not allowed");
282: }
283: Object result = null;
284: switch (status) {
285: case STATUS_RESOLVED:
286: result = backingArray.get(index);
287: break;
288: case STATUS_SPARSE:
289: result = internalGet(index, maxAvailableIndex);
290: break;
291: case STATUS_NON_INITIALISED:
292: if (queryDetails.prefetchAll()) {
293: resolve();
294: result = backingArray.get(index);
295: } else {
296: if (!queryDetails.isIgnoreCache()) {
297: pm.flushIfDepOn(compiledQuery
298: .getEvictionClassBits());
299: }
300:
301: QueryResultWrapper qrsIF = this .pm.executeQuery(
302: compiledQuery, params);
303: QueryResultContainer qContainer = this .pm
304: .getNextQueryResult(qrsIF, index);
305:
306: if (qContainer.allResults) {
307: pm.addToCache(qContainer.container);
308:
309: status = STATUS_RESOLVED;
310: backingArray = new PCList(qContainer
311: .toResolvedObject(pm), 0, qContainer.size());
312: size = backingArray.size();
313: status = STATUS_RESOLVED;
314:
315: qContainer.reset();
316: result = backingArray.get(index);
317: pm.processLocalCacheReferenceQueue();
318: } else {
319: status = STATUS_SPARSE;
320: qrw = qrsIF;
321: addNewData(qContainer, index);
322: result = internalGet(index, maxAvailableIndex);
323: }
324: }
325: break;
326: default:
327: checkClosed();
328: throw BindingSupportImpl.getInstance().internal(
329: "Status does not exist. Status = '" + status + "'");
330: }
331: return result;
332: }
333:
334: private Object internalGet(int requestedIndex,
335: final int maxAvailableIndex) {
336: if (requestedIndex > maxAvailableIndex
337: && !noMoreDataInResultSet) {
338: getMoreData(requestedIndex);
339: return getNextData(0);
340: } else {
341: return getNextData(windowSize
342: - ((maxAvailableIndex - requestedIndex) + 1));
343: }
344:
345: }
346:
347: private Object getNextData(int windowIndex) {
348: if (windowIndex == windowSize)
349: throw BindingSupportImpl.getInstance()
350: .arrayIndexOutOfBounds(
351: "index '" + index + "' is too big");
352:
353: if (windowIndex < 0) {
354: throw BindingSupportImpl
355: .getInstance()
356: .unsupported(
357: "May only request index greater than the previously requested index."
358: + "\nIf this is required then use VersantQuery.setRandomAccess.");
359: }
360:
361: Object result = QueryResultBase.resolveRow(window[windowIndex],
362: pm);
363: window[windowIndex] = null;
364: return result;
365: }
366:
367: private void getMoreData(int requestedIndex) {
368: //get data from server
369: addNewData(pm.getNextQueryResult(qrw, requestedIndex
370: - maxAvailableIndex - 1), requestedIndex);
371: }
372:
373: private void addNewData(QueryResultContainer container,
374: int requestedIndex) {
375: pm.addToCache(container.container);
376:
377: window = container.getDataArray();
378: windowSize = container.size();
379: noMoreDataInResultSet = container.isqFinished();
380:
381: index = requestedIndex;
382: maxAvailableIndex = index + windowSize - 1;
383: container.reset();
384: }
385:
386: public int indexOf(Object o) {
387: if (status != STATUS_RESOLVED) {
388: resolve();
389: }
390: return backingArray.indexOf(o);
391: }
392:
393: public int lastIndexOf(Object o) {
394: if (status != STATUS_RESOLVED) {
395: resolve();
396: }
397: return backingArray.lastIndexOf(o);
398: }
399:
400: public List subList(int fromIndex, int toIndex) {
401: if (status != STATUS_RESOLVED) {
402: resolve();
403: }
404: return backingArray.subList(fromIndex, toIndex);
405: }
406:
407: public boolean containsAll(Collection c) {
408: if (status != STATUS_RESOLVED) {
409: resolve();
410: }
411: return backingArray.containsAll(c);
412: }
413:
414: public boolean equals(Object obj) {
415: if (status != STATUS_RESOLVED) {
416: resolve();
417: }
418: return backingArray.equals(obj);
419: }
420:
421: public String toString() {
422: if (status != STATUS_RESOLVED) {
423: resolve();
424: }
425: return backingArray.toString();
426: }
427:
428: /**
429: * Serialize out an ArrayList instead of ourselves.
430: */
431: public Object writeReplace() {
432: return new ArrayList(this);
433: }
434:
435: }
|