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