001: /**********************************************************************
002: Copyright (c) 2007 Andy Jefferson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: ...
017: **********************************************************************/package org.jpox.store.query;
018:
019: import java.util.Collection;
020: import java.util.HashMap;
021: import java.util.Hashtable;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.StringTokenizer;
025:
026: import org.jpox.ClassLoaderResolver;
027: import org.jpox.ObjectManager;
028: import org.jpox.exceptions.JPOXUserException;
029: import org.jpox.plugin.ConfigurationElement;
030: import org.jpox.plugin.Extension;
031: import org.jpox.plugin.PluginManager;
032: import org.jpox.store.Extent;
033: import org.jpox.store.expression.QueryExpression;
034: import org.jpox.util.Imports;
035: import org.jpox.util.JPOXLogger;
036:
037: /**
038: * Abstract representation of a Java-based query.
039: * To be extended by Java-based query languages.
040: *
041: * @version $Revision$
042: */
043: public abstract class AbstractJavaQuery extends Query {
044: /** Extent of candidates for this query. */
045: protected transient Extent candidateExtent = null;
046:
047: /** Collection of candidates for this query. */
048: protected transient Collection candidateCollection = null;
049:
050: /** Candidates for this query. */
051: protected transient Queryable candidates = null;
052:
053: /** The Query Statement. */
054: protected transient QueryExpression queryStmt = null;
055:
056: /** Factory for obtaining the results from the query result set. */
057: protected transient ResultObjectFactory rof = null;
058:
059: /** whether to apply "distinct" results. **/
060: protected transient boolean distinct = false;
061:
062: /** Result metadata (JPOX extension, allowing access to more info). **/
063: protected transient JPOXResultSetMetaData resultMetaData;
064:
065: /** Cached form of the single string form of the query. */
066: protected String singleString = null;
067:
068: /** Register of user-defined ScalarExpression, provided via plugins. */
069: protected static transient Map userDefinedScalarExpressions = new Hashtable();
070:
071: /**
072: * Constructor for a Java-based query.
073: * @param om The ObjectManager
074: */
075: public AbstractJavaQuery(ObjectManager om) {
076: super (om);
077: registerScalarExpressions(
078: om.getOMFContext().getPluginManager(), om
079: .getClassLoaderResolver());
080: }
081:
082: /**
083: * Set the candidate Extent to query.
084: * @param pcs the Candidate Extent.
085: * @see javax.jdo.Query#setCandidates(javax.jdo.Extent)
086: */
087: public void setCandidates(Extent pcs) {
088: discardCompiled();
089: assertIsModifiable();
090:
091: if (pcs == null) {
092: JPOXLogger.QUERY.warn(LOCALISER.msg("021073"));
093: return;
094: }
095:
096: setSubclasses(pcs.hasSubclasses());
097: setClass(pcs.getCandidateClass());
098:
099: candidateExtent = pcs;
100: candidateCollection = null; // We have an Extent, so remove any collection
101: }
102:
103: /**
104: * Set the candidate Collection to query.
105: * @param pcs the Candidate collection.
106: * @see javax.jdo.Query#setCandidates(java.util.Collection)
107: */
108: public void setCandidates(Collection pcs) {
109: discardCompiled();
110: assertIsModifiable();
111: if (pcs == null) {
112: JPOXLogger.QUERY.warn(LOCALISER.msg("021072"));
113: return;
114: }
115:
116: candidateExtent = null;
117: candidateCollection = pcs; // We have a Collection, so remove any Extent
118: }
119:
120: /**
121: * Accessor for the candidate Extent (if specified using an Extent).
122: * @return Candidate Extent
123: */
124: public Extent getCandidateExtent() {
125: return candidateExtent;
126: }
127:
128: /**
129: * Accessor for the candidate collection (if specified using a collection).
130: * @return Candidate collection
131: */
132: public Collection getCandidateCollection() {
133: return candidateCollection;
134: }
135:
136: /**
137: * Accessor for the candidates for the query.
138: * This is only valid after compiling the query.
139: * @return Candidates for the query
140: */
141: public Queryable getCandidates() {
142: return candidates;
143: }
144:
145: /**
146: * Method to discard our current compiled query due to changes.
147: * @see org.jpox.store.query.Query#discardCompiled()
148: */
149: protected void discardCompiled() {
150: super .discardCompiled();
151: singleString = null;
152: queryStmt = null;
153: rof = null;
154: parsedImports = null;
155: }
156:
157: /**
158: * Accessor for the parsed imports.
159: * If no imports are set then adds candidate class, user imports, and any user-defined expression packages.
160: * @return Parsed imports
161: */
162: protected Imports getParsedImports() {
163: if (parsedImports == null) {
164: super .getParsedImports();
165:
166: // Add any imports for expressions etc
167: Iterator it = userDefinedScalarExpressions.keySet()
168: .iterator();
169: while (it.hasNext()) {
170: parsedImports.importClass((String) it.next());
171: }
172: }
173: return parsedImports;
174: }
175:
176: /**
177: * Retrieve the metadata for the results
178: * @return the ResultSetMetaData
179: */
180: public JPOXResultSetMetaData getResultSetMetaData() {
181: if (resultMetaData == null) {
182: throw new JPOXUserException(
183: "You must compile the query before calling this method.");
184: }
185: return resultMetaData;
186: }
187:
188: /**
189: * Convenience method to simple-parse the "result" clause returning if it includes
190: * solely aggregates. This is necessary since we need this information before creating the
191: * QueryStatement, after which we compile the result.
192: * @param result The result required
193: * @return Whether it has only aggregates
194: */
195: public boolean resultHasOnlyAggregates(String result) {
196: if (result == null) {
197: return false;
198: }
199:
200: String resultDefn = result;
201: if (resultDefn.toLowerCase().startsWith("distinct")) {
202: resultDefn = resultDefn.substring(8);
203: }
204: StringTokenizer tokenizer = new StringTokenizer(resultDefn, ",");
205: while (tokenizer.hasMoreTokens()) {
206: String token = tokenizer.nextToken().trim().toLowerCase();
207: if (token.startsWith("max") || token.startsWith("min")
208: || token.startsWith("avg")
209: || token.startsWith("sum")) {
210: token = token.substring(3).trim();
211: if (token.startsWith("(")) {
212: // Aggregate
213: } else {
214: // Not aggregate (some name that starts min, max, avg, sum etc)
215: return false;
216: }
217: } else if (token.startsWith("count")) {
218: token = token.substring(5).trim();
219: if (token.startsWith("(")) {
220: // Aggregate
221: } else {
222: // Not aggregate (some name that starts count...)
223: return false;
224: }
225: } else {
226: // Doesnt start with aggregate keyword so is not an aggregate
227: return false;
228: }
229: }
230:
231: return true;
232: }
233:
234: /**
235: * Accessor for a single string form of the query.
236: * @return Single string form of the query.
237: */
238: public abstract String getSingleStringQuery();
239:
240: /**
241: * Stringifier method
242: * @return Single-string form of this JDOQL query.
243: */
244: public String toString() {
245: return getSingleStringQuery();
246: }
247:
248: /**
249: * Register ScalarExpressions for the given <code>cls</code>. It allows
250: * to perform operations in the query on <i>cls.method([arglist])</i>.
251: * @param literal the class providing the operations; e.g. java.lang.Math.class
252: * @param scalarExpressionClass the class with the corresponding ScalarExpression. eg. org.jpox.store.expression.MathExpression.class
253: * @param name alternative name of the given literal class
254: */
255: public static void registerScalarExpression(Class literal,
256: Class scalarExpressionClass, String name) {
257: userDefinedScalarExpressions.put(name == null ? literal
258: .getName() : name, scalarExpressionClass);
259: }
260:
261: /**
262: * Register ScalarExpression classes delcared as plug-ins extensions
263: * TODO currently register only the first time this class is instantiated. Should be registered per PMF?
264: * @param pluginMgr The PluginManager
265: * @param clr The ClassLoaderResolver to load the literal and ScalarExpression classes
266: */
267: protected void registerScalarExpressions(PluginManager pluginMgr,
268: ClassLoaderResolver clr) {
269: if (userDefinedScalarExpressions.isEmpty()) {
270: Extension[] ex = pluginMgr.getExtensionPoint(
271: "org.jpox.store_expression_scalarexpression")
272: .getExtensions();
273: for (int i = 0; i < ex.length; i++) {
274: ConfigurationElement[] confElm = ex[i]
275: .getConfigurationElements();
276: for (int c = 0; c < confElm.length; c++) {
277: Class literalClass = null;
278: if (confElm[c].getAttribute("literal-class") != null) {
279: literalClass = pluginMgr.loadClass(confElm[c]
280: .getExtension().getPlugin()
281: .getSymbolicName(), confElm[c]
282: .getAttribute("literal-class"));
283: }
284: Class scalarExpression = null;
285: if (confElm[c]
286: .getAttribute("scalar-expression-class") != null) {
287: scalarExpression = pluginMgr
288: .loadClass(
289: confElm[c].getExtension()
290: .getPlugin()
291: .getSymbolicName(),
292: confElm[c]
293: .getAttribute("scalar-expression-class"));
294: }
295: registerScalarExpression(literalClass,
296: scalarExpression, confElm[c]
297: .getAttribute("name"));
298: }
299: }
300: }
301: }
302:
303: /**
304: * Accessor for the user-defined scalar expressions.
305: * @return Map of user-defined scalar expressions
306: */
307: public static Map getUserDefinedScalarExpressions() {
308: return userDefinedScalarExpressions;
309: }
310:
311: // -------------------------------------- Caching of compiled queries ----------------------------------------
312:
313: /** Some cache of compiled queries that is never used currently. */
314: protected static Map compiledCache = new HashMap();
315:
316: /**
317: * Accessor for a compiled form of this query in the cache (if present).
318: * @return Compiled form of this query if currently cached.
319: */
320: protected ExecutedCompileCache getCachedQuery() {
321: return (ExecutedCompileCache) compiledCache
322: .get(getSingleStringQuery());
323: }
324:
325: /**
326: * Cache a compiled query
327: */
328: public static class ExecutedCompileCache {
329: private final QueryExpression queryStmt;
330: private final String singleStringQuery;
331: private final Queryable candidates;
332: private final String[] parameterNames;
333:
334: public ExecutedCompileCache(QueryExpression queryStmt,
335: String singleStringQuery, Queryable candidates,
336: String[] parameterNames) {
337: this .queryStmt = queryStmt;
338: this .singleStringQuery = singleStringQuery;
339: this .candidates = candidates;
340: this .parameterNames = parameterNames;
341: }
342:
343: public QueryExpression getQueryExpression() {
344: return this .queryStmt;
345: }
346:
347: public Queryable getCandidates() {
348: return candidates;
349: }
350:
351: public String[] getParameterNames() {
352: return parameterNames;
353: }
354:
355: public int hashCode() {
356: return singleStringQuery.hashCode();
357: }
358:
359: public boolean equals(Object obj) {
360: if (obj == null) {
361: return false;
362: }
363: if (!(obj instanceof ExecutedCompileCache)) {
364: return false;
365: }
366: ExecutedCompileCache cached = (ExecutedCompileCache) obj;
367: return cached.queryStmt.equals(queryStmt)
368: && cached.singleStringQuery
369: .equals(singleStringQuery);
370: }
371: }
372: }
|