FileDocCategorySizeDatePackage
QueryImpl.javaAPI DocGlassfish v2 API26860Fri May 04 22:35:06 BST 2007com.sun.jdo.spi.persistence.support.sqlstore.query

QueryImpl.java

/*
 * 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;
    }

}