001: /*
002: * Copyright 2002 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: Query.java,v 1.5 2003/08/11 16:03:57 pierreg0 Exp $
009: */
010:
011: package com.triactive.jdo.store;
012:
013: import com.triactive.jdo.PersistenceManager;
014: import com.triactive.jdo.util.Imports;
015: import java.sql.ResultSet;
016: import java.util.ArrayList;
017: import java.util.Collection;
018: import java.util.HashSet;
019: import java.util.HashMap;
020: import java.util.List;
021: import java.util.Map;
022: import java.util.StringTokenizer;
023: import javax.jdo.Extent;
024: import javax.jdo.JDOUserException;
025:
026: /**
027: * The Query interface allows applications to obtain persistent instances
028: * from the data store.
029: *
030: * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
031: * @version $Revision: 1.5 $
032: *
033: * @see javax.jdo.Query
034: */
035:
036: public abstract class Query implements javax.jdo.Query {
037: protected final transient PersistenceManager pm;
038: protected final transient StoreManager storeMgr;
039: protected final transient DatabaseAdapter dba;
040:
041: protected Class candidateClass;
042: protected String filter;
043: protected String imports;
044: protected String variables;
045: protected String parameters;
046: protected String ordering;
047:
048: protected transient boolean isCompiled = false;
049: protected transient Imports parsedImports = null;
050: protected transient List parameterNames = null;
051: protected transient Map parameterTypesByName = null;
052: protected transient List variableNames = null;
053: protected transient Map variableTypesByName = null;
054:
055: protected transient HashSet queryResults = new HashSet();
056:
057: /**
058: * Constructs a new query instance that uses the given persistence manager.
059: *
060: * @param pm the associated persistence manager for this query.
061: */
062:
063: public Query(PersistenceManager pm, StoreManager storeMgr) {
064: this .pm = pm;
065: this .storeMgr = storeMgr;
066:
067: dba = storeMgr == null ? null : storeMgr.getDatabaseAdapter();
068: }
069:
070: protected void discardCompiled() {
071: isCompiled = false;
072: parsedImports = null;
073: parameterNames = null;
074: parameterTypesByName = null;
075: variableNames = null;
076: variableTypesByName = null;
077: }
078:
079: public boolean equals(Object obj) {
080: if (obj == this )
081: return true;
082:
083: if (!(obj instanceof Query))
084: return false;
085:
086: Query q = (Query) obj;
087:
088: if (candidateClass == null) {
089: if (q.candidateClass != null)
090: return false;
091: } else if (!candidateClass.equals(q.candidateClass))
092: return false;
093:
094: if (filter == null) {
095: if (q.filter != null)
096: return false;
097: } else if (!filter.equals(q.filter))
098: return false;
099:
100: if (imports == null) {
101: if (q.imports != null)
102: return false;
103: } else if (!imports.equals(q.imports))
104: return false;
105:
106: if (parameters == null) {
107: if (q.parameters != null)
108: return false;
109: } else if (!parameters.equals(q.parameters))
110: return false;
111:
112: if (variables == null) {
113: if (q.variables != null)
114: return false;
115: } else if (!variables.equals(q.variables))
116: return false;
117:
118: if (ordering == null) {
119: if (q.ordering != null)
120: return false;
121: } else if (!ordering.equals(q.ordering))
122: return false;
123:
124: return true;
125: }
126:
127: public int hashCode() {
128: return (candidateClass == null ? 0 : candidateClass.hashCode())
129: ^ (filter == null ? 0 : filter.hashCode())
130: ^ (imports == null ? 0 : imports.hashCode())
131: ^ (parameters == null ? 0 : parameters.hashCode())
132: ^ (variables == null ? 0 : variables.hashCode())
133: ^ (ordering == null ? 0 : ordering.hashCode());
134: }
135:
136: /**
137: * Get the StoreManager associated with this Query.
138: *
139: * @return the StoreManager associated with this Query.
140: */
141:
142: public StoreManager getStoreManager() {
143: return storeMgr;
144: }
145:
146: /**
147: * Get the PersistenceManager associated with this Query.
148: *
149: * @return the PersistenceManager associated with this Query.
150: *
151: * @see javax.jdo.Query#getPersistenceManager
152: */
153:
154: public javax.jdo.PersistenceManager getPersistenceManager() {
155: return pm;
156: }
157:
158: /**
159: * Get the class of the candidate instances of the query.
160: *
161: * @return the Class of the candidate instances.
162: *
163: * @see javax.jdo.Query#setClass
164: */
165:
166: public Class getCandidateClass() {
167: return candidateClass;
168: }
169:
170: /**
171: * Set the class of the candidate instances of the query.
172: *
173: * @param candidateClass the Class of the candidate instances.
174: *
175: * @see javax.jdo.Query#setClass
176: */
177:
178: public void setClass(Class candidateClass) {
179: discardCompiled();
180:
181: this .candidateClass = candidateClass;
182: }
183:
184: /**
185: * Set the candidate Extent to query.
186: *
187: * @param pcs the Candidate Extent.
188: *
189: * @see javax.jdo.Query#setCandidates(javax.jdo.Extent)
190: */
191:
192: public abstract void setCandidates(Extent pcs);
193:
194: /**
195: * Set the candidate Collection to query.
196: *
197: * @param pcs the Candidate collection.
198: *
199: * @see javax.jdo.Query#setCandidates(java.util.Collection)
200: */
201:
202: public abstract void setCandidates(Collection pcs);
203:
204: /**
205: * Set the filter for the query.
206: *
207: * @param filter the query filter.
208: *
209: * @see javax.jdo.Query#setFilter
210: */
211:
212: public void setFilter(String filter) {
213: discardCompiled();
214:
215: this .filter = filter;
216: }
217:
218: /**
219: * Set the import statements to be used to identify the fully qualified
220: * name of variables or parameters.
221: *
222: * @param imports import statements separated by semicolons.
223: *
224: * @see javax.jdo.Query#declareImports
225: */
226:
227: public void declareImports(String imports) {
228: discardCompiled();
229:
230: this .imports = imports;
231: }
232:
233: /**
234: * Declare the list of parameters query execution.
235: *
236: * @param parameters the list of parameters separated by commas.
237: *
238: * @see javax.jdo.Query#declareParameters
239: */
240:
241: public void declareParameters(String parameters) {
242: discardCompiled();
243:
244: this .parameters = parameters;
245: }
246:
247: /**
248: * Declare the unbound variables to be used in the query.
249: *
250: * @param variables the variables separated by semicolons.
251: *
252: * @see javax.jdo.Query#declareVariables
253: */
254:
255: public void declareVariables(String variables) {
256: discardCompiled();
257:
258: this .variables = variables;
259: }
260:
261: /**
262: * Set the ordering specification for the result Collection.
263: *
264: * @param ordering the ordering specification.
265: *
266: * @see javax.jdo.Query#setOrdering
267: */
268:
269: public void setOrdering(String ordering) {
270: discardCompiled();
271:
272: this .ordering = ordering;
273: }
274:
275: /**
276: * Set the ignoreCache option.
277: *
278: * <p>This implementation does not support the ignore cache option, so this
279: * method does nothing.
280: *
281: * @param ignoreCache the setting of the ignoreCache option.
282: *
283: * @see javax.jdo.Query#setIgnoreCache
284: */
285:
286: public void setIgnoreCache(boolean ignoreCache) {
287: discardCompiled();
288:
289: return;
290: }
291:
292: /**
293: * Get the ignoreCache option setting.
294: *
295: * <p>This implementation does not support the ignore cache option, so this
296: * method always returns <tt>false</tt>.
297: *
298: * @return the ignoreCache option setting, always <tt>false</tt>.
299: *
300: * @see javax.jdo.Query#getIgnoreCache
301: * @see #setIgnoreCache
302: */
303:
304: public boolean getIgnoreCache() {
305: return false;
306: }
307:
308: /**
309: * Verify the elements of the query and provide a hint to the query to
310: * prepare and optimize an execution plan.
311: *
312: * @see javax.jdo.Query#compile
313: */
314:
315: public void compile() {
316: if (isCompiled)
317: return;
318:
319: if (pm == null)
320: throw new JDOUserException(
321: "Query has no associated PersistenceManager");
322:
323: boolean done = false;
324:
325: try {
326: /*
327: * Parse imports list.
328: */
329:
330: parsedImports = new Imports();
331: parsedImports.importPackage(candidateClass);
332:
333: if (imports != null)
334: parsedImports.parseImports(imports);
335:
336: /*
337: * Parse parameters list.
338: */
339:
340: parameterNames = new ArrayList();
341: parameterTypesByName = new HashMap();
342:
343: if (parameters != null && parameters.length() > 0) {
344: StringTokenizer t1 = new StringTokenizer(parameters,
345: ",");
346:
347: while (t1.hasMoreTokens()) {
348: StringTokenizer t2 = new StringTokenizer(t1
349: .nextToken(), " ");
350:
351: if (t2.countTokens() != 2)
352: throw new JDOUserException(
353: "Invalid parameter list: \""
354: + parameters + '"');
355:
356: String classDecl = t2.nextToken();
357: String parameterName = t2.nextToken();
358:
359: if (!isValidJavaIdentifier(parameterName))
360: throw new JDOUserException(
361: "Illegal parameter name \""
362: + parameterName + '"');
363:
364: if (parameterNames.contains(parameterName))
365: throw new JDOUserException(
366: "Duplicate parameter name \""
367: + parameterName + '"');
368:
369: parameterNames.add(parameterName);
370: parameterTypesByName.put(parameterName,
371: resolveClassDeclaration(classDecl));
372: }
373: }
374:
375: /*
376: * Parse variables list.
377: */
378:
379: variableNames = new ArrayList();
380: variableTypesByName = new HashMap();
381:
382: if (variables != null && variables.length() > 0) {
383: StringTokenizer t1 = new StringTokenizer(variables, ";");
384:
385: while (t1.hasMoreTokens()) {
386: StringTokenizer t2 = new StringTokenizer(t1
387: .nextToken(), " ");
388:
389: if (t2.countTokens() != 2)
390: throw new JDOUserException(
391: "Invalid variable list: \"" + variables
392: + '"');
393:
394: String classDecl = t2.nextToken();
395: String variableName = t2.nextToken();
396:
397: if (!isValidJavaIdentifier(variableName))
398: throw new JDOUserException(
399: "Illegal variable name \""
400: + variableName + '"');
401:
402: if (parameterNames.contains(variableName))
403: throw new JDOUserException(
404: "Variable name conflicts with parameter name \""
405: + variableName + '"');
406:
407: if (variableNames.contains(variableName))
408: throw new JDOUserException(
409: "Duplicate variable name \""
410: + variableName + '"');
411:
412: variableNames.add(variableName);
413: variableTypesByName.put(variableName,
414: resolveClassDeclaration(classDecl));
415: }
416: }
417:
418: done = true;
419: } finally {
420: if (!done)
421: discardCompiled();
422: }
423: }
424:
425: protected static boolean isValidJavaIdentifier(String s) {
426: int len = s.length();
427:
428: if (len < 1)
429: return false;
430:
431: char[] c = new char[len];
432: s.getChars(0, len, c, 0);
433:
434: if (!Character.isJavaIdentifierStart(c[0]))
435: return false;
436:
437: for (int i = 1; i < len; ++i) {
438: if (!Character.isJavaIdentifierPart(c[i]))
439: return false;
440: }
441:
442: return true;
443: }
444:
445: public Class resolveClassDeclaration(String classDecl) {
446: try {
447: return parsedImports.resolveClassDeclaration(classDecl);
448: } catch (ClassNotFoundException e) {
449: throw new JDOUserException(
450: "Declared parameter or variable type not found: "
451: + classDecl);
452: }
453: }
454:
455: /**
456: * Execute the query and return the filtered Collection.
457: *
458: * @return the filtered Collection.
459: *
460: * @see javax.jdo.Query#execute()
461: * @see #executeWithArray(Object[] parameters)
462: */
463:
464: public Object execute() {
465: return executeWithArray(new Object[0]);
466: }
467:
468: /**
469: * Execute the query and return the filtered Collection.
470: *
471: * @param p1 the value of the first parameter declared.
472: *
473: * @return the filtered Collection.
474: *
475: * @see javax.jdo.Query#execute(Object)
476: * @see #executeWithArray(Object[] parameters)
477: */
478:
479: public Object execute(Object p1) {
480: return executeWithArray(new Object[] { p1 });
481: }
482:
483: /**
484: * Execute the query and return the filtered Collection.
485: *
486: * @param p1 the value of the first parameter declared.
487: * @param p2 the value of the second parameter declared.
488: *
489: * @return the filtered Collection.
490: *
491: * @see javax.jdo.Query#execute(Object,Object)
492: * @see #executeWithArray(Object[] parameters)
493: */
494:
495: public Object execute(Object p1, Object p2) {
496: return executeWithArray(new Object[] { p1, p2 });
497: }
498:
499: /**
500: * Execute the query and return the filtered Collection.
501: *
502: * @param p1 the value of the first parameter declared.
503: * @param p2 the value of the second parameter declared.
504: * @param p3 the value of the third parameter declared.
505: *
506: * @return the filtered Collection.
507: *
508: * @see javax.jdo.Query#execute(Object,Object,Object)
509: * @see #executeWithArray(Object[] parameters)
510: */
511:
512: public Object execute(Object p1, Object p2, Object p3) {
513: return executeWithArray(new Object[] { p1, p2, p3 });
514: }
515:
516: /**
517: * Execute the query and return the filtered Collection.
518: *
519: * @param parameters the Object array with all of the parameters.
520: *
521: * @return the filtered Collection.
522: *
523: * @see javax.jdo.Query#executeWithArray(Object[])
524: */
525:
526: public Object executeWithArray(Object[] parameters) {
527: compile();
528:
529: if (parameters.length != parameterNames.size())
530: throw new JDOUserException(
531: "Incorrect number of parameters: "
532: + parameters.length + ", s/b "
533: + parameterNames.size());
534:
535: HashMap parameterMap = new HashMap();
536:
537: for (int i = 0; i < parameters.length; ++i)
538: parameterMap.put(parameterNames.get(i), parameters[i]);
539:
540: return executeWithMap(parameterMap);
541: }
542:
543: /**
544: * Execute the query and return the filtered Collection.
545: *
546: * @param parameters the Map containing all of the parameters.
547: *
548: * @return the filtered Collection.
549: *
550: * @see javax.jdo.Query#executeWithMap(Map)
551: * @see #executeWithArray(Object[] parameters)
552: */
553:
554: public abstract Object executeWithMap(Map parameters);
555:
556: /**
557: * Close a query result and release any resources associated with it.
558: *
559: * @param queryResult the result of execute(...) on this Query instance.
560: *
561: * @see javax.jdo.Query#close
562: */
563:
564: public void close(Object queryResult) {
565: if (queryResult != null) {
566: ((QueryResult) queryResult).close();
567: queryResults.remove(queryResult);
568: }
569: }
570:
571: /**
572: * Close all query results associated with this Query instance, and release all
573: * resources associated with them.
574: *
575: * @see javax.jdo.Query#closeAll
576: */
577:
578: public void closeAll() {
579: QueryResult[] qrs = (QueryResult[]) queryResults
580: .toArray(new QueryResult[queryResults.size()]);
581:
582: for (int i = 0; i < qrs.length; ++i)
583: close(qrs[i]);
584: }
585:
586: /**
587: * An object that reads result set rows and returns corresponding persistent
588: * objects from them. Different queries accomplish this in different ways,
589: * so a query supplies a suitable ResultObjectFactory to each QueryResult
590: * when it is executed. The QueryResult only uses it to turn ResultSet rows
591: * into objects and otherwise manages the ResultSet itself.
592: *
593: * @see QueryResult
594: */
595:
596: public static interface ResultObjectFactory {
597: /**
598: * Instantiates a persistent object instance from the current row of
599: * the given result set.
600: *
601: * @param rs The result set. The contents of the current row are
602: * used to locate or create a corresponding persistent
603: * object instance.
604: *
605: * @return A persistent object instance.
606: */
607:
608: Object getObject(ResultSet rs);
609: }
610: }
|