/** * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 1999 (C) Intalio, Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.jdo.engine; import java.util.Enumeration; import java.util.Hashtable; import java.util.NoSuchElementException; import java.util.Vector; import org.castor.core.util.AbstractProperties; import org.castor.core.util.Messages; import org.castor.cpa.CPAProperties; import org.castor.cpa.persistence.convertor.TypeConvertorRegistry; import org.castor.jdo.util.ClassLoadingUtils; import org.castor.persist.TransactionContext; import org.exolab.castor.jdo.Database; import org.exolab.castor.jdo.DbMetaInfo; import org.exolab.castor.jdo.OQLQuery; import org.exolab.castor.jdo.ObjectNotFoundException; import org.exolab.castor.jdo.PersistenceException; import org.exolab.castor.jdo.Query; import org.exolab.castor.jdo.QueryException; import org.exolab.castor.jdo.QueryResults; import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature; import org.exolab.castor.jdo.oql.Lexer; import org.exolab.castor.jdo.oql.ParamInfo; import org.exolab.castor.jdo.oql.ParseTreeNode; import org.exolab.castor.jdo.oql.ParseTreeWalker; import org.exolab.castor.jdo.oql.Parser; import org.exolab.castor.mapping.AccessMode; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.TypeConvertor; import org.exolab.castor.persist.ClassMolder; import org.exolab.castor.persist.LockEngine; import org.exolab.castor.persist.spi.Identity; import org.exolab.castor.persist.spi.PersistenceQuery; import org.exolab.castor.persist.spi.QueryExpression; /** * An OQLQuery implementation to execute a query based upon an OQL statement. * * @author <a href="arkin@intalio.com">Assaf Arkin</a> * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ */ public class OQLQueryImpl implements Query, OQLQuery { private static TypeConvertorRegistry _typeConvertorRegistry = null; private LockEngine _dbEngine; private Database _database; private Class _objClass; private ClassDescriptor _clsDesc; private QueryExpression _expr; /** Stored procedure call. */ private String _spCall; private Class[] _bindTypes; private Object[] _bindValues; private Hashtable<Integer, ParamInfo> _paramInfo; private int _fieldNum; private int _projectionType; private Vector<String> _projectionInfo; private PersistenceQuery _query; private QueryResults _results; /** * Creates an instance to execute a query based upon an OQL statement. * * @param database The Castor database to run the query against. */ OQLQueryImpl(final Database database) { _database = database; } private TypeConvertorRegistry getTypeConvertorRegistry() { if (_typeConvertorRegistry == null) { AbstractProperties properties = CPAProperties.getInstance(); _typeConvertorRegistry = new TypeConvertorRegistry(properties); } return _typeConvertorRegistry; } /** * @inheritDoc * @see org.exolab.castor.jdo.Query#bind(java.lang.Object) */ public void bind(final Object value) { Object internalValue = value; if ((_expr == null) && (_spCall == null)) { throw new IllegalStateException("Must create query before using it"); } if (_fieldNum == _paramInfo.size()) { throw new IllegalArgumentException("Only " + _paramInfo.size() + " fields in this query"); } try { ParamInfo info = _paramInfo.get(new Integer(_fieldNum + 1)); //do type checking and conversion Class paramClass = info.getTheClass(); Class fieldClass = info.getFieldType(); Class sqlClass = info.getSQLType(); if (internalValue != null) { Class valueClass = internalValue.getClass(); if (paramClass.isAssignableFrom(valueClass)) { LockEngine lockEngine = ((AbstractDatabaseImpl) _database).getLockEngine(); ClassMolder molder = lockEngine.getClassMolder(valueClass); if (molder != null) { Identity temp = molder.getActualIdentity( _database.getClassLoader(), internalValue); if (temp == null) { internalValue = null; } else if (temp.size() == 1) { internalValue = temp.get(0); } else { throw new IllegalArgumentException( "Unable to bind multi column identities"); } } } else if (info.isUserDefined()) { //If the user specified a type they must pass that exact type. throw new IllegalArgumentException( "Query paramter " + (_fieldNum + 1) + " is not of the expected type " + paramClass + " it is an instance of the class " + valueClass); } if ((sqlClass != null) && !sqlClass.isAssignableFrom(valueClass)) { // First convert the actual value to the field value if (fieldClass != valueClass) { try { TypeConvertor tc = getTypeConvertorRegistry().getConvertor( valueClass, fieldClass, null); internalValue = tc.convert(internalValue); } catch (MappingException e) { throw new IllegalArgumentException("Query parameter " + (_fieldNum + 1) + " cannot be converted from " + valueClass + " to " + paramClass + ", because no convertor can be found."); } } // Perform conversion from field type to SQL type, if needed if (info.getConvertor() != null) { internalValue = info.getConvertor().convert(internalValue); } } } if (_bindValues == null) { _bindValues = new Object[ _bindTypes.length ]; } _bindValues[_fieldNum++] = internalValue; } catch (IllegalArgumentException except) { throw except; } } /** * @inheritDoc */ public void bind(final boolean value) { bind(new Boolean(value)); } /** * @inheritDoc */ public void bind(final short value) { bind(new Short(value)); } /** * @inheritDoc */ public void bind(final int value) { bind(new Integer(value)); } /** * @inheritDoc */ public void bind(final long value) { bind(new Long(value)); } /** * @inheritDoc */ public void bind(final float value) { bind(new Float(value)); } /** * @inheritDoc */ public void bind(final double value) { bind(new Double(value)); } /** * @inheritDoc */ public void create(final String oql) throws PersistenceException { _fieldNum = 0; _expr = null; _spCall = null; // Separate parser for CALL-type queries (using stored procedured) if (oql.startsWith("CALL ")) { createCall(oql); return; } Lexer lexer = new Lexer(oql); Parser parser = new Parser(lexer); ParseTreeNode parseTree = parser.getParseTree(); _dbEngine = ((AbstractDatabaseImpl) _database).getLockEngine(); if (_dbEngine == null) { throw new QueryException("Could not get a persistence engine"); } TransactionContext trans = ((AbstractDatabaseImpl) _database).getTransaction(); DbMetaInfo dbInfo = trans.getConnectionInfo(_dbEngine); ParseTreeWalker walker = new ParseTreeWalker(_dbEngine, parseTree, _database.getClassLoader(), dbInfo); _objClass = walker.getObjClass(); _clsDesc = walker.getClassDescriptor(); _expr = walker.getQueryExpression(); _paramInfo = walker.getParamInfo(); _projectionType = walker.getProjectionType(); _projectionInfo = walker.getProjectionInfo(); // create the types array and fill it _bindTypes = new Class[_paramInfo.size()]; int paramIndex = 0; for (Enumeration<ParamInfo> e = _paramInfo.elements(); e.hasMoreElements(); ) { ParamInfo info = e.nextElement(); _bindTypes[paramIndex++] = ((info.getSQLType() == null)) ? info.getTheClass() : info.getSQLType(); } } /** * @param oql * @throws QueryException */ public void createCall(final String oql) throws QueryException { StringBuffer sql; int as; int leftParen; int rightParen; int paramCnt; String objType; ParamInfo info; StringBuffer sb; Integer paramNo; if (!oql.startsWith("CALL ")) { throw new QueryException("Stored procedure call must start with CALL"); } // Fix for bug #995 // as = oql.indexOf( " AS " ); as = oql.lastIndexOf(" AS "); if (as < 0) { throw new QueryException("Stored procedure call must end with \"AS <class-name>\""); } leftParen = oql.indexOf("("); rightParen = oql.indexOf(")"); sql = new StringBuffer(); paramCnt = 0; _paramInfo = new Hashtable<Integer, ParamInfo>(); if (oql.startsWith("CALL SQL")) { int startOff = oql.toUpperCase().indexOf("WHERE "); // parameters begin here! if (!(startOff < 0)) { startOff += 6; sql.append(oql.substring(5, startOff)); int i = startOff; while (i < as) { if (oql.charAt(i) == '$') { // get parameter number if given sb = new StringBuffer(); for (int j = i + 1; j < as; j++) { char c = oql.charAt(j); if (!Character.isDigit(c)) { break; } sb.append(c); } sql.append('?'); // replace "$" with "?" if (sb.length() > 0) { sql.append(sb); // and add parameter number to it paramNo = Integer.valueOf(sb.toString()); } else { paramNo = new Integer(paramCnt + 1); } info = _paramInfo.get(paramNo); if (info == null) { info = new ParamInfo("", "java.lang.Object", null, _database.getClassLoader()); } //info.mapToSQLParam( paramCnt + 1 ); _paramInfo.put(paramNo , info); paramCnt++; i += sb.length() + 1; } else { sql.append(oql.charAt(i)); i++; } } } else { sql.append(oql.substring(5, as)); } } else if ((leftParen < 0) && (rightParen < 0)) { sql.append(oql.substring(5, as)); } else { if (((leftParen < 0) && (rightParen >= 0)) || (leftParen > rightParen)) { throw new QueryException("Syntax error: parenthesis"); } sql.append(oql.substring(5, leftParen)); sql.append('('); for (int i = leftParen + 1; i < rightParen; i++) { if (oql.charAt(i) == '$') { // get parameter number if given sb = new StringBuffer(); for (int j = i + 1; j < rightParen; j++) { char c = oql.charAt(j); if (!Character.isDigit(c)) { break; } sb.append(c); } if (sb.length() > 0) { paramNo = Integer.valueOf(sb.toString()); } else { paramNo = new Integer(paramCnt + 1); } info = _paramInfo.get(paramNo); if (info == null) { info = new ParamInfo("", "java.lang.Object", null, _database.getClassLoader()); } //info.mapToSQLParam( paramCnt + 1 ); _paramInfo.put(paramNo , info); paramCnt++; } } for (int i = 0; i < paramCnt; i++) { sql.append('?'); if (i < paramCnt - 1) { sql.append(','); } } sql.append(')'); } _spCall = sql.toString(); _projectionType = ParseTreeWalker.PARENT_OBJECT; _bindTypes = new Class[ paramCnt ]; for (int i = 0; i < paramCnt; i++) { _bindTypes[i] = Object.class; } objType = oql.substring(as + 4).trim(); if (objType.length() == 0) { throw new QueryException("Missing object name"); } try { _objClass = ClassLoadingUtils.loadClass(_database.getClassLoader(), objType); } catch (ClassNotFoundException except) { throw new QueryException("Could not find class " + objType); } _dbEngine = ((AbstractDatabaseImpl) _database).getLockEngine(); if ((_dbEngine == null) || (_dbEngine.getPersistence(_objClass) == null)) { throw new QueryException("Could not find an engine supporting class " + objType); } } /** * @inheritDoc * @see org.exolab.castor.jdo.Query#execute() */ public QueryResults execute() throws PersistenceException { return execute(null); } /** * @inheritDoc * @see org.exolab.castor.jdo.Query#execute(boolean) */ public QueryResults execute(final boolean scrollable) throws PersistenceException { return execute(null, scrollable); } /** * @inheritDoc * @see org.exolab.castor.jdo.Query#execute(org.exolab.castor.mapping.AccessMode) */ public QueryResults execute(final AccessMode accessMode) throws PersistenceException { return execute(accessMode, false); } /** * @inheritDoc * @see org.exolab.castor.jdo.Query#execute(org.exolab.castor.mapping.AccessMode, boolean) */ public QueryResults execute(final AccessMode accessMode, final boolean scrollable) throws PersistenceException { org.exolab.castor.persist.QueryResults results; SQLEngine engine; if ((_expr == null) && (_spCall == null)) { throw new IllegalStateException("Must create query before using it"); } if (_results != null) { _results.close(); } try { switch (_projectionType) { case ParseTreeWalker.PARENT_OBJECT: case ParseTreeWalker.DEPENDANT_OBJECT: case ParseTreeWalker.DEPENDANT_OBJECT_VALUE: try { engine = (SQLEngine) _dbEngine.getPersistence(_objClass); if (_expr != null) { _query = engine.createQuery(_expr, _bindTypes, accessMode); } else { _query = engine.createCall(_spCall, _bindTypes); } if (_bindValues != null) { for (int i = 0; i < _bindValues.length; ++i) { _query.setParameter(i, _bindValues[i]); } } } catch (QueryException except) { throw new QueryException(except.getMessage()); } results = ((AbstractDatabaseImpl) _database).getTransaction().query( _dbEngine, _query, accessMode, scrollable); _fieldNum = 0; if (_projectionType == ParseTreeWalker.PARENT_OBJECT) { _results = new OQLEnumeration(results); } else { _results = new OQLEnumeration(results, _projectionInfo, _clsDesc); } break; case ParseTreeWalker.DEPENDANT_VALUE: case ParseTreeWalker.AGGREGATE: case ParseTreeWalker.FUNCTION: try { TransactionContext tx = ((AbstractDatabaseImpl) _database).getTransaction(); java.sql.Connection conn = tx.getConnection(_dbEngine); SimpleQueryExecutor sqe = new SimpleQueryExecutor(_database); _results = sqe.execute(conn, _expr, _bindValues); } catch (QueryException except) { throw new QueryException(Messages.message( "persist.simple.query.failed"), except); } _fieldNum = 0; break; default: throw new PersistenceException("Unknown projection type: " + _projectionType); } } catch (PersistenceException except) { throw except; } return _results; } /** * Get the generated SQL statement for this OQLQuery. * * @return A SQL statement. * @throws QueryException If the SQL query cannot be generated. */ public String getSQL() throws org.exolab.castor.jdo.QueryException { if (_expr != null) { return _expr.getStatement(true); } return _spCall; } /** * @inheritDoc */ public void close() { if (_query != null) { _query.close(); _query = null; } if (_results != null) { _results.close(); _results = null; } } /** * {@link java.util.Enumeration} implementation to traverse the result as returned by the * execution of the OQL query. */ class OQLEnumeration implements QueryResults, Enumeration<Object> { private Object _lastObject; private Vector<String> _pathInfo; private ClassDescriptor _classDescriptor; private org.exolab.castor.persist.QueryResults _results; /** * Creates an instance of this class. * @param results * @param pathInfo * @param clsDesc */ OQLEnumeration(final org.exolab.castor.persist.QueryResults results, final Vector<String> pathInfo, final ClassDescriptor clsDesc) { _results = results; _pathInfo = pathInfo; _classDescriptor = clsDesc; } OQLEnumeration(final org.exolab.castor.persist.QueryResults results) { _results = results; _pathInfo = null; _classDescriptor = null; } /** * @inheritDoc */ public boolean absolute(final int row) throws PersistenceException { return _results.absolute(row); } /** * @inheritDoc */ public int size() throws PersistenceException { return _results.size(); } /** * @inheritDoc */ public boolean hasMoreElements() { try { return hasMore(true); } catch (PersistenceException except) { // Will never happen return false; } } /** * @inheritDoc */ public boolean hasMore() throws PersistenceException { return hasMore(false); } public boolean hasMore(final boolean skipError) throws PersistenceException { Object identity; if (_lastObject != null) { return true; } if (_results == null) { return false; } try { identity = _results.nextIdentity(); while (identity != null) { try { _lastObject = _results.fetch(); if (_lastObject != null) { break; } } catch (ObjectNotFoundException except) { // Object not found, deleted, etc. Just skip to next one. identity = _results.nextIdentity(); } catch (PersistenceException except) { // Error occured. If not throwing exception just skip to // next object. identity = _results.nextIdentity(); if (!skipError) { throw except; } } } if (identity == null) { _results.close(); _results = null; } } catch (PersistenceException except) { _results.close(); _results = null; if (!skipError) { throw except; } } return (_lastObject != null); } /** * @inheritDoc */ public Object nextElement() throws NoSuchElementException { try { return next(true); } catch (PersistenceException except) { // Will never happen return null; } } /** * @inheritDoc */ public Object next() throws PersistenceException, NoSuchElementException { return next(false); } private Object next(final boolean skipError) throws PersistenceException, NoSuchElementException { Object identity; if (_lastObject != null) { Object result = _lastObject; _lastObject = null; if (_pathInfo == null) { return result; } return followPath(result); } if (_results == null) { throw new NoSuchElementException(); } try { identity = _results.nextIdentity(); while (identity != null) { try { Object result = _results.fetch(); if (result != null) { if (_pathInfo == null) { return result; } } return followPath(result); } catch (ObjectNotFoundException except) { // Object not found, deleted, etc. Just skip to next one. } catch (PersistenceException except) { // Error occured. If not throwing exception just skip to // next object. if (!skipError) { throw except; } } identity = _results.nextIdentity(); } if (identity == null) { _results.close(); _results = null; } } catch (PersistenceException except) { _results.close(); _results = null; if (!skipError) { throw except; } } throw new NoSuchElementException(); } /** * @inheritDoc */ public void close() { if (_results != null) { _results.close(); _results = null; } } private Object followPath(final Object parent) { ClassDescriptor curClassDesc = _classDescriptor; Object curObject = parent; for (int i = 1; i < _pathInfo.size(); i++) { String curFieldName = _pathInfo.elementAt(i); try { ClassDescriptorJDONature nature; nature = new ClassDescriptorJDONature(curClassDesc); FieldDescriptor curFieldDesc = nature.getField(curFieldName); FieldHandler handler = curFieldDesc.getHandler(); curObject = handler.getValue(curObject); curClassDesc = curFieldDesc.getClassDescriptor(); } catch (Exception ex) { throw new NoSuchElementException( "An exception was thrown trying to access get methods to follow " + "the path expression. " + ex.toString()); } } return curObject; } } }