/**
* Speedo: an implementation of JDO compliant personality on top of JORM generic
* I/O sub-system.
* Copyright (C) 2001-2004 France Telecom R&D
*
* 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.naming.api.PName;
import org.objectweb.medor.api.MedorException;
import org.objectweb.medor.expression.api.ExpressionException;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.api.SpeedoException;
import org.objectweb.speedo.api.SpeedoRuntimeException;
import org.objectweb.speedo.mim.api.PersistentObjectItf;
import org.objectweb.speedo.mim.api.StateItf;
import org.objectweb.speedo.pm.jdo.api.JDOPOManagerItf;
import org.objectweb.speedo.query.api.CompiledQuery;
import org.objectweb.speedo.query.api.QueryManager;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.jdo.Extent;
import javax.jdo.FetchPlan;
import javax.jdo.JDOException;
import javax.jdo.JDOFatalUserException;
import javax.jdo.JDOUserException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.listener.DeleteCallback;
/**
* SpeedoQuery is the basic implementation of the javax.jdo.Query interface.
* This class is just a wrapper to a SpeedoCompiledQuery object which is a
* reused object.
*
* @author S. Chassande-Barrioz
*/
public class JDOQuery extends JDOQueryDefinitionImpl implements Query {
/**
*
*/
private static final long serialVersionUID = 3412369898739619393L;
/**
* The queryManager object is used to create or return a CompiledQuery
* object.
*/
private QueryManager queryManager = null;
/**
* Logger for monolog.
*/
private Logger logger = null;
private boolean hasChanged = true;
/**
* PersistenceManager which manages this query.
*/
private JDOPOManagerItf pm;
private List results;
/**
* sQueryCompiler is the query, this object can compile and execute the
* query. This object is reused in the past, or future.
*/
private CompiledQuery qc = null;
/**
* The fetch plan.
* When a Query is retrieved from a PersistenceManager, its FetchPlan is initialized to the same settings
* as that of the PersistenceManager.
* Subsequent modifications of the Query's FetchPlan are not reflected
* in the FetchPlan of the PersistenceManager.
*/
private FetchPlan fetchPlan;
public JDOQuery() {
super();
results = new ArrayList(1);
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public QueryManager getQueryManager() {
return queryManager;
}
public void setQueryManager(QueryManager aqm) {
queryManager = aqm;
}
public CompiledQuery getQueryCompiler() {
return qc;
}
public void setQueryCompiler(CompiledQuery aqc) {
qc = aqc;
}
public void setPOManager(JDOPOManagerItf apm) {
pm = apm;
}
public void defineWith(String query) {
//TODO: support query definition a single string
}
// IMPLEMENTATION OF THE Query INTERFACE //
//---------------------------------------//
public void defineWith(JDOQueryDefinitionImpl qd) {
super.defineWith(qd);
hasChanged = true;
}
/**
* Set the class of the candidate instances of the query.
* <P>The class specifies the class
* of the candidates of the query. Elements of the candidate collection
* that are of the specified class are filtered before being
* put into the result Set.
* @param cls the Class of the candidate instances.
*/
public void setClass(Class cls) {
assertPMIsOpen();
assertModifiable();
if (candidateClass != null) hasChanged = true;
candidateClass = cls;
assertCandidateClass();
}
/**
* Set the candidate Extent to query.
* @param pcs the Candidate Extent.
*/
public void setCandidates(Extent pcs) {
assertPMIsOpen();
if (extentClass != null) hasChanged = true;
extentClass = pcs;
assertExtentClass();
}
/**
* Set the candidate Set to query.
* @param pcs the Candidate collection.
*/
public void setCandidates(Collection pcs) {
assertPMIsOpen();
assertModifiable();
if (candidateInstances != null) hasChanged = true;
candidateInstances = pcs;
assertCandidateInstances();
}
/**
* Set the filter for the query.
* <P>The filter specification is a String containing a boolean
* expression that is to be evaluated for each of the instances
* in the candidate collection. If the filter is not specified,
* then it defaults to "true", which has the effect of filtering
* the input Set only for class type.
* <P>An element of the candidate collection is returned in the result if:
* <ul><li>it is assignment compatible to the candidate Class of the Query; and
* <li>for all variables there exists a value for which the filter
* expression evaluates to true.
* </ul>
* <P>The user may denote uniqueness in the filter expression by
* explicitly declaring an expression (for example, e1 != e2).
* <P>Rules for constructing valid expressions follow the Java
* language, except for these differences:
* <ul>
* <li>Equality and ordering comparisons between primitives and instances
* of wrapper classes are valid.
* <li>Equality and ordering comparisons of Date fields and Date
* parameters are valid.
* <li>White space (non-printing characters space, tab, carriage
* return, and line feed) is a separator and is otherwise ignored.
* <li>The assignment operators =, +=, etc. and pre- and post-increment
* and -decrement are not supported. Therefore, there are no side
* effects from evaluation of any expressions.
* <li>Methods, including object construction, are not supported, except
* for Set.contains(Object o), Set.isEmpty(),
* String.startsWith (String s), and String.endsWith (String e).
* Implementations might choose to support non-mutating method
* calls as non-standard extensions.
* <li>Navigation through a null-valued field, which would throw
* NullPointerException, is treated as if the filter expression
* returned false for the evaluation of the current set of variable
* values. Other values for variables might still qualify the candidate
* instance for inclusion in the result set.
* <li>Navigation through multi-valued fields (Set types) is
* specified using a variable declaration and the
* Set.contains(Object o) method.
* </ul>
* <P>Identifiers in the expression are considered to be in the name
* space of the specified class, with the addition of declared imports,
* parameters and variables. As in the Java language, this is a reserved
* word which means the element of the collection being evaluated.
* <P>Navigation through single-valued fields is specified by the Java
* language syntax of field_name.field_name....field_name.
* <P>A JDO implementation is allowed to reorder the filter expression
* for optimization purposes.
* @param f the query filter.
*/
public void setFilter(String f) {
assertPMIsOpen();
assertModifiable();
if (f == null || f.length() == 0) {
filter = "true";
} else {
filter = "(" + f + ")";
hasChanged = true;
}
}
/**
* Set the import statements to be used to identify the fully qualified name of
* variables or parameters. Parameters and unbound variables might
* come from a different class from the candidate class, and the names
* need to be declared in an import statement to eliminate ambiguity.
* Import statements are specified as a String with semicolon-separated
* statements.
* <P>The String parameter to this method follows the syntax of the
* import statement of the Java language.
* @param imports import statements separated by semicolons.
*/
public void declareImports(String imports) {
assertPMIsOpen();
assertModifiable();
if (importStatements != null) hasChanged = true;
StringTokenizer tok = new StringTokenizer(imports, ";");
importStatements = new Vector();
while (tok.hasMoreTokens())
importStatements.add(tok.nextToken().trim());
}
/**
* Declare the list of parameters query execution.
* The parameter declaration is a String containing one or more query
* parameter declarations separated with commas. Each parameter named
* in the parameter declaration must be bound to a value when
* the query is executed.
* <P>The String parameter to this method follows the syntax for formal
* parameters in the Java language.
* @param parameters the list of parameters separated by commas.
*/
public void declareParameters(String parameters) {
assertPMIsOpen();
assertModifiable();
if (this.parameters != null) {
hasChanged = true;
}
this.parameters = parameters;
assertParameters();
}
/**
* Declare the unbound variables to be used in the query. Variables
* might be used in the filter, and these variables must be declared
* with their type. The unbound variable declaration is a String
* containing one or more unbound variable declarations separated
* with semicolons. It follows the syntax for local variables in
* the Java language.
* @param variables the variables separated by semicolons.
*/
public void declareVariables(String variables) {
assertPMIsOpen();
assertModifiable();
if (this.variables != null) hasChanged = true;
this.variables = variables;
assertVariables();
}
/**
* Set the ordering specification for the result Set. The
* ordering specification is a String containing one or more ordering
* declarations separated by commas.
*
* <P>Each ordering declaration is the name of the field on which
* to order the results followed by one of the following words:
* "ascending" or "descending".
*
*<P>The field must be declared in the candidate class or must be
* a navigation expression starting with a field in the candidate class.
*
*<P>Valid field types are primitive types except boolean; wrapper types
* except Boolean; BigDecimal; BigInteger; String; and Date.
* @param ordering the ordering specification.
*/
public void setOrdering(String ordering) {
assertPMIsOpen();
assertModifiable();
if (order != null) hasChanged = true;
StringTokenizer tok = new StringTokenizer(ordering, ",");
order = new ArrayList();
while (tok.hasMoreTokens())
order.add(tok.nextToken().trim());
}
/**
* Set the ignoreCache option. The default value for this option was
* set by the PersistenceManagerFactory or the PersistenceManager used
* to create this Query.
*
* The ignoreCache option setting specifies whether the query should execute
* entirely in the back end, instead of in the cache. If this flag is set
* to true, an implementation might be able to optimize the query
* execution by ignoring changed values in the cache. For optimistic
* transactions, this can dramatically improve query response times.
* @param ignoreCache the setting of the ignoreCache option.
*/
public void setIgnoreCache(boolean ignoreCache) {
assertPMIsOpen();
assertModifiable();
this.ignoreCache = ignoreCache;
}
/**
* Get the ignoreCache option setting.
* @return the ignoreCache option setting.
* @see #setIgnoreCache
*/
public boolean getIgnoreCache() {
assertPMIsOpen();
return ignoreCache;
}
public void setIncludeSubClasses(boolean val) {
assertModifiable();
hasChanged = true;
includeSubClasses = val;
}
/**
* Verify the elements of the query and provide a hint to
* the query to prepare and optimize an execution plan.
*/
public void compile() {
// build a CompiledQuery object with the current object.
// if the object already exists, it is returned
qc = queryManager.getQueryCompiler(this);
// compile the CompiledQuery object
try {
qc.compile();
} catch (Exception e) {
throw new JDOException("Impossible to compile a query ", e);
}
hasChanged = false;
}
private void treatSpeedoHints(Map parameterValues) {
String speedoHints = (String) parameterValues.get("SPEEDO_HINTS");
if (speedoHints != null) {
treatSpeedoHints(speedoHints);
}
}
private void treatSpeedoHints(Object[] parameterValues) {
StringTokenizer st = new StringTokenizer(parameters, ",", false);
int speedoHintsIndex = -1;
int tokenIndex = 0;
while(speedoHintsIndex < 0 && st.hasMoreTokens()) {
String token = st.nextToken();
if (token.indexOf("SPEEDO_HINTS") > -1) {
speedoHintsIndex = tokenIndex;
}
tokenIndex++;
}
if (speedoHintsIndex >= 0) {
treatSpeedoHints((String) parameterValues[speedoHintsIndex]);
}
}
private void treatSpeedoHints(String speedoHints) {
StringTokenizer st = new StringTokenizer(speedoHints, ",", false);
while(st.hasMoreTokens()) {
String token = st.nextToken();
if (token.equalsIgnoreCase("prefetch")) {
if (!withPrefetch) {
withPrefetch = true;
hasChanged = true;
}
} else if (token.equalsIgnoreCase("noprefetch")) {
if (withPrefetch) {
withPrefetch = false;
hasChanged = true;
}
//} else if (token.equalsIgnoreCase("list")) {
}
}
}
/**
* Execute the query and return the filtered Set.
* @return the filtered Set.
* @see #executeWithArray(java.lang.Object[] parameters)
*/
public Object execute() {
Object[] params = new Object[0];
beforeExecute(params);
try {
Object res = qc.execute(params, pm, this);
if (!unique) {
results.add(res);
}
return res;
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
/**
* Execute the query and return the filtered Set.
* @return the filtered Set.
* @see #executeWithArray(java.lang.Object[] parameters)
* @param p1 the value of the first parameter declared.
*/
public Object execute(Object p1) {
Object[] params = new Object[]{p1};
beforeExecute(params);
try {
Object res = qc.execute(params, pm, this);
if (!unique) {
results.add(res);
}
return res;
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
/**
* Execute the query and return the filtered Set.
* @return the filtered Set.
* @see #executeWithArray(java.lang.Object[] parameters)
* @param p1 the value of the first parameter declared.
* @param p2 the value of the second parameter declared.
*/
public Object execute(Object p1, Object p2) {
Object[] params = new Object[]{p1, p2};
beforeExecute(params);
try {
Object res = qc.execute(params, pm, this);
if (!unique) {
results.add(res);
}
return res;
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
/**
* Execute the query and return the filtered Set.
* @return the filtered Set.
* @see #executeWithArray(java.lang.Object[] parameters)
* @param p1 the value of the first parameter declared.
* @param p2 the value of the second parameter declared.
* @param p3 the value of the third parameter declared.
*/
public Object execute(Object p1, Object p2, Object p3) {
Object[] params = new Object[]{p1, p2, p3};
beforeExecute(params);
try {
Object res = qc.execute(params, pm, this);
if (!unique) {
results.add(res);
}
return res;
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
/**
* Execute the query and return the filtered Set.
* The query is executed with the parameters set by the Map values. Each
* Map entry consists of a key which is the name of the parameter in the
* declareParameters method, and a value which is the value used in
* the execute method. The keys in the Map and the declared parameters
* must exactly match or a JDOUserException is thrown.
* @return the filtered Set.
* @see #executeWithArray(java.lang.Object[] parameters)
* @param amap the Map containing all of the parameters.
*/
public Object executeWithMap(Map amap) {
beforeExecute(amap);
try {
Object res = qc.execute(amap, pm, this);
if (!unique) {
results.add(res);
}
return res;
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
/**
* Execute the query and return the filtered Set.
* <P>The execution of the query obtains the values of the parameters and
* matches them against the declared parameters in order. The names
* of the declared parameters are ignored. The type of
* the declared parameters must match the type of the passed parameters,
* except that the passed parameters might need to be unwrapped to get
* their primitive values.
*
* <P>The filter, import, declared parameters, declared variables, and
* ordering statements are verified for consistency.
*
* <P>Each element in the candidate Set is examined to see that it
* is assignment compatible to the Class of the query. It is then evaluated
* by the boolean expression of the filter. The element passes the filter
* if there exist unique values for all variables for which the filter
* expression evaluates to true.
* @return the filtered Set.
* @param anarray the Object array with all of the parameters.
*/
public Object executeWithArray(Object[] anarray) {
beforeExecute(anarray);
try {
Object res = qc.execute(anarray, pm, this);
if (!unique) {
results.add(res);
}
return res;
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
/**
* Get the PersistenceManager associated with this Query.
* <P>If this Query was restored from a serialized form, it has no
* PersistenceManager, and this method returns null.
* @return the PersistenceManager associated with this Query.
*/
public PersistenceManager getPersistenceManager() {
return pm;
}
/**
* Close a query result and release any resources
* associated with it. The parameter is the return from execute(...) and
* might have iterators open on it. Iterators associated with the query
* result are invalidated: they return false to hasNext() and throw
* NoSuchElementException to next().
*
* This method makes nothing, all is done by the SpeedoCompiledQuery
* object. This object is never closed, and could be reused in the
* future.
*
* @param queryResult the result of execute(...) on this Query instance.
*/
public void close(Object queryResult) {
if (queryResult instanceof JDOQueryResultList) {
((JDOQueryResultList) queryResult).close();
int i=0;
while(i < results.size() && results.get(i) != queryResult) {
i++;
}
if (i < results.size()) {
results.remove(i);
}
}
}
/**
* Close all query results associated with this Query
* instance, and release all resources associated with them. The query
* results might have iterators open on them. Iterators associated with the
* query results are invalidated: they return false to hasNext() and throw
* NoSuchElementException to next().
*/
public void closeAll() {
for(int i=0; i<results.size(); i++) {
Object queryResult = results.get(i);
if (queryResult instanceof JDOQueryResultList) {
((JDOQueryResultList) queryResult).close();
}
}
results.clear();
}
public FetchPlan getFetchPlan(){
return fetchPlan;
}
public void setFetchPlan(FetchPlan fp){
assertModifiable();
fetchPlan = fp;
}
public long deletePersistentAll() {
return deletePersistentAll(new Object[0]);
}
public long deletePersistentAll(Map params) {
beforeDelete(params);
try {
Long res = (Long) qc.execute(params, pm, this);
if (!unique) {
results.add(res);
}
return res.longValue();
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
public long deletePersistentAll(Object[] params) {
beforeDelete(params);
try {
Long res = (Long) qc.execute(params, pm, this);
if (!unique) {
results.add(res);
}
return res.longValue();
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(BasicLevel.ERROR, "execute failed", e);
throw new JDOException("Impossible to execute a query ", e);
}
}
public void setGrouping(String arg0) {
assertModifiable();
hasChanged = true;
this.grouping = arg0;
}
public void setRange(long first, long last) {
assertModifiable();
indexFirst = first;
indexLast = last;
}
public void setUnique(boolean _unique) {
assertModifiable();
this.unique = _unique;
}
public void setResult(String _result) {
assertModifiable();
hasChanged = true;
this.result = _result;
}
public void setResultClass(Class _resultClass) {
assertModifiable();
this.resultClass = _resultClass;
}
public Map extensions = null;
public void setExtensions(Map exts) {
assertModifiable();
this.extensions = exts;
}
public void addExtension(String key, Object value) {
assertModifiable();
if (extensions == null) {
if (value == null) {
return;
}
extensions = new HashMap();
}
if (value == null) {
extensions.remove(key);
} else {
extensions.put(key, value);
}
}
boolean modifiable = true;
public boolean isUnmodifiable() {
return modifiable;
}
public void setUnmodifiable() {
modifiable = false;
}
public void setRange(String arg0) {
// TODO implement JDO Query setRange(String)
}
// PRIVATE METHODS //
//-----------------//
private void beforeExecute(Object parameterValues) {
int size = 0;
if (parameterValues != null) {
if (parameterValues instanceof Map) {
size = ((Map) parameterValues).size();
} else if (parameterValues.getClass().isArray()) {
size = ((Object[]) parameterValues).length;
}
}
if (parameters != null && size == 0 && parameters.length()>0)
throw new JDOUserException(
"Parameters in not null, and you try to execute without parameters");
if (pm.isPOMClosed())
throw new JDOFatalUserException(
"Impossible to use the query: the persistent manager is closed.");
if (parameterValues != null && parameters != null) {
if (parameterValues instanceof Map) {
treatSpeedoHints((Map) parameterValues);
} else if (parameterValues.getClass().isArray()) {
treatSpeedoHints((Object[]) parameterValues);
}
}
if (hasChanged) {
compile();
}
}
private void beforeDelete(Object parameterValues) {
//Load all identifier in order to take locks
boolean concurrencyManagedBySpeedo = false;
//TODO: compute concurrencyManagedBySpeedo
if (concurrencyManagedBySpeedo) {
boolean mustLoad = DeleteCallback.class.isAssignableFrom(candidateClass);
//TODO: take in account listener in addition to DeleteCallback
//home.
type = TYPE_SELECT;
hasChanged = true;
withPrefetch = mustLoad;
this.fetchIdentifierOnly = !mustLoad;
compile();
try {
Collection toRemoved;
if (parameterValues instanceof Map) {
toRemoved = (Collection) qc.execute((Map) parameterValues, pm, this);
} else if (parameterValues.getClass().isArray()) {
toRemoved = (Collection) qc.execute((Object[]) parameterValues, pm, this);
} else {
throw new SpeedoRuntimeException("Bad state: no param specified in query");
}
for (Iterator it = toRemoved.iterator(); it.hasNext();) {
Object o = it.next();
if (o instanceof PersistentObjectItf) {
pm.speedoDeletePersistent(o);
StateItf s = ((PersistentObjectItf) o).speedoGetState();
s.setFlushed(true);
} else {
pm.speedoDeletePersistent(o, candidateClass);
}
}
pm.deletePersistentAll(toRemoved);
closeAll();
} catch (Exception e) {
throw new JDOException("", ExceptionHelper.getNested(e));
}
}
type = TYPE_DELETE;
hasChanged = true;
withPrefetch = false;
beforeExecute(parameterValues);
}
private void beforeDelete(Map parameterValues) {
if (type != TYPE_DELETE) {
hasChanged = true;
withPrefetch = false;
type = TYPE_DELETE;
}
beforeExecute(parameterValues);
}
private void assertPMIsOpen() {
if (pm.isPOMClosed())
throw new JDOUserException("The persistence manager is closed.");
}
private void assertModifiable() {
if (!modifiable)
throw new JDOUserException("The query has been marked as not modifiable.");
}
private void assertParameters() {
if (parameters == null)
throw new JDOUserException("Parameters string can not be null");
}
private void assertVariables() {
if (variables == null)
throw new JDOUserException("variables string can not be null");
}
private void assertCandidateClass() {
if (candidateClass == null)
throw new JDOUserException("candidate class can not be null");
}
private void assertExtentClass() {
if (extentClass == null)
throw new JDOUserException("Extent class can not be null");
}
private void assertCandidateInstances() {
if (candidateInstances == null)
throw new JDOUserException("candidate Collection can not be null");
}
}