Package org.jpox.store.rdbms.query

Source Code of org.jpox.store.rdbms.query.JPOXSQLQuery$JPOXSQLQueryEvaluator

/**********************************************************************
Copyright (c) 2002 Mike Martin (TJDO) and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors:
2003 Andy Jefferson - commented and localised
2005 Andy Jefferson - added timeout support
2005 Andy Jefferson - added support for use without candidate class
    ...
**********************************************************************/
package org.jpox.store.rdbms.query;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.jpox.ManagedConnection;
import org.jpox.ManagedConnectionResourceListener;
import org.jpox.ObjectManager;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.jdo.exceptions.ClassNotPersistenceCapableException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.FieldPersistenceModifier;
import org.jpox.metadata.FieldRole;
import org.jpox.metadata.IdentityType;
import org.jpox.store.Extent;
import org.jpox.store.mapped.DatastoreAdapter;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.StatementExpressionIndex;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.mapping.AbstractContainerMapping;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.Mappings;
import org.jpox.store.mapped.query.Evaluator;
import org.jpox.store.query.Query;
import org.jpox.store.query.QueryCompiler;
import org.jpox.store.query.QueryResult;
import org.jpox.store.query.ResultObjectFactory;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.store.rdbms.SQLController;
import org.jpox.store.rdbms.exceptions.PersistentSuperclassNotAllowedException;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
import org.jpox.util.MacroString;

/**
* Implementation of a query language based on SQL but allowing macros for parameter names and imports.
* Known internally as "JPOXSQL".
* This was written before the addition of SQL standard queries to JDO2 and so really is outdated
* and not recommended now, and we should consider removing support for it.
*/
public class JPOXSQLQuery extends Query
{
    /** Localiser of messages. */
    protected static final Localiser LOCALISER_RDBMS = Localiser.getInstance("org.jpox.store.rdbms.Localisation",
        RDBMSManager.class.getClassLoader());

    /** The statement that the user specified to the Query. */
    protected transient final String inputSQL;

    /** The actual SQL issued at execution time. */
    protected transient String compiledSQL = null;

    protected transient List parameterOccurrences = null;

    protected transient List fieldColumnNames = null;

    protected transient int[] fieldNumbers = null;

    protected transient StatementExpressionIndex[] statementExpressionIndex;

    /** Look-up for the parameter types, keyed by the name. */
    protected transient Map parameterTypesByName = null;

    /**
     * Constructor for a new query using the existing query.
     * @param om Object Manager
     * @param query The existing query
     */
    public JPOXSQLQuery(ObjectManager om, JPOXSQLQuery query)
    {
        this(om, query.inputSQL);
    }

    /**
     * Constructs a new query instance having the same criteria as the given query.
     * @param om The ObjectManager
     */
    public JPOXSQLQuery(ObjectManager om)
    {
        this(om, (String)null);
    }

    /**
     * Constructs a new query instance having the same criteria as the given* query.
     * @param om The ObjectManager
     * @param sqlText The JPOX SQL query string
     */
    public JPOXSQLQuery(ObjectManager om, String sqlText)
    {
        super(om);

        candidateClass = null;
        filter = null;
        imports = null;
        explicitVariables = null;
        explicitParameters = null;
        ordering = null;

        if (sqlText == null)
        {
            throw new JPOXUserException(LOCALISER.msg("059001"));
        }
        this.inputSQL = sqlText.trim();
    }

    /**
     * Utility to discard any compiled query.
     * @see org.jpox.store.query.Query#discardCompiled()
     */
    protected void discardCompiled()
    {
        super.discardCompiled();

        parameterOccurrences = null;
        fieldColumnNames = null;
        fieldNumbers = null;
        statementExpressionIndex = null;
        parameterTypesByName = null;
        compiledSQL = null;
    }

    /**
     * Accessor for the user-input SQL query.
     * @return User-input SQL
     */
    public String getInputSQL()
    {
        return inputSQL;
    }

    /**
     * Convenience method to return whether the query should return a single row.
     * @return Whether a single row should result
     */
    protected boolean shouldReturnSingleRow()
    {
        if (unique)
        {
            // UNIQUE implies a single row
            return true;
        }
        return false;
    }

    /**
     * Set the candidate Extent to query.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param pcs the Candidate Extent.
     * @throws JPOXUserException Always thrown since method not applicable
     */
    public void setCandidates(Extent pcs)
    {
        throw new JPOXUserException(LOCALISER.msg("059004"));
    }

    /**
     * Set the candidate Collection to query.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param pcs the Candidate collection.
     * @throws JPOXUserException Always thrown since method not applicable
     */
    public void setCandidates(Collection pcs)
    {
        throw new JPOXUserException(LOCALISER.msg("059005"));
    }

    /**
     * Set the result for the results. The application might want to get results
     * from a query that are not instances of the candidate class. The results
     * might be fields of persistent instances, instances of classes other than
     * the candidate class, or aggregates of fields.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param result The result parameter consists of the optional keyword
     * distinct followed by a commaseparated list of named result expressions or
     * a result class specification.
     * @throws JPOXUserException Always thrown.
     * @since 1.1
     */
    public void setResult(String result)
    {
        throw new JPOXUserException(LOCALISER.msg("059006"));
    }

    /**
     * Set the range of the results. Not applicable to SQL/JPOXSQL queries.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param fromIncl From element no (inclusive) to return
     * @param toExcl To element no (exclusive) to return
     * @throws JPOXUserException Always thrown.
     * @since 1.1
     */
    public void setRange(int fromIncl, int toExcl)
    {
        throw new JPOXUserException(LOCALISER.msg("059007"));
    }

    /**
     * Method to set whether to use subclasses. This is not used with JPOXSQL.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param subclasses Whether to use subclasses
     * @throws JPOXUserException Always thrown.
     */
    public void setSubclasses(boolean subclasses)
    {
        throw new JPOXUserException(LOCALISER.msg("059004"));
    }

    /**
     * Set the filter for the query.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param filter the query filter.
     * @throws JPOXUserException Always thrown since method not applicable
     */
    public void setFilter(String filter)
    {
        throw new JPOXUserException(LOCALISER.msg("059008"));
    }

    /**
     * Declare the unbound variables to be used in the query.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param variables the variables separated by semicolons.
     * @throws JPOXUserException Always thrown since method not applicable
     */
    public void declareExplicitVariables(String variables)
    {
        throw new JPOXUserException(LOCALISER.msg("059009"));
    }

    /**
     * Set the grouping specification for the result Collection.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param grouping the grouping specification.
     * @throws JPOXUserException  Always thrown.
     */
    public void setGrouping(String grouping)
    {
        throw new JPOXUserException(LOCALISER.msg("059010"));
    }

    /**
     * Set the ordering specification for the result Collection.
     * This implementation always throws a JPOXUserException since this concept doesn't apply to SQL queries.
     * @param ordering  the ordering specification.
     * @throws JPOXUserException  Always thrown.
     */
    public void setOrdering(String ordering)
    {
        throw new JPOXUserException(LOCALISER.msg("059011"));
    }

    /**
     * Execute the query to delete persistent objects.
     * @param parameters the Map containing all of the parameters.
     * @return the filtered QueryResult of the deleted objects.
     */
    protected long performDeletePersistentAll(Map parameters)
    {
        throw new JPOXUserException(LOCALISER.msg("059000"));
    }

    /**
     * Equality operator
     * @param obj The object to compare against
     * @return Whether they are equal
     */
    public boolean equals(Object obj)
    {
        if (obj == this)
        {
            return true;
        }

        if (!(obj instanceof JPOXSQLQuery) || !super.equals(obj))
        {
            return false;
        }

        return inputSQL.equals(((JPOXSQLQuery)obj).inputSQL);
    }

    /**
     * Verify the elements of the query and provide a hint to the query to prepare and optimize an execution plan.
     */
    public void compileInternal(boolean forExecute, Map parameterValues)
    {
        if (isCompiled)
        {
            return;
        }

        // Compiled explicit parameters since JPOXSQL supports them
        QueryCompiler c = new QueryCompiler(this, getParsedImports(), parameterValues);
        c.compile(QueryCompiler.COMPILE_EXPLICIT_PARAMETERS);
        parameterNames = c.getParameterNames();
        parameterTypesByName = c.getParameterTypesByName();

        // Generate the SQL
        compiledSQL = generateQueryStatement();
        if (JPOXLogger.QUERY.isDebugEnabled())
        {
            JPOXLogger.QUERY.debug(LOCALISER.msg("059012", compiledSQL)); // TODO Use JPOXSQL message not SQL
        }

        isCompiled = true;
    }

    /**
     * Method to perform any necessary pre-processing on the users query statement before we execute it.
     * @return The compiled SQL
     */
    protected String generateQueryStatement()
    {
        final AbstractClassMetaData candidateCmd;
        if (candidateClass == null)
        {
            // We have no candidate so don't need to bother about aligning the return columns to our mappings.
            fieldColumnNames = new ArrayList(); // Empty
            candidateCmd = null;
        }
        else
        {
            MappedStoreManager storeMgr = (MappedStoreManager)om.getStoreManager();
            DatastoreAdapter dba = storeMgr.getDatastoreAdapter();
            candidateCmd = om.getMetaDataManager().getMetaDataForClass(candidateClass,om.getClassLoaderResolver());
            if (candidateCmd == null)
            {
                throw new ClassNotPersistenceCapableException(candidateClass.getName());
            }
            if (candidateCmd.getPersistenceCapableSuperclass() != null)
            {
                throw new PersistentSuperclassNotAllowedException(candidateClass.getName());
            }
            if (candidateCmd.isRequiresExtent())
            {
                throw new JPOXUserException(LOCALISER_RDBMS.msg("060000",
                    candidateClass.getName()));
            }
            if (candidateCmd.getIdentityType() != IdentityType.NONDURABLE)
            {
                throw new JPOXUserException(LOCALISER_RDBMS.msg("060001",
                    candidateClass.getName()));
            }

            int fieldCount = candidateCmd.getNoOfManagedMembers();
            int[] fn = new int[fieldCount];
            statementExpressionIndex = new StatementExpressionIndex[fieldCount];
            fieldColumnNames = new ArrayList(fieldCount);
            int n = 0;

            for (int fieldNumber = 0; fieldNumber < fieldCount; ++fieldNumber)
            {
                statementExpressionIndex[fieldNumber] = new StatementExpressionIndex();
                AbstractMemberMetaData fmd = candidateCmd.getMetaDataForManagedMemberAtPosition(fieldNumber);
                String fieldName = fmd.getName();
                Class fieldType = fmd.getType();

                if (fmd.getPersistenceModifier() == FieldPersistenceModifier.PERSISTENT)
                {
                    JavaTypeMapping m = dba.getMapping(fieldType, storeMgr, om.getClassLoaderResolver());
                    if (m.includeInFetchStatement())
                    {
                        statementExpressionIndex[fieldNumber].setMapping(m);
                        fn[n++] = fieldNumber;

                        String columnName = null;
                        if (fmd.getColumnMetaData() != null && fmd.getColumnMetaData().length > 0)
                        {
                            columnName = fmd.getColumnMetaData()[0].getName();
                        }
                        else
                        {
                            columnName = storeMgr.getIdentifierFactory().newDatastoreFieldIdentifier(fieldName,
                                om.getOMFContext().getTypeManager().isDefaultEmbeddedType(fieldType),
                                FieldRole.ROLE_NONE).getIdentifier();
                        }
                        fieldColumnNames.add(columnName);
                    }

                    if (m instanceof AbstractContainerMapping)
                    {
                        // TODO Remove this. Why is it needed ?
                        throw new JPOXUserException("Mapping " + m +
                            " not suitable for a JPOXSQL result column, field = " + fieldName).setFatal();
                    }
                }
                else if (fmd.getPersistenceModifier() != FieldPersistenceModifier.TRANSACTIONAL)
                {
                    throw new JPOXException("Invalid persistence modifier on field " + fieldName).setFatal();
                }
            }

            if (n == 0)
            {
                throw new JPOXUserException("View class has no persistent fields: " + candidateClass.getName()).setFatal();
            }

            fieldNumbers = new int[n];
            System.arraycopy(fn, 0, fieldNumbers, 0, n);
        }

        // Generate the actual JDBC SQL text by processing the embedded parameter/field macros
        // in the provided JPOXSQL text.
        parameterOccurrences = new ArrayList();
        MacroString ms = new MacroString(candidateClass != null ? candidateClass.getName() : null,
                                         candidateClass != null ? imports : null, inputSQL);
        return ms.substituteMacros(new MacroString.MacroHandler()
            {
            public void onIdentifierMacro(MacroString.IdentifierMacro im)
            {
                // Only process identifier macros when the candidate class is known
                if (candidateClass != null)
                {
                    if (im.className.equals(candidateClass.getName()))
                    {
                        if (im.fieldName == null)
                        {
                            throw new JPOXUserException(LOCALISER_RDBMS.msg("060004",im));
                        }
                        if (im.subfieldName != null)
                        {
                            throw new JPOXUserException(LOCALISER_RDBMS.msg("060003",im.className,im));
                        }
                        int fieldNumber = candidateCmd.getRelativePositionOfMember(im.fieldName);
                       
                        if (fieldNumber < 0)
                        {
                            throw new JPOXUserException(LOCALISER_RDBMS.msg("060003",im.className,im));
                        }
                        im.value = (String) fieldColumnNames.get(fieldNumber);
                    }
                    else
                    {
                        ((RDBMSManager)getStoreManager()).resolveIdentifierMacro(im, om.getClassLoaderResolver());
                    }
                }
            }
           
            public void onParameterMacro(MacroString.ParameterMacro pm)
            {
                parameterOccurrences.add(pm.parameterName);
            }
            }, om.getClassLoaderResolver());
    }

    /**
     * Execute the query and return the filtered QueryResult.
     * @param parameters the Map containing all of the parameters.
     * @return the filtered QueryResult
     */
    public Object performExecute(Map parameters)
    {
        compileInternal(true, parameters);

        if (parameters.size() != (parameterNames != null ? parameterNames.length : 0))
        {
            throw new JPOXUserException(LOCALISER_RDBMS.msg("060002",
                "" + parameterNames.length, "" + parameters.size()));
        }

        Evaluator eval = new JPOXSQLQueryEvaluator(this, parameters);
        QueryResult qr = (QueryResult)eval.evaluate(null);
        queryResults.add(qr);

        return qr;
    }
   
    class JPOXSQLQueryEvaluator extends SQLEvaluator
    {
        Map parameters;
        public JPOXSQLQueryEvaluator(Query query, Map parameters)
        {
            super(query, false, null, null);
            this.parameters = parameters;
        }
       
        public Object evaluate(QueryExpression queryStmt)
        {
            QueryResult qr = null;
            try
            {
                RDBMSManager storeMgr = (RDBMSManager)om.getStoreManager();
                DatastoreAdapter dba = storeMgr.getDatastoreAdapter();
                ManagedConnection mconn = storeMgr.getConnection(om);
                Connection conn = (Connection) mconn.getConnection();
                SQLController sqlControl = storeMgr.getSQLController();

                try
                {
                    PreparedStatement ps = conn.prepareStatement(compiledSQL);
                    try
                    {
                        Iterator iter = parameterOccurrences.iterator();
                        int stmtParamNum = 1;

                        while (iter.hasNext())
                        {
                            String paramName = (String) iter.next();
                            Class paramType = (Class) parameterTypesByName.get(paramName);

                            if (!parameters.containsKey(paramName))
                            {
                                throw new JPOXUserException(LOCALISER_RDBMS.msg("060006",
                                    paramName));
                            }
                            if (paramType == null)
                            {
                                throw new JPOXUserException(LOCALISER_RDBMS.msg("060007",
                                    paramName));
                            }

                            JavaTypeMapping mapping = dba.getMapping(paramType, storeMgr,
                                om.getClassLoaderResolver());
                            Object paramValue = parameters.get(paramName);

                            mapping.setObject(om, ps, Mappings.getParametersIndex(stmtParamNum,mapping), paramValue);
                            if (mapping.getNumberOfDatastoreFields() == 0)
                            {
                                stmtParamNum++;
                            }
                            else
                            {
                                stmtParamNum += mapping.getNumberOfDatastoreFields();
                            }
                        }

                        // Apply any user-specified constraints over timeouts and ResultSet
                        prepareStatementForExecution(ps);

                        // Execute the query
                        ResultSet rs = sqlControl.executeStatementQuery(mconn, compiledSQL, ps);
                        try
                        {
                            ResultObjectFactory rof = null;
                            if (candidateClass != null)
                            {
                                ResultSetMetaData rsmd = rs.getMetaData();
                                HashSet remainingColumnNames = new HashSet(fieldColumnNames);

                                int colCount = rsmd.getColumnCount();
                                for (int colNum = 1; colNum <= colCount; ++colNum)
                                {
                                    String colName = rsmd.getColumnName(colNum);
                                    int fieldNumber = fieldColumnNames.indexOf(colName);
                                    if (fieldNumber >= 0)
                                    {
                                        statementExpressionIndex[fieldNumber].setExpressionIndex(new int[]{colNum});
                                        remainingColumnNames.remove(colName);
                                    }
                                }
                               
                                if (!remainingColumnNames.isEmpty())
                                {
                                    throw new JPOXUserException(LOCALISER_RDBMS.msg("060005",
                                        remainingColumnNames));
                                }

                                if (resultClass != null)
                                {
                                    rof = new ResultClassROF(resultClass, statementExpressionIndex);
                                }
                                else
                                {
                                    rof = new TransientIDROF(candidateClass, fieldNumbers, statementExpressionIndex);
                                }
                            }
                            else
                            {
                                rof = getResultObjectFactoryForNoCandidateClass(rs, resultClass);
                            }

                            // Return the associated type of results depending on whether insensitive or not
                            if (getResultSetType().equals("scroll-insensitive") ||
                                getResultSetType().equals("scroll-sensitive"))
                            {
                                qr = new ScrollableQueryResult(null, query, rof, rs, null);
                            }
                            else
                            {
                                qr = new ForwardQueryResult(null, query, rof, rs, null);
                            }
                            final QueryResult qr1 = qr;
                            final ManagedConnection mconn1 = mconn;
                            mconn.addListener(new ManagedConnectionResourceListener()
                            {
                                public void managedConnectionFlushed()
                                {
                                    qr1.disconnect();                       
                                }
                                public void managedConnectionPreClose() {}
                                public void managedConnectionPostClose() {}
                                public void resourcePostClose()
                                {
                                    mconn1.removeListener(this);
                                }
                            });
                        }
                        finally
                        {
                            if (qr == null)
                            {
                                rs.close();
                            }
                        }
                    }
                    finally
                    {
                        if (qr == null)
                        {
                            sqlControl.closeStatement(mconn, ps);
                        }
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER_RDBMS.msg("060008", compiledSQL), e);
            }
            return qr;
       }
    }  
}
TOP

Related Classes of org.jpox.store.rdbms.query.JPOXSQLQuery$JPOXSQLQueryEvaluator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.