001: /**
002: * Speedo: an implementation of JDO compliant personality on top of JORM generic
003: * I/O sub-system.
004: * Copyright (C) 2001-2006 France Telecom
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * Contact: speedo@objectweb.org
021: *
022: * Authors: S. Chassande-Barrioz
023: *
024: */package org.objectweb.speedo.query.jdo;
025:
026: import org.objectweb.jorm.api.PException;
027: import org.objectweb.jorm.metainfo.api.Manager;
028: import org.objectweb.jorm.naming.api.PName;
029: import org.objectweb.medor.api.EvaluationException;
030: import org.objectweb.medor.api.MedorException;
031: import org.objectweb.medor.eval.lib.SelectEvaluator;
032: import org.objectweb.medor.expression.api.ExpressionException;
033: import org.objectweb.medor.expression.api.Operand;
034: import org.objectweb.medor.expression.api.ParameterOperand;
035: import org.objectweb.medor.query.api.OrderField;
036: import org.objectweb.medor.query.api.QueryTree;
037: import org.objectweb.medor.query.api.QueryTreeField;
038: import org.objectweb.medor.query.jorm.lib.QueryBuilder;
039: import org.objectweb.medor.query.lib.BasicOrderField;
040: import org.objectweb.medor.query.lib.QueryTreePrinter;
041: import org.objectweb.medor.query.lib.SelectProject;
042: import org.objectweb.medor.tuple.api.TupleCollection;
043: import org.objectweb.speedo.api.SpeedoException;
044: import org.objectweb.speedo.mim.api.HomeItf;
045: import org.objectweb.speedo.pm.jdo.api.JDOPOManagerItf;
046: import org.objectweb.speedo.query.api.QueryDefinition;
047: import org.objectweb.speedo.query.jdo.parser.ASTSpeedoQL;
048: import org.objectweb.speedo.query.jdo.parser.ParseException;
049: import org.objectweb.speedo.query.jdo.parser.SelectGroupByVisitor;
050: import org.objectweb.speedo.query.jdo.parser.SimpleNode;
051: import org.objectweb.speedo.query.jdo.parser.SpeedoQL;
052: import org.objectweb.speedo.query.jdo.parser.SpeedoQLQueryFilterVisitor;
053: import org.objectweb.speedo.query.jdo.parser.SpeedoQLVariableVisitor;
054: import org.objectweb.speedo.usercache.lib.UserCacheKey;
055: import org.objectweb.speedo.workingset.api.TransactionItf;
056: import org.objectweb.util.monolog.api.BasicLevel;
057:
058: import java.io.CharArrayReader;
059: import java.util.Collections;
060: import java.util.HashMap;
061: import java.util.Map;
062:
063: import javax.jdo.JDOException;
064:
065: /**
066: * JDOCompiledQuery object represents a jdo query. This object is created
067: * when a new query is created, and can be used several times. A list of
068: * JDOCompiledQuery is managed with a JDOQueryManager component.
069: * When a user creates a new JDO Query object (SpeedoQuery), a
070: * JDOCompiledQuery object is associated to the JDOQuery object which is
071: * used to delegate some methods.
072: *
073: * @author S.Chassande-Barrioz
074: */
075: public class JDOCompiledSelectQuery extends JDOAbstractCompiledQuery {
076:
077: private JDOQueryEvalContext[] qecs = null;
078: private Class[] selectedFieldTypes = null;
079:
080: public JDOQueryEvalContext[] getQueryEvalContext() {
081: return qecs;
082: }
083:
084: /**
085: * compile the current SpeedoCompiledQuery.
086: * The query is prepared to be executed.
087: * The PersistenceManager is set (even if there was a previous definition
088: * of a PersistenceManager.
089: */
090: public synchronized void compile() throws SpeedoException,
091: MedorException, ExpressionException {
092: if (status == UNDEFINED)
093: throw new SpeedoException(
094: "Impossible to compile an undefined query");
095: if (status == COMPILED)
096: return;
097: long timeToCompile = System.currentTimeMillis();
098: boolean debug = logger.isLoggable(BasicLevel.DEBUG);
099: // create a speedoQL object with a filter string
100: String filter = qd.filter;
101: filter = '(' + filter + ')';
102: // create representations of the parameters list and the variable
103: // list
104: toHashtableParams(qd.parameters, ";,");
105: toHashtableVars(qd.variables, ";,");
106: Manager miManager = mapper.getMetaInfoManager();
107: if (miManager == null)
108: throw new SpeedoException(
109: "A non null Meta information manager is needed");
110: try {
111: jf.getPClassMapping(qd.candidateClass.getName(),
112: classLoader);
113: } catch (Exception e) {
114: throw new SpeedoException(e);
115: }
116: SimpleNode node = null;
117: try {
118: node = new SpeedoQL(new CharArrayReader(filter
119: .toCharArray())).SpeedoQL();
120: } catch (ParseException e) {
121: throw new SpeedoException(
122: "Impossible to parse the filter and to create AST",
123: e);
124: }
125: SpeedoQLVariableVisitor sqvv = new SpeedoQLVariableVisitor(
126: node, miManager, varParserlogger, hparams, hvars,
127: qd.order, qd.candidateClass.getName(),
128: qd.includeSubClasses);
129: // start the variable visitor to catch all variables and build a
130: // first tree of them without collection navigation
131: Map fields = sqvv.getFields();
132: QueryBuilder qb = sqvv.getQueryBuilder();
133: QueryTree qt = sqvv.getQueryTree();
134:
135: SelectProject sp = new SelectProject("");
136: if (!filter.equals("(true)") && !filter.equals("true")) {
137: //Ther is a filter and potentialy collection navigation
138: if (debug) {
139: logger.log(BasicLevel.DEBUG, "filter = " + qd.filter);
140: }
141: // start the query filter visitor, to build and expression tree of
142: // the filter expression
143: SpeedoQLQueryFilterVisitor sqfv = new SpeedoQLQueryFilterVisitor(
144: fields, sp, (ASTSpeedoQL) node, filterParserLogger,
145: hparams, hvars, qd.candidateClass, qb, jf);
146: sp.setQueryFilter(sqfv.getQueryFilter());
147: }
148: assignMapper(sp);
149: assignMapper(qt);
150:
151: JDOQueryEvalContext qec = new JDOQueryEvalContext(sp, this );
152:
153: SelectGroupByVisitor sgv = new SelectGroupByVisitor(sp, qt,
154: mapper, sqvv, qec, classLoader);
155:
156: sgv.visit(qd);
157: selectedFieldTypes = sgv.getSelectFieldTypes();
158: assignMapper(qec.query);
159:
160: //Specify the ordering
161: if (qd.order != null && qd.order.size() > 0) {
162: OrderField[] ofs = new OrderField[qd.order.size()];
163: for (int i = 0; i < ofs.length; i++) {
164: String o = (String) qd.order.get(i);
165: int idx = o.indexOf(' ');
166: boolean desc = false;
167: if (idx != -1) {
168: desc = o.substring(idx + 1).trim().equals(
169: "descending");
170: o = o.substring(0, idx);
171: }
172: o = "this." + o;
173: ofs[i] = new BasicOrderField((QueryTreeField) qt
174: .getTupleStructure().getField(o), desc);
175: }
176: sp.setOrderBy(ofs);
177: }
178:
179: logger.log(BasicLevel.INFO, "QueryTree built");
180: if (debug) {
181: QueryTreePrinter.printQueryTree(qec.query, logger);
182: }
183: //check for the use of the userCache
184: if (qd.result == null && qd.variables == null) {
185: //no variable used and the result is the candidate class
186: Map field2value = new HashMap();
187: if (getFieldComparaison(sp.getQueryFilter(), field2value)) {
188: HomeItf sh = null;
189: try {
190: sh = (HomeItf) jf.getPClassMapping(
191: qd.candidateClass.getName(), classLoader);
192: } catch (PException e) {
193: //never happen
194: }
195: userCache = sh.getUserCache(field2value.keySet());
196: if (userCache != null) {
197: userCacheIndexes = new Operand[field2value.size()];
198: String[] ifs = userCache.getIndexFieldNames();
199: for (int i = 0; i < ifs.length; i++) {
200: userCacheIndexes[i] = (Operand) field2value
201: .get(ifs[i]);
202: }
203: }
204: }
205:
206: }
207:
208: // Optimize the queryTree
209: qec.query = optimize(qec.query, debug);
210:
211: // Creates an evaluator associated to the QueryTree
212: qec.evaluator = new SelectEvaluator(qec.query, 0);
213:
214: qecs = new JDOQueryEvalContext[] { qec };
215: timeToCompile = System.currentTimeMillis() - timeToCompile;
216: status = COMPILED;
217: logger.log(BasicLevel.INFO, "Query compiled in "
218: + timeToCompile + "ms");
219: }
220:
221: /**
222: * executes a the current query, and returns a Collection of object.
223: * @param userqd is the user query definition. It contains the range values
224: * and some other values that can change from the original compiled query.
225: * @return a new Collection of objects.
226: * @throws org.objectweb.medor.api.EvaluationException
227: * @throws org.objectweb.medor.api.MedorException
228: */
229: protected Object executeQT(JDOPOManagerItf pm,
230: ParameterOperand[] pos, QueryDefinition userqd)
231: throws EvaluationException, MedorException, SpeedoException {
232: flushCache(pm);
233: if (userCache != null) { //the query matches to an unique index
234: Object key = null;
235: //building the key
236: if (userCacheIndexes.length == 1) {
237: //Index is single
238: key = getValueFromOperand(userCacheIndexes[0], pos);
239: } else {
240: //key is composite
241: key = new UserCacheKey(userCacheIndexes.length);
242: for (int i = 0; i < userCacheIndexes.length; i++) {
243: ((UserCacheKey) key).setPart(i,
244: getValueFromOperand(userCacheIndexes[0],
245: pos));
246: }
247: }
248: // search in the user cache
249: key = userCache.lookup(key);
250: if (key != null) {
251: try {
252: if (key instanceof PName) { //the cache contains PName
253: key = pm.getObjectById(key, false);
254: }
255: if (qd.unique) {
256: return key;
257: } else {
258: return Collections.singletonList(key);
259: }
260: } catch (JDOException e) {
261: logger.log(BasicLevel.WARN,
262: "User cache is not up to date:", e);
263: }
264: }
265: }
266:
267: Object connection = ((TransactionItf) pm.currentTransaction())
268: .getConnectionHolder();
269: //fetch the result as TupleCollection
270: TupleCollection queryResult = null;
271: if (qecs.length == 1) {
272: //optimization: avoid to use an intermediate class
273: queryResult = qecs[0].eval(pm, pos, connection, userqd);
274: if (queryResult == null || queryResult.isEmpty()) {
275: logger.log(BasicLevel.INFO,
276: "Be careful, the result is empty");
277: if (queryResult != null) {
278: queryResult.close();
279: }
280: JDOQueryResultCommon.closeConnection(connection);
281: if (qd.unique) {
282: return null;
283: } else {
284: return Collections.EMPTY_SET;
285: }
286: }
287: } else {
288: //Use a multiplex of query
289: queryResult = new JDOQueriesUnion(pos, pm, connection,
290: qecs, userqd);
291: }
292: if (qd.unique) {
293: //Only one object is expected, then return it
294: return new JDOQueryResultUnique(queryResult, pm,
295: new Object[] { connection }, qd.resultClass,
296: selectedFieldTypes, qecs.length == 1, userqd
297: .fetchIdentifierOnly(), logger).getResult();
298: } else {
299: //the expected result size is greater than one, then
300: // a result container is use (List)
301: return new JDOQueryResultList(queryResult, pm,
302: new Object[] { connection }, qd.resultClass,
303: selectedFieldTypes, qecs.length == 1, userqd
304: .fetchIdentifierOnly(), logger);
305: }
306: }
307:
308: private Object getValueFromOperand(Operand op,
309: ParameterOperand[] pos) {
310: if (op instanceof ParameterOperand) {
311: String param = ((ParameterOperand) op).getName();
312: for (int i = 0; i < pos.length; i++) {
313: if (pos[i].getName().equals(param)) {
314: return pos[i].getObject();
315: }
316: }
317: return null;
318: } else {
319: return op.getObject();
320: }
321: }
322:
323: }
|