/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* QueryImpl.java
*
* Created on March 8, 2000
*/
package com.sun.jdo.spi.persistence.support.sqlstore.query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.ResourceBundle;
import com.sun.jdo.api.persistence.support.Query;
import com.sun.jdo.api.persistence.support.Transaction;
import com.sun.jdo.api.persistence.support.JDOException;
import com.sun.jdo.api.persistence.support.JDOQueryException;
import com.sun.jdo.api.persistence.support.JDOUnsupportedOptionException;
import com.sun.jdo.spi.persistence.support.sqlstore.PersistenceManager;
import com.sun.jdo.spi.persistence.support.sqlstore.RetrieveDesc;
import com.sun.jdo.spi.persistence.utility.I18NHelper;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc.JQLC;
import com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc.ParameterTable;
import com.sun.jdo.spi.persistence.support.sqlstore.ValueFetcher;
/**
*
* @author Michael Bouschen
* @version 0.1
*/
public class QueryImpl
implements Query
{
/**
*
*/
private Class candidateClass;
/**
*
*/
private String filterExpression;
/**
*
*/
private String importDeclarations;
/**
*
*/
private String parameterDeclarations;
/**
*
*/
private String variableDeclarations;
/**
*
*/
private String orderingSpecification;
/**
*
*/
private String resultExpression;
/**
*
*/
private boolean compiled = false;
/**
*
*/
private transient PersistenceManager pm;
/**
*
*/
private transient Collection candidateCollection;
/**
*
*/
private transient boolean ignoreCache;
/**
* Enable relationship fields prefetch for this query.
*/
private transient boolean prefetchEnabled = true;
/**
*
*/
private transient JQLC jqlc;
/**
*
*/
private transient ParameterTable paramtab;
/**
* Flag indicating whtehr this instance was created by serialization.
*/
private transient boolean createdBySerialization = false;
/**
* I18N support
*/
protected final static ResourceBundle messages =
I18NHelper.loadBundle(QueryImpl.class);
/** The logger */
private static Logger logger = LogHelperQueryExecute.getLogger();
/**
* Create an empty query instance with no elements.
*/
public QueryImpl(PersistenceManager pm)
{
if (logger.isLoggable(Logger.FINER))
logger.finer("LOG_CreateNewQuery", identity()); //NOI18N
this.pm = pm;
this.paramtab = new ParameterTable();
this.ignoreCache = pm.getPersistenceManagerFactory().getIgnoreCache();
}
/**
* Create a new Query using elements from another Query. The other Query
* must have been created by the same JDO implementation. It might be active
* in a different PersistenceManager or might have been serialized and restored.
* @param compiled another Query from the same JDO implementation
*/
public QueryImpl (PersistenceManager pm, Object compiled)
{
if (logger.isLoggable(Logger.FINER))
logger.finer("LOG_CreateNewQueryFromCompiled", identity(), compiled); //NOI18N
this.pm = pm;
if (compiled == null)
{
JDOException ex = new JDOQueryException(I18NHelper.getMessage(
messages, "query.queryimpl.init.compiledquery.isnull")); //NOI18N
logger.throwing("query.QueryImpl", "<init>", ex); //NOI18N
throw ex;
}
if (!(compiled instanceof QueryImpl))
{
JDOException ex = new JDOQueryException(I18NHelper.getMessage(
messages, "query.queryimpl.init.compiledquery.invalidtype", //NOI18N
compiled.getClass().getName()));
logger.throwing("query.QueryImpl", "<init>", ex); //NOI18N
throw ex;
}
QueryImpl other = (QueryImpl)compiled;
this.candidateClass = other.candidateClass;
this.filterExpression = other.filterExpression;
this.importDeclarations = other.importDeclarations;
this.parameterDeclarations = other.parameterDeclarations;
this.variableDeclarations = other.variableDeclarations;
this.orderingSpecification = other.orderingSpecification;
this.resultExpression = other.resultExpression;
this.ignoreCache = other.ignoreCache;
this.prefetchEnabled = other.prefetchEnabled;
this.candidateCollection = null;
// initialize paramtab, jqlc and compiled
if (other.paramtab != null)
{
this.jqlc = other.jqlc;
this.paramtab = new ParameterTable(other.paramtab);
this.compiled = other.compiled;
}
else
{
// other.paramtab == null means deserialized query =>
// - parameter table
// - set compiled = false
this.jqlc = null;
this.paramtab = new ParameterTable();
this.compiled = false;
}
}
/**
* Create a query instance with the candidate class specified.
* @param candidateClass the Class of the candidate instances.
*/
public QueryImpl(PersistenceManager pm, Class candidateClass)
{
this(pm);
setClass(candidateClass);
}
/**
* Create a query instance with the candidate class and
* candidate collection specified.
* @param candidateClass the Class of the candidate instances.
* @param candidateCollection the Collection of candidate instances.
*/
public QueryImpl(PersistenceManager pm, Class candidateClass, Collection candidateCollection)
{
this(pm);
setClass(candidateClass);
setCandidates(candidateCollection);
}
/**
* Create a query instance with the candidate class and
* filter specified.
* @param candidateClass the Class of the candidate instances.
* @param filter the Filter for candidate instances.
*/
public QueryImpl(PersistenceManager pm, Class candidateClass, String filter)
{
this(pm);
setClass(candidateClass);
setFilter(filter);
}
/**
* Create a query instance with the candidate class,
* the candidate collection, and filter specified.
* @param candidateClass the Class of the candidate instances.
* @param candidateCollection the Collection of candidate instances.
* @param filter the Filter for candidate instances
*/
public QueryImpl(PersistenceManager pm, Class candidateClass, Collection candidateCollection, String filter)
{
this(pm);
setClass(candidateClass);
setCandidates(candidateCollection);
setFilter(filter);
}
/**
* Bind the candidate class to the query instance.
*
* The class is used to scope the names in the query filter.
* All of the candidate instances will be of this class or subclass.
*
* @param candidateClass the Class of the candidate instances.
*/
public void setClass(Class candidateClass)
{
synchronized (this.paramtab)
{
this.candidateClass = candidateClass;
this.compiled = false;
}
}
/**
* Bind the candidate Collection to the query instance.
*
* @param pcs the Candidate collection.
*/
public void setCandidates(Collection candidateCollection)
{
synchronized (this.paramtab)
{
this.candidateCollection = candidateCollection;
// candidateCollection is not part of query compilation =>
// do not change compiled flag
}
}
/**
* Bind the query filter to the query instance.
*
* The query filter is a Java boolean expression, which tells whether
* instances in the candidate collection are to be returned in the result.
*
* @param filter the query filter.
*/
public void setFilter(String filter)
{
synchronized (this.paramtab)
{
this.filterExpression = filter;
this.compiled = false;
}
}
/**
* Bind the import statements to the query instance.
* All imports must be declared in the same method call,
* and the imports must be separated by semicolons.
* The syntax is the same as in the Java language import statement.
*
* Parameters and unbound variables might come from a different class
* from the candidate class, and the names might need to be declared in an
* import statement to eliminate ambiguity.
*
* @param imports import statements separated by semicolons.
*/
public void declareImports(String imports)
{
synchronized (this.paramtab)
{
this.importDeclarations = imports;
this.compiled = false;
}
}
/**
* Bind the parameter statements to the query instance.
* This method defines the parameter types and names
* which will be used by a subsequent execute method.
*
* The parameter declaration is a String containing one or
* more query parameter declarations separated with commas.
* It follows the syntax for formal parameters in the Java language.
* Each parameter named in the parameter declaration must be bound
* to a value when the query is executed.
*
* @param parameters the list of parameters separated by commas.
*/
public void declareParameters(String parameters)
{
synchronized (this.paramtab)
{
this.parameterDeclarations = parameters;
this.compiled = false;
}
}
/**
* Bind the unbound variable statements to the query instance.
* This method defines the types and names of variables that will be used
* in the filter but not provided as values by the execute method.
*
* 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)
{
synchronized (this.paramtab)
{
this.variableDeclarations = variables;
this.compiled = false;
}
}
/**
* Bind the ordering statements to the query instance.
*
* The ordering specification includes a list of expressions
* with the ascending/descending indicator.
*/
public void setOrdering(String ordering)
{
synchronized (this.paramtab)
{
this.orderingSpecification = ordering;
this.compiled = false;
}
}
/**
* Set the result of the query.
* <p>
* The query result is an optional keyword distinct followed by a Java
* expression, which tells what values are to be returned by the JDO query.
* If the result is not specified, then it defaults to "distinct this",
* which has the effect of returning the elements of the candidates
* that match the filter.
*/
public void setResult(String result)
{
synchronized (this.paramtab)
{
this.resultExpression = result;
this.compiled = false;
}
}
/**
* Set the ignoreCache option.
*
* The ignoreCache option setting specifies whether the query should execute
* entirely in the back end, instead of in the cache.
* @param ignoreCache the setting of the ignoreCache option.
*/
public void setIgnoreCache(boolean ignoreCache)
{
synchronized (this.paramtab)
{
this.ignoreCache = ignoreCache;
}
}
/**
* Get the ignoreCache option setting.
* @return the ignoreCache option setting.
* @see #setIgnoreCache
*/
public boolean getIgnoreCache()
{
return ignoreCache;
}
/** Sets the prefetchEnabled option.
*
* The prefetchEnabled option specifies whether prefetch of relationship
* fields should be enabled for this query. The prefetch is enabled by
* default if such fields are part of DFG. A user needs to explicitely
* disable prefetch for any particular query if the related instances
* will not be used in this transaction.
*
* @param prefetchEnabled the setting of the prefetchEnabled option.
*/
public void setPrefetchEnabled(boolean prefetchEnabled)
{
synchronized (this.paramtab)
{
this.prefetchEnabled = prefetchEnabled;
this.compiled = false;
}
}
/**
* Verify the elements of the query and provide a hint to the query to
* prepare and optimize an execution plan.
*/
public void compile()
{
synchronized (this.paramtab)
{
if (!this.compiled)
{
if (logger.isLoggable(Logger.FINER))
logger.finer("LOG_CompileQuery", this); //NOI18N
// create new query compiler instance
jqlc = new JQLC();
// define the query parts including syntax checks
jqlc.setClass(candidateClass);
jqlc.declareImports(importDeclarations);
jqlc.declareParameters(parameterDeclarations);
jqlc.declareVariables(variableDeclarations);
jqlc.setOrdering(orderingSpecification);
jqlc.setResult(resultExpression);
jqlc.setFilter(filterExpression);
jqlc.setPrefetchEnabled(prefetchEnabled);
// semantic analysis
jqlc.semanticCheck(paramtab);
this.compiled = true;
}
}
}
/**
* Execute the query and return the filtered Collection.
* @return the filtered Collection.
* @see #executeWithArray (Object[] parameters)
*/
public Object execute()
{
synchronized (this.paramtab)
{
compile();
ParameterTable params = new ParameterTable(paramtab);
params.initValueHandling();
params.checkUnboundParams();
return doExecute(params);
}
}
/**
* Execute the query and return the filtered Collection.
* @return the filtered Collection.
* @see #executeWithArray (Object[] parameters)
* @param p1 the value of the first parameter declared.
*/
public Object execute(Object p1)
{
Object [] params = new Object[1];
params[0] = p1;
return executeWithArray(params);
}
/**
* Execute the query and return the filtered Collection.
* @return the filtered Collection.
* @see #executeWithArray (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[2];
params[0] = p1;
params[1] = p2;
return executeWithArray(params);
}
/**
* Execute the query and return the filtered Collection.
* @return the filtered Collection.
* @see #executeWithArray (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[3];
params[0] = p1;
params[1] = p2;
params[2] = p3;
return executeWithArray(params);
}
/**
* Execute the query and return the filtered Collection.
* @return the filtered Collection.
* @see #executeWithArray (Object[] parameters)
* @param parameters the Map containing all of the parameters.
*/
public Object executeWithMap (Map parameters)
{
synchronized (this.paramtab)
{
compile();
ParameterTable params = new ParameterTable(paramtab);
params.initValueHandling();
params.setValues(parameters);
params.checkUnboundParams();
return doExecute(params);
}
}
/**
* Execute the query and return the filtered Collection.
*
* <P>The execution of the query obtains the values of the parameters and
* matches them against the declared parameters in order. 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 Collection 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 Collection.
* @param parameters the Object array with all of the parameters.
*/
public Object executeWithArray (Object[] parameters)
{
synchronized (this.paramtab)
{
compile();
ParameterTable params = new ParameterTable(paramtab);
params.initValueHandling();
params.setValues(parameters);
params.checkUnboundParams();
return doExecute(params);
}
}
/**
* Get the PersistenceManager associated with this Query.
*
* <P>If this Query has no PersistenceManager return null.
* @return the PersistenceManager associated with this Query.
*/
public com.sun.jdo.api.persistence.support.PersistenceManager getPersistenceManager()
{
return (pm == null)? null : pm.getCurrentWrapper();
}
/**
* This method clears the PersistenceManager and the candidateCollection fields.
* Then this query instance cannot be executed anymore, but it might be used to
* create a new equivalent query instance by passing this query instance to
* PersistenceManager newQuery method taking a compiled query.
* <p>
* This method effectively disconnects the PersistenceManager allowing it to be
* garbage collected.
*/
public void clearPersistenceManager()
{
this.pm = null;
this.candidateCollection = null;
}
/**
* Internal method called by execute, executeWithArray, executeWithMap.
* - calls the code generation of the query compiler
* - flushes updates
* - executes the RetrieveDesc returned by the code generation
* - resets the compiler
*/
private Object doExecute(ParameterTable params)
{
Object result = null;
RetrieveDesc rd = null;
try
{
// We need to make sure that no parallel thread closes the pm =>
// try to get a shared lock for the pm. Today, the pm impl does
// not allow to promote a shared lock into a exclusive lock. Thus
// we need to get an exclusive lock here. Otherwise pm.internalFlush
// runs into a deadlock, because it tries to get a exclusive lock.
// This code need to be changed to get a ahared lock as soon as
// The next line might result in a NPE, if pm is closed or if the
// query instance was deserialized. Please note, I cannot check the
// pm and then get the lock, because the pm might be closed in
// parallel. Then subsequent uses of pm in doexecute would fail.
pm.acquireExclusiveLock();
}
catch (NullPointerException npe)
{
// NPE means pm is closed or query instance was serialized.
String key = (createdBySerialization ?
"query.queryimpl.doexecute.notboundtopm" : //NOI18N
"query.queryimpl.doexecute.pmclosed"); //NOI18N
JDOException ex = new JDOQueryException(
I18NHelper.getMessage(messages, key));
logger.throwing("query.QueryImpl", "compile", ex); //NOI18N
throw ex;
}
try
{
checkCandidates();
// call the code generation
rd = jqlc.codeGen(pm, params);
// flush changes (inserts, updates, deletes) to the datastore
flush();
if (logger.isLoggable(Logger.FINER))
logger.finer("LOG_ExecuteQuery", this, params.getValues()); //NOI18N
// Note, the RetrieveDesc returned by the code generation is null
// in the case of a query having a false filter =>
// do not go to the datastore, but return an emtpy collection
result = (rd != null) ? pm.retrieve(rd, params.getValueFetcher()) : new ArrayList();
}
finally
{
// Note, the following stmt needs to be replaced by
// pm.releaseSharedLock, as soon as the pm supports promoting a
// shared lock into an exclusive lock.
pm.releaseExclusiveLock();
}
return result;
}
/**
* This method checks a valid candidates setting for this query.
*/
private void checkCandidates()
{
if ((candidateCollection == null) && (candidateClass != null))
{
// Set candidateCollection to the extent of the candidate class, if
// candidateCollection is not specified. Note, the JDO spec defines
// subclasses=true as the default, but since this is not supported
// right now, I set it to subclasses=false.
candidateCollection = pm.getExtent(candidateClass, false);
}
else {
jqlc.checkCandidates(candidateClass, candidateCollection);
}
}
/**
*
*/
private void flush()
{
Transaction tx = pm.currentTransaction();
// flush updates to the database,
// - if the is a transaction active and
// - if transaction is not optimistic
// - if ignoreCache is false
if ((tx != null) && tx.isActive() &&
!tx.getOptimistic() && !this.ignoreCache)
{
pm.internalFlush();
}
}
/** Returns a string representation of the object. */
public String toString()
{
StringBuffer repr = new StringBuffer();
repr.append("QueryImpl("); //NOI18N
repr.append("candidateClass: "); //NOI18N
repr.append(candidateClass);
if (importDeclarations != null) {
repr.append(", imports: "); //NOI18N
repr.append(importDeclarations);
}
if (parameterDeclarations != null) {
repr.append(", parameters: "); //NOI18N
repr.append(parameterDeclarations);
}
if (variableDeclarations != null) {
repr.append(", variables: "); //NOI18N
repr.append(variableDeclarations);
}
if (filterExpression != null) {
repr.append(", filter: "); //NOI18N
repr.append(filterExpression);
}
if (orderingSpecification != null) {
repr.append(", ordering: "); //NOI18N
repr.append(orderingSpecification);
}
if (resultExpression != null) {
repr.append(", result: "); //NOI18N
repr.append(resultExpression);
}
repr.append(", prefetchEnabled: "); //NOI18N
repr.append(prefetchEnabled);
repr.append(", identity: "); //NOI18N
repr.append(identity());
repr.append(")"); //NOI18N
return repr.toString();
}
/** */
private String identity()
{
return "QueryImpl@" + System.identityHashCode(this); //NOI18N
}
/**
* Define readObject to initialize the transient field paramtab after deserialization.
* This object is used for synchronization, thus it cannot be null.
*/
private void readObject(java.io.ObjectInputStream in)
throws java.io.IOException, ClassNotFoundException
{
in.defaultReadObject();
this.paramtab = new ParameterTable();
this.createdBySerialization = true;
}
}
|