/**
* Speedo: an implementation of JDO compliant personality on top of JORM generic
* I/O sub-system.
* Copyright (C) 2001-2006 France Telecom
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Contact: speedo@objectweb.org
*
* Authors: S. Chassande-Barrioz
*
*/
package org.objectweb.speedo.query.jdo;
import org.objectweb.jorm.api.PException;
import org.objectweb.jorm.metainfo.api.Manager;
import org.objectweb.jorm.naming.api.PName;
import org.objectweb.medor.api.EvaluationException;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.eval.lib.SelectEvaluator;
import org.objectweb.medor.expression.api.ExpressionException;
import org.objectweb.medor.expression.api.Operand;
import org.objectweb.medor.expression.api.ParameterOperand;
import org.objectweb.medor.query.api.OrderField;
import org.objectweb.medor.query.api.QueryTree;
import org.objectweb.medor.query.api.QueryTreeField;
import org.objectweb.medor.query.jorm.lib.QueryBuilder;
import org.objectweb.medor.query.lib.BasicOrderField;
import org.objectweb.medor.query.lib.QueryTreePrinter;
import org.objectweb.medor.query.lib.SelectProject;
import org.objectweb.medor.tuple.api.TupleCollection;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.speedo.mim.api.HomeItf;
import org.objectweb.speedo.pm.jdo.api.JDOPOManagerItf;
import org.objectweb.speedo.query.api.QueryDefinition;
import org.objectweb.speedo.query.jdo.parser.ASTSpeedoQL;
import org.objectweb.speedo.query.jdo.parser.ParseException;
import org.objectweb.speedo.query.jdo.parser.SelectGroupByVisitor;
import org.objectweb.speedo.query.jdo.parser.SimpleNode;
import org.objectweb.speedo.query.jdo.parser.SpeedoQL;
import org.objectweb.speedo.query.jdo.parser.SpeedoQLQueryFilterVisitor;
import org.objectweb.speedo.query.jdo.parser.SpeedoQLVariableVisitor;
import org.objectweb.speedo.usercache.lib.UserCacheKey;
import org.objectweb.speedo.workingset.api.TransactionItf;
import org.objectweb.util.monolog.api.BasicLevel;
import java.io.CharArrayReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.jdo.JDOException;
/**
* JDOCompiledQuery object represents a jdo query. This object is created
* when a new query is created, and can be used several times. A list of
* JDOCompiledQuery is managed with a JDOQueryManager component.
* When a user creates a new JDO Query object (SpeedoQuery), a
* JDOCompiledQuery object is associated to the JDOQuery object which is
* used to delegate some methods.
*
* @author S.Chassande-Barrioz
*/
public class JDOCompiledSelectQuery extends JDOAbstractCompiledQuery {
private JDOQueryEvalContext[] qecs = null;
private Class[] selectedFieldTypes = null;
public JDOQueryEvalContext[] getQueryEvalContext() {
return qecs;
}
/**
* compile the current SpeedoCompiledQuery.
* The query is prepared to be executed.
* The PersistenceManager is set (even if there was a previous definition
* of a PersistenceManager.
*/
public synchronized void compile() throws SpeedoException, MedorException, ExpressionException {
if (status == UNDEFINED)
throw new SpeedoException("Impossible to compile an undefined query");
if (status == COMPILED)
return;
long timeToCompile = System.currentTimeMillis();
boolean debug = logger.isLoggable(BasicLevel.DEBUG);
// create a speedoQL object with a filter string
String filter = qd.filter;
filter = '(' + filter + ')';
// create representations of the parameters list and the variable
// list
toHashtableParams(qd.parameters, ";,");
toHashtableVars(qd.variables, ";,");
Manager miManager = mapper.getMetaInfoManager();
if (miManager == null)
throw new SpeedoException(
"A non null Meta information manager is needed");
try {
jf.getPClassMapping(
qd.candidateClass.getName(),
classLoader);
} catch (Exception e) {
throw new SpeedoException(e);
}
SimpleNode node = null;
try {
node = new SpeedoQL(new CharArrayReader(filter.toCharArray())).SpeedoQL();
} catch (ParseException e) {
throw new SpeedoException(
"Impossible to parse the filter and to create AST", e);
}
SpeedoQLVariableVisitor sqvv = new SpeedoQLVariableVisitor(
node, miManager, varParserlogger, hparams, hvars, qd.order,
qd.candidateClass.getName(), qd.includeSubClasses);
// start the variable visitor to catch all variables and build a
// first tree of them without collection navigation
Map fields = sqvv.getFields();
QueryBuilder qb = sqvv.getQueryBuilder();
QueryTree qt = sqvv.getQueryTree();
SelectProject sp = new SelectProject("");
if (!filter.equals("(true)") && !filter.equals("true")) {
//Ther is a filter and potentialy collection navigation
if (debug) {
logger.log(BasicLevel.DEBUG, "filter = " + qd.filter);
}
// start the query filter visitor, to build and expression tree of
// the filter expression
SpeedoQLQueryFilterVisitor sqfv = new SpeedoQLQueryFilterVisitor(
fields, sp, (ASTSpeedoQL) node,
filterParserLogger, hparams, hvars,
qd.candidateClass,
qb, jf);
sp.setQueryFilter(sqfv.getQueryFilter());
}
assignMapper(sp);
assignMapper(qt);
JDOQueryEvalContext qec = new JDOQueryEvalContext(sp, this);
SelectGroupByVisitor sgv = new SelectGroupByVisitor(
sp, qt, mapper, sqvv, qec, classLoader);
sgv.visit(qd);
selectedFieldTypes = sgv.getSelectFieldTypes();
assignMapper(qec.query);
//Specify the ordering
if (qd.order != null && qd.order.size() > 0) {
OrderField[] ofs = new OrderField[qd.order.size()];
for(int i=0; i<ofs.length; i++) {
String o = (String) qd.order.get(i);
int idx = o.indexOf(' ');
boolean desc = false;
if (idx != -1) {
desc = o.substring(idx + 1).trim().equals("descending");
o = o.substring(0, idx);
}
o = "this." + o;
ofs[i] = new BasicOrderField((QueryTreeField)
qt.getTupleStructure().getField(o), desc);
}
sp.setOrderBy(ofs);
}
logger.log(BasicLevel.INFO, "QueryTree built");
if (debug) {
QueryTreePrinter.printQueryTree(qec.query, logger);
}
//check for the use of the userCache
if (qd.result == null && qd.variables == null) {
//no variable used and the result is the candidate class
Map field2value = new HashMap();
if (getFieldComparaison(sp.getQueryFilter(), field2value)) {
HomeItf sh = null;
try {
sh = (HomeItf) jf.getPClassMapping(
qd.candidateClass.getName(),
classLoader);
} catch (PException e) {
//never happen
}
userCache = sh.getUserCache(field2value.keySet());
if (userCache != null) {
userCacheIndexes = new Operand[field2value.size()];
String[] ifs = userCache.getIndexFieldNames();
for (int i = 0; i < ifs.length; i++) {
userCacheIndexes[i] = (Operand) field2value.get(ifs[i]);
}
}
}
}
// Optimize the queryTree
qec.query = optimize(qec.query, debug);
// Creates an evaluator associated to the QueryTree
qec.evaluator = new SelectEvaluator(qec.query, 0);
qecs = new JDOQueryEvalContext[] {qec};
timeToCompile = System.currentTimeMillis() - timeToCompile;
status = COMPILED;
logger.log(BasicLevel.INFO, "Query compiled in " + timeToCompile + "ms");
}
/**
* executes a the current query, and returns a Collection of object.
* @param userqd is the user query definition. It contains the range values
* and some other values that can change from the original compiled query.
* @return a new Collection of objects.
* @throws org.objectweb.medor.api.EvaluationException
* @throws org.objectweb.medor.api.MedorException
*/
protected Object executeQT(JDOPOManagerItf pm, ParameterOperand[] pos, QueryDefinition userqd)
throws EvaluationException, MedorException, SpeedoException {
flushCache(pm);
if (userCache != null) { //the query matches to an unique index
Object key = null;
//building the key
if (userCacheIndexes.length == 1) {
//Index is single
key = getValueFromOperand(userCacheIndexes[0], pos);
} else {
//key is composite
key = new UserCacheKey(userCacheIndexes.length);
for (int i = 0; i < userCacheIndexes.length; i++) {
((UserCacheKey) key).setPart(i,
getValueFromOperand(userCacheIndexes[0], pos));
}
}
// search in the user cache
key = userCache.lookup(key);
if (key != null) {
try {
if (key instanceof PName) { //the cache contains PName
key = pm.getObjectById(key, false);
}
if (qd.unique) {
return key;
} else {
return Collections.singletonList(key);
}
} catch (JDOException e) {
logger.log(BasicLevel.WARN,
"User cache is not up to date:", e);
}
}
}
Object connection = ((TransactionItf) pm.currentTransaction())
.getConnectionHolder();
//fetch the result as TupleCollection
TupleCollection queryResult = null;
if (qecs.length == 1) {
//optimization: avoid to use an intermediate class
queryResult = qecs[0].eval(pm, pos, connection, userqd);
if (queryResult == null || queryResult.isEmpty()) {
logger.log(BasicLevel.INFO,
"Be careful, the result is empty");
if (queryResult != null) {
queryResult.close();
}
JDOQueryResultCommon.closeConnection(connection);
if (qd.unique) {
return null;
} else {
return Collections.EMPTY_SET;
}
}
} else {
//Use a multiplex of query
queryResult = new JDOQueriesUnion(pos, pm, connection, qecs, userqd);
}
if (qd.unique) {
//Only one object is expected, then return it
return new JDOQueryResultUnique(
queryResult,
pm,
new Object[]{connection},
qd.resultClass,
selectedFieldTypes,
qecs.length == 1,
userqd.fetchIdentifierOnly(),
logger).getResult();
} else {
//the expected result size is greater than one, then
// a result container is use (List)
return new JDOQueryResultList(
queryResult,
pm,
new Object[]{connection},
qd.resultClass,
selectedFieldTypes,
qecs.length == 1,
userqd.fetchIdentifierOnly(),
logger);
}
}
private Object getValueFromOperand(Operand op, ParameterOperand[] pos) {
if (op instanceof ParameterOperand) {
String param = ((ParameterOperand) op).getName();
for (int i = 0; i < pos.length; i++) {
if (pos[i].getName().equals(param)) {
return pos[i].getObject();
}
}
return null;
} else {
return op.getObject();
}
}
}