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.jdo;
012:
013: import com.versant.core.common.NewObjectOID;
014: import com.versant.core.metadata.ClassMetaData;
015: import com.versant.core.metadata.FetchGroup;
016: import com.versant.core.server.CompiledQuery;
017: import com.versant.core.util.BeanUtils;
018:
019: import javax.jdo.Extent;
020: import javax.jdo.PersistenceManager;
021: import javax.jdo.spi.PersistenceCapable;
022: import java.io.Externalizable;
023: import java.io.IOException;
024: import java.io.ObjectInput;
025: import java.io.ObjectOutput;
026: import java.util.*;
027:
028: import com.versant.core.common.BindingSupportImpl;
029:
030: /**
031: * This is the implementation of a Query.
032: */
033: public final class VersantQueryImp implements VersantQuery,
034: Externalizable {
035:
036: private final QueryDetails queryDetails = new QueryDetails();
037: private QueryResult resultList;
038: private PMProxy pmProxy;
039: private CompiledQuery compiledQuery;
040:
041: /**
042: * For Serialization.
043: */
044: public VersantQueryImp() {
045: }
046:
047: /**
048: * Create a new JDOQL query for pmProxy. The ignoreCache setting is taken
049: * from the curremt setting of pmProxy.
050: */
051: public VersantQueryImp(PMProxy pmProxy) {
052: this (pmProxy, QueryDetails.LANGUAGE_JDOQL);
053: }
054:
055: /**
056: * Create a new query for pmProxy. The ignoreCache setting is taken
057: * from the curremt setting of pmProxy.
058: *
059: * @param language Query language
060: * @see QueryDetails#LANGUAGE_JDOQL
061: * @see QueryDetails#LANGUAGE_SQL
062: */
063: public VersantQueryImp(PMProxy pmProxy, int language) {
064: this .pmProxy = pmProxy;
065: queryDetails.setLanguage(language);
066: setIgnoreCache(pmProxy.getIgnoreCache());
067: }
068:
069: /**
070: * Create a new query for pmProxy using all the settings of other.
071: */
072: public VersantQueryImp(PMProxy pmProxy, VersantQueryImp other) {
073: this (pmProxy, other.queryDetails);
074: }
075:
076: /**
077: * Create a new query for pmProxy using all the settings of params.
078: * This is used to create Query's from named queries in the meta data.
079: */
080: public VersantQueryImp(PMProxy pmProxy, QueryDetails params) {
081: this .pmProxy = pmProxy;
082: queryDetails.fillFrom(params);
083: if (params.isUseIgnoreCacheFromPM()) {
084: setIgnoreCache(pmProxy.getIgnoreCache());
085: }
086: }
087:
088: /**
089: * This is a util method that invalidates the preCompiled query because of
090: * change made by the client.
091: */
092: private void changed() {
093: compiledQuery = null;
094: }
095:
096: public void setBounded(boolean value) {
097: queryDetails.setBounded(value);
098: }
099:
100: public boolean isBounded() {
101: return queryDetails.isBounded();
102: }
103:
104: public void setClass(Class cls) {
105: changed();
106: queryDetails.setCandidateClass(cls);
107: }
108:
109: public void setCandidates(Extent pcs) {
110: changed();
111: queryDetails.setExtent(pcs);
112: }
113:
114: public void setCandidates(Collection pcs) {
115: changed();
116: queryDetails.setCol(pcs);
117: }
118:
119: public void setFilter(String filter) {
120: changed();
121: queryDetails.setFilter(filter);
122: }
123:
124: public String getFilter() {
125: return queryDetails.getFilter();
126: }
127:
128: public void declareImports(String imports) {
129: changed();
130: queryDetails.setImports(imports);
131: }
132:
133: public void declareParameters(String params) {
134: changed();
135: queryDetails.declareParameters(params);
136: }
137:
138: public void declareVariables(String variables) {
139: changed();
140: queryDetails.setVariables(variables);
141: }
142:
143: public void setOrdering(String ordering) {
144: changed();
145: queryDetails.setOrdering(ordering);
146: }
147:
148: public void setIgnoreCache(boolean ignoreCache) {
149: changed();
150: queryDetails.setIgnoreCache(ignoreCache);
151: }
152:
153: private VersantPersistenceManagerImp getRealPM() {
154: if (pmProxy == null) {
155: throw BindingSupportImpl
156: .getInstance()
157: .invalidOperation(
158: "Query is not associated with a PersistenceManager");
159: }
160: return pmProxy.getRealPM();
161: }
162:
163: public void setFetchGroup(String fgName) {
164: if (fgName == null) {
165: queryDetails.setFetchGroupIndex(0);
166: changed();
167: return;
168: } else {
169: if (queryDetails.getCandidateClass() == null) {
170: throw BindingSupportImpl
171: .getInstance()
172: .invalidOperation(
173: "Please first supply a candidate class");
174: }
175: FetchGroup fg = getRealPM().modelMetaData.getClassMetaData(
176: queryDetails.getCandidateClass()).getFetchGroup(
177: fgName);
178: if (fg == null) {
179: throw BindingSupportImpl.getInstance()
180: .invalidOperation(
181: "No fetchGroup with name "
182: + fgName
183: + " for class "
184: + queryDetails
185: .getCandidateClass()
186: .getName());
187: }
188:
189: queryDetails.setFetchGroupIndex(fg.index);
190: changed();
191: }
192: }
193:
194: public String getFetchGroup() {
195: int i = queryDetails.getFetchGroupIndex();
196: if (i == 0)
197: return null;
198: ClassMetaData cmd = getRealPM().modelMetaData
199: .getClassMetaData(queryDetails.getCandidateClass());
200: return cmd.fetchGroups[i].name;
201: }
202:
203: public void setMaxRows(int amount) {
204: changed();
205: this .queryDetails.setMaxResultCount(amount);
206: }
207:
208: public int getMaxRows() {
209: return queryDetails.getMaxResultCount();
210: }
211:
212: public void setFetchSize(int value) {
213: changed();
214: this .queryDetails.setResultBatchSize(value);
215: }
216:
217: public int getFetchSize() {
218: return queryDetails.getResultBatchSize();
219: }
220:
221: public void setRandomAccess(boolean on) {
222: changed();
223: queryDetails.setRandomAccess(on);
224: }
225:
226: public boolean isRandomAccess() {
227: return queryDetails.isRandomAccess();
228: }
229:
230: public void setCountStarOnSize(boolean on) {
231: changed();
232: queryDetails.setCountOnSize(on);
233: }
234:
235: public boolean isCountStarOnSize() {
236: return queryDetails.isCountOnSize();
237: }
238:
239: public boolean getIgnoreCache() {
240: return queryDetails.isIgnoreCache();
241: }
242:
243: public void setEvictionClasses(Class[] classes,
244: boolean includeSubclasses) {
245: setEvictionClasses(getRealPM().modelMetaData
246: .convertToClassIndexes(classes, includeSubclasses));
247: }
248:
249: public void setEvictionClasses(int[] classIndexes) {
250: changed();
251: queryDetails.setExtraEvictClasses(classIndexes);
252: }
253:
254: public Class[] getEvictionClasses() {
255: int[] a = queryDetails.getExtraEvictClasses();
256: return a == null ? null : getRealPM().modelMetaData
257: .convertFromClassIndexes(a);
258: }
259:
260: public void setResult(String result) {
261: changed();
262: queryDetails.setResult(result);
263: }
264:
265: public void setGrouping(String grouping) {
266: changed();
267: queryDetails.setGrouping(grouping);
268: }
269:
270: /**
271: * Specify that there is a single result of the query.
272: */
273: public void setUnique(boolean unique) {
274: changed();
275: queryDetails.setUnique(unique);
276: }
277:
278: public void compile() {
279: if (compiledQuery == null) {
280: queryDetails.updateCounts();
281: compiledQuery = pmProxy.getRealPM().getStorageManager()
282: .compileQuery(queryDetails);
283: }
284: }
285:
286: private void checkParamCount(int n) {
287: int tc = queryDetails.getTotalParamCount();
288: if (tc >= 0 && n != tc) {
289: throw BindingSupportImpl.getInstance().runtime(
290: "Expected " + queryDetails.getTotalParamCount()
291: + " parameters, have " + n);
292: }
293: }
294:
295: public Object execute() {
296: checkParamCount(0);
297: queryDetails.updateCounts();
298: return executeWithArrayImp(null);
299: }
300:
301: public Object execute(Object p1) {
302: checkParamCount(1);
303: queryDetails.updateCounts();
304: if (queryDetails.hasJdoGenieOptions()) {
305: processJdoGenieOptions(p1);
306: return executeWithArrayImp(null);
307: } else {
308: return executeWithArrayImp(new Object[] { p1 });
309: }
310: }
311:
312: public Object execute(Object p1, Object p2) {
313: checkParamCount(2);
314: queryDetails.updateCounts();
315: switch (queryDetails.getOptionsParamIndex()) {
316: case 0:
317: processJdoGenieOptions(p1);
318: return executeWithArrayImp(new Object[] { p2 });
319: case 1:
320: processJdoGenieOptions(p2);
321: return executeWithArrayImp(new Object[] { p1 });
322: }
323: return executeWithArrayImp(new Object[] { p1, p2 });
324: }
325:
326: public Object execute(Object p1, Object p2, Object p3) {
327: checkParamCount(3);
328: queryDetails.updateCounts();
329: switch (queryDetails.getOptionsParamIndex()) {
330: case 0:
331: processJdoGenieOptions(p1);
332: return executeWithArrayImp(new Object[] { p2, p3 });
333: case 1:
334: processJdoGenieOptions(p2);
335: return executeWithArrayImp(new Object[] { p1, p3 });
336: case 2:
337: processJdoGenieOptions(p2);
338: return executeWithArrayImp(new Object[] { p1, p2 });
339: }
340: return executeWithArrayImp(new Object[] { p1, p2, p3 });
341: }
342:
343: public final Object executeWithArray(Object[] parameters) {
344: queryDetails.updateCounts();
345: int n = parameters == null ? 0 : parameters.length;
346: checkParamCount(n);
347: int oi = queryDetails.getOptionsParamIndex();
348: if (oi >= 0) {
349: processJdoGenieOptions(parameters[oi]);
350: if (n == 1) {
351: return executeWithArrayImp(null);
352: }
353: Object[] a = new Object[n - 1];
354: if (oi > 0)
355: System.arraycopy(parameters, 0, a, 0, oi);
356: if (oi < n)
357: System.arraycopy(parameters, oi + 1, a, oi, n - oi - 1);
358: return executeWithArrayImp(a);
359: } else {
360: return executeWithArrayImp(copyParams(parameters));
361: }
362: }
363:
364: /**
365: * Get the query plan for this query. This will include the SQL and
366: * possibly also a query plan for the SQL from the database itself.
367: */
368: public VersantQueryPlan getPlan(Object[] parameters) {
369: queryDetails.updateCounts();
370: if (queryDetails.getCol() != null) {
371: throw BindingSupportImpl
372: .getInstance()
373: .invalidOperation(
374: "getPlan is not supported for queries executed against a collection");
375: }
376: int n = parameters == null ? 0 : parameters.length;
377: checkParamCount(n);
378: int oi = queryDetails.getOptionsParamIndex();
379: if (oi >= 0) {
380: processJdoGenieOptions(parameters[oi]);
381: if (n == 1) {
382: return getPlanImp(null);
383: }
384: Object[] a = new Object[n - 1];
385: if (oi > 0)
386: System.arraycopy(parameters, 0, a, 0, oi);
387: if (oi < n)
388: System.arraycopy(parameters, oi, a, oi - 1, n - oi - 1);
389: return getPlanImp(a);
390: } else {
391: return getPlanImp(parameters);
392: }
393: }
394:
395: public Object executeWithMap(Map parameters) {
396: queryDetails.updateCounts();
397: int tp = queryDetails.getTotalParamCount();
398: if (parameters.size() != tp) {
399: throw BindingSupportImpl
400: .getInstance()
401: .runtime(
402: "The number of entries in the map ("
403: + parameters.size()
404: + ") "
405: + "differs from the number of declared parameters ("
406: + tp + ")");
407: }
408: if (tp == 0)
409: return executeWithArrayImp(null);
410:
411: // extract the normal parameters from the map in declaration order
412: Object[] pa;
413: int np = queryDetails.getParamCount();
414: if (np > 0) {
415: pa = new Object[np];
416: String[] names = queryDetails.getParamNames();
417: for (int i = 0; i < np; i++) {
418: String name = names[i];
419: if (!parameters.containsKey(name)) {
420: throw BindingSupportImpl.getInstance()
421: .runtime(
422: "Parameter '" + name
423: + "' not found in map");
424: }
425: pa[i] = parameters.get(name);
426: }
427: } else {
428: pa = null;
429: }
430:
431: // process the jdoGenieOptions parameter if required
432: if (queryDetails.hasJdoGenieOptions()) {
433: Object o = parameters.get(VERSANT_OPTIONS);
434: if (o == null)
435: o = parameters.get(JDO_GENIE_OPTIONS);
436: processJdoGenieOptions(o);
437: }
438:
439: // exec
440: return executeWithArrayImp(pa);
441: }
442:
443: private void processJdoGenieOptions(Object o) {
444: // restore default values first
445: setFetchGroup(null);
446: setRandomAccess(false);
447:
448: // now set properties
449: if (o == null)
450: return;
451: if (!(o instanceof String)) {
452: throw BindingSupportImpl.getInstance().runtime(
453: "Invalid " + VERSANT_OPTIONS
454: + ": Expected String value: "
455: + o.getClass());
456: }
457: String props = (String) o;
458: if (props.length() == 0)
459: return;
460: try {
461: BeanUtils.parseProperties(props, this );
462: } catch (Exception e) {
463: throw BindingSupportImpl.getInstance().runtime(
464: "Invalid " + VERSANT_OPTIONS + ": "
465: + e.getMessage(), e);
466: }
467: }
468:
469: /**
470: * The parameters array must NOT contain the jdoGenieOptions.
471: */
472: private final Object executeWithArrayImp(Object[] parameters) {
473: final PMProxy pmProxy = this .pmProxy;
474: pmProxy.getRealPM().convertPcParamsToOID(parameters);
475: if (!pmProxy.isActive()
476: && !pmProxy.getRealPM().isNontransactionalRead()) {
477: throw BindingSupportImpl.getInstance().invalidOperation(
478: "Must set nonTransactionalRead to true");
479: }
480:
481: //Check if this should be a multi-part query
482: Class cls = queryDetails.getCandidateClass();
483: Class[] candidates = null;
484: if (cls != null) {
485: candidates = pmProxy.getRealPM().modelMetaData
486: .getQueryCandidatesFor(cls);
487: if (candidates == null) {
488: throw BindingSupportImpl.getInstance().unsupported(
489: "Queries for class '"
490: + queryDetails.getCandidateClass()
491: + "' is not supported");
492: }
493: }
494:
495: if (candidates != null && candidates.length > 1) {
496: //create subQueries for all the candidates and compile it
497: queryDetails.updateCounts();
498: Set qResults = new HashSet();
499: for (int i = 0; i < candidates.length; i++) {
500: Class candidate = candidates[i];
501:
502: QueryDetails qd = new QueryDetails(queryDetails);
503: qd.setCandidateClass(candidate);
504:
505: CompiledQuery cq = this .pmProxy.getRealPM()
506: .getStorageManager().compileQuery(qd);
507: QueryResult qr = getQueryResult(parameters, qd, cq,
508: this .pmProxy);
509:
510: qResults.add(qr);
511: }
512: return new MultiPartQueryResult(qResults);
513: } else {
514: compile();
515:
516: //is this a unique query
517: if (compiledQuery.isUnique()) {
518: //must do immediately
519: return QueryResultBase.resolveRow(pmProxy
520: .getAllQueryResults(compiledQuery, parameters)
521: .getUnique(), pmProxy);
522: } else {
523: return getQueryResult(parameters, queryDetails,
524: compiledQuery, this .pmProxy);
525: }
526: }
527: }
528:
529: private static Object[] copyParams(Object[] parameters) {
530: Object[] params = null;
531: if (parameters != null) {
532: params = new Object[parameters.length];
533: for (int i = 0; i < params.length; i++) {
534: params[i] = parameters[i];
535: }
536: }
537: return params;
538: }
539:
540: private QueryResult getQueryResult(Object[] params,
541: QueryDetails queryDetails, CompiledQuery compiledQuery,
542: PMProxy pmProxy) {
543: QueryResult res = null;
544: boolean collectionQuery = queryDetails.getCol() != null;
545: boolean containsNewOID = containsNewOID(params);
546:
547: if (collectionQuery) {
548: // query agains a collection in memory
549: res = new MemoryQueryResult(pmProxy, queryDetails,
550: createSMList(queryDetails.getCol(), pmProxy),
551: params);
552: } else if (containsNewOID && queryDetails.isIgnoreCache()) {
553: // query agains a collection in memory with some new instances
554: res = new MemoryQueryResult();
555: } else if (queryDetails.isRandomAccess()) {
556: // random access query against database
557: res = new RandomAccessQueryResult(pmProxy, compiledQuery,
558: params);
559: } else {
560: // normal query against database
561: res = new ForwardQueryResult(pmProxy, queryDetails,
562: compiledQuery, params);
563: }
564: if (pmProxy.getMultithreaded()) {
565: res = new SynchronizedQueryResult(pmProxy, res);
566: }
567:
568: addResults(res);
569: return res;
570: }
571:
572: /**
573: * Add a set of results to this query.
574: */
575: private void addResults(QueryResult q) {
576: synchronized (pmProxy) {
577: if (resultList == null) {
578: resultList = q;
579: } else {
580: q.setNext(resultList);
581: resultList.setPrev(q);
582: resultList = q;
583: }
584: }
585: }
586:
587: /**
588: * Remove a set of results from this query.
589: */
590: private void removeResults(QueryResult q) {
591: synchronized (pmProxy) {
592: if (resultList == q) { // at tail of list
593: resultList = q.getNext();
594: if (resultList != null)
595: resultList.setPrev(null);
596: q.setNext(null);
597: } else {
598: q.getPrev().setNext(q.getNext());
599: if (q.getNext() != null)
600: q.getNext().setPrev(q.getPrev());
601: q.setNext(null);
602: q.setPrev(null);
603: }
604: }
605: }
606:
607: /**
608: * TODO must check not to try and do convertPcParams twice
609: * <p/>
610: * The parameters array must NOT contain the jdoGenieOptions.
611: */
612: private VersantQueryPlan getPlanImp(Object[] parameters) {
613: VersantPersistenceManagerImp realPM = pmProxy.getRealPM();
614: realPM.convertPcParamsToOID(parameters);
615: compile();
616: return realPM.getStorageManager().getQueryPlan(queryDetails,
617: compiledQuery, parameters);
618: }
619:
620: private static List createSMList(Collection col, PMProxy pmProxy) {
621: List tmpList = new ArrayList();
622: for (Iterator iterator = col.iterator(); iterator.hasNext();) {
623: PersistenceCapable persistenceCapable = (PersistenceCapable) iterator
624: .next();
625: tmpList.add(pmProxy.getInternalSM(persistenceCapable));
626: }
627: return tmpList;
628: }
629:
630: private static boolean containsNewOID(Object[] params) {
631: if (params != null) {
632: for (int i = 0; i < params.length; i++) {
633: Object param = params[i];
634: if (param instanceof NewObjectOID) {
635: return true;
636: }
637: }
638: }
639: return false;
640: }
641:
642: public PersistenceManager getPersistenceManager() {
643: return pmProxy;
644: }
645:
646: public void close(Object queryResult) {
647: QueryResult qr = (QueryResult) queryResult;
648: qr.close();
649: removeResults(qr);
650: }
651:
652: public void closeAll() {
653: synchronized (pmProxy) {
654: if (resultList == null)
655: return;
656: resultList.close();
657: for (QueryResult i = resultList.getNext(); i != null; i = i
658: .getNext()) {
659: i.close();
660: i.getPrev().setNext(null);
661: i.setPrev(null);
662: }
663: resultList = null;
664: }
665: }
666:
667: public void writeExternal(ObjectOutput out) throws IOException {
668: queryDetails.writeExternal(out);
669: }
670:
671: public void readExternal(ObjectInput in) throws IOException,
672: ClassNotFoundException {
673: QueryDetails qp = new QueryDetails();
674: qp.readExternal(in);
675: this .queryDetails.fillFrom(qp);
676: }
677:
678: public void initialiseFrom(VersantQueryImp clientQuery) {
679: this .queryDetails.fillFrom(clientQuery.queryDetails);
680: this .queryDetails.clearExtentAndCol();
681: }
682:
683: public void setCacheable(boolean on) {
684: changed();
685: queryDetails.setCacheable(on);
686: }
687:
688: public String getImports() {
689: return queryDetails.getImports();
690: }
691:
692: public String getParameters() {
693: return queryDetails.getParameters();
694: }
695:
696: public String getVariables() {
697: return queryDetails.getVariables();
698: }
699:
700: public String getOrdering() {
701: return queryDetails.getOrdering();
702: }
703:
704: public String getGrouping() {
705: return queryDetails.getGrouping();
706: }
707:
708: public String getResult() {
709: return queryDetails.getResult();
710: }
711:
712: public boolean isUnique() {
713: return queryDetails.getUnique() == QueryDetails.TRUE;
714: }
715: }
|