/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb.plugins.cmp.jdbc2; import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCEntityBridge2; import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCCMPFieldBridge2; import org.jboss.ejb.plugins.cmp.jdbc2.schema.Schema; import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil; import org.jboss.ejb.plugins.cmp.jdbc.QueryParameter; import org.jboss.ejb.plugins.cmp.ejbql.SelectFunction; import org.jboss.ejb.GenericEntityObjectFactory; import org.jboss.logging.Logger; import javax.ejb.FinderException; import javax.ejb.ObjectNotFoundException; import java.util.Collection; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.HashSet; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a> * @version <tt>$Revision: 81030 $</tt> */ public abstract class AbstractQueryCommand implements QueryCommand { static final CollectionFactory COLLECTION_FACTORY = new CollectionFactory() { public Collection newCollection() { return new ArrayList(); } }; static final CollectionFactory SET_FACTORY = new CollectionFactory() { public Collection newCollection() { return new HashSet(); } }; protected String sql; protected Logger log; protected JDBCEntityBridge2 entity; protected QueryParameter[] params = null; private CollectionFactory collectionFactory; private CollectionStrategy collectionStrategy; private ResultReader resultReader; private int offsetParam; private int offsetValue; private int limitParam; private int limitValue; // Protected protected void setResultType(Class clazz) { if(Set.class.isAssignableFrom(clazz)) { collectionFactory = SET_FACTORY; } else if(Collection.class.isAssignableFrom(clazz)) { collectionFactory = COLLECTION_FACTORY; } initCollectionStrategy(); } protected void setFieldReader(JDBCCMPFieldBridge2 field) { this.resultReader = new FieldReader(field); initCollectionStrategy(); } protected void setFunctionReader(SelectFunction func) { this.resultReader = new FunctionReader(func); initCollectionStrategy(); } protected void setEntityReader(JDBCEntityBridge2 entity, boolean searchableOnly) { this.entity = entity; this.resultReader = new EntityReader(entity, searchableOnly); initCollectionStrategy(); } private void initCollectionStrategy() { if(collectionFactory != null && resultReader != null) { collectionStrategy = new EagerCollectionStrategy(collectionFactory, resultReader, log); } } // QueryCommand implementation public JDBCStoreManager2 getStoreManager() { return (JDBCStoreManager2) entity.getManager(); } public Collection fetchCollection(Schema schema, GenericEntityObjectFactory factory, Object[] args) throws FinderException { int offset = toInt(args, offsetParam, offsetValue); int limit = toInt(args, limitParam, limitValue); return fetchCollection(entity, sql, params, offset, limit, collectionStrategy, schema, factory, args, log); } public Object fetchOne(Schema schema, GenericEntityObjectFactory factory, Object[] args) throws FinderException { schema.flush(); return executeFetchOne(args, factory); } public void setOffsetValue(int offsetValue) { this.offsetValue = offsetValue; } public void setLimitValue(int limitValue) { this.limitValue = limitValue; } public void setOffsetParam(int offsetParam) { this.offsetParam = offsetParam; } public void setLimitParam(int limitParam) { this.limitParam = limitParam; } // Protected protected static int toInt(Object[] params, int paramNumber, int defaultValue) { if(paramNumber == 0) { return defaultValue; } Integer arg = (Integer) params[paramNumber - 1]; return arg.intValue(); } protected Object executeFetchOne(Object[] args, GenericEntityObjectFactory factory) throws FinderException { return fetchOne(entity, sql, params, resultReader, args, factory, log); } static Collection fetchCollection(JDBCEntityBridge2 entity, String sql, QueryParameter[] params, int offset, int limit, CollectionStrategy collectionStrategy, Schema schema, GenericEntityObjectFactory factory, Object[] args, Logger log) throws FinderException { schema.flush(); int count = offset; Collection result; Connection con = null; PreparedStatement ps = null; ResultSet rs = null; boolean throwRuntimeExceptions = entity.getMetaData().getThrowRuntimeExceptions(); // if metadata is true, the getconnection is done inside // its own try catch block to throw a runtime exception (EJBException) if (throwRuntimeExceptions) { try { con = entity.getDataSource().getConnection(); } catch (SQLException sqle) { javax.ejb.EJBException ejbe = new javax.ejb.EJBException("Could not get a connection; " + sqle); ejbe.initCause(sqle); throw ejbe; } } try { if(log.isDebugEnabled()) { log.debug("executing: " + sql); } // if metadata is false, the getconnection is done inside this try catch block if ( ! throwRuntimeExceptions) { con = entity.getDataSource().getConnection(); } ps = con.prepareStatement(sql); if(params != null) { for(int i = 0; i < params.length; i++) { params[i].set(log, ps, i + 1, args); } } rs = ps.executeQuery(); // skip 'offset' results while(count > 0 && rs.next()) { count--; } count = limit; } catch(Exception e) { JDBCUtil.safeClose(rs); JDBCUtil.safeClose(ps); JDBCUtil.safeClose(con); log.error("Finder failed: " + e.getMessage(), e); FinderException fe = new FinderException(e.getMessage()); fe.initCause(e); throw fe; } result = collectionStrategy.readResultSet(con, ps, rs, limit, count, factory); return result; } static Object fetchOne(JDBCEntityBridge2 entity, String sql, QueryParameter[] params, ResultReader resultReader, Object[] args, GenericEntityObjectFactory factory, Logger log) throws FinderException { Object pk; Connection con = null; PreparedStatement ps = null; ResultSet rs = null; boolean throwRuntimeExceptions = entity.getMetaData().getThrowRuntimeExceptions(); // if metadata is true, the getconnection is done inside // its own try catch block to throw a runtime exception (EJBException) if (throwRuntimeExceptions) { try { con = entity.getDataSource().getConnection(); } catch (SQLException sqle) { javax.ejb.EJBException ejbe = new javax.ejb.EJBException("Could not get a connection; " + sqle); //ejbe.initCause(sqle); only for JBoss 4 and + throw ejbe; } } try { if(log.isDebugEnabled()) { log.debug("executing: " + sql); } // if metadata is false, the getconnection is done inside this try catch block if ( ! throwRuntimeExceptions) { con = entity.getDataSource().getConnection(); } ps = con.prepareStatement(sql); if(params != null) { for(int i = 0; i < params.length; i++) { params[i].set(log, ps, i + 1, args); } } rs = ps.executeQuery(); if(rs.next()) { pk = resultReader.readRow(rs, factory); if(rs.next()) { List list = new ArrayList(); list.add(pk); list.add(resultReader.readRow(rs, factory)); while(rs.next()) { list.add(resultReader.readRow(rs, factory)); } throw new FinderException("More than one instance matches the single-object finder criteria: " + list); } } else { throw new ObjectNotFoundException(); } } catch(FinderException e) { throw e; } catch(Exception e) { FinderException fe = new FinderException(e.getMessage()); fe.initCause(e); throw fe; } finally { JDBCUtil.safeClose(rs); JDBCUtil.safeClose(ps); JDBCUtil.safeClose(con); } return pk; } protected void setParameters(List p) { if(p.size() > 0) { params = new QueryParameter[p.size()]; for(int i = 0; i < p.size(); i++) { Object pi = p.get(i); if(!(pi instanceof QueryParameter)) { throw new IllegalArgumentException("Element " + i + " of list is not an instance of QueryParameter, but " + p.get(i).getClass().getName()); } params[i] = (QueryParameter) pi; } } } // Inner static interface CollectionFactory { Collection newCollection(); } static interface ResultReader { Object readRow(ResultSet rs, GenericEntityObjectFactory factory) throws SQLException; } static class EntityReader implements ResultReader { private final JDBCEntityBridge2 entity; private final boolean searchableOnly; public EntityReader(JDBCEntityBridge2 entity, boolean searchableOnly) { this.entity = entity; this.searchableOnly = searchableOnly; } public Object readRow(ResultSet rs, GenericEntityObjectFactory factory) { final Object pk = entity.getTable().loadRow(rs, searchableOnly); return pk == null ? null : factory.getEntityEJBObject(pk); } }; static class FieldReader implements ResultReader { private final JDBCCMPFieldBridge2 field; public FieldReader(JDBCCMPFieldBridge2 field) { this.field = field; } public Object readRow(ResultSet rs, GenericEntityObjectFactory factory) throws SQLException { return field.loadArgumentResults(rs, 1); } } static class FunctionReader implements ResultReader { private final SelectFunction function; public FunctionReader(SelectFunction function) { this.function = function; } public Object readRow(ResultSet rs, GenericEntityObjectFactory factory) throws SQLException { return function.readResult(rs); } } interface CollectionStrategy { Collection readResultSet(Connection con, PreparedStatement ps, ResultSet rs, int limit, int count, GenericEntityObjectFactory factory) throws FinderException; } static class EagerCollectionStrategy implements CollectionStrategy { private final CollectionFactory collectionFactory; private final ResultReader resultReader; private final Logger log; public EagerCollectionStrategy(CollectionFactory collectionFactory, ResultReader resultReader, Logger log) { this.collectionFactory = collectionFactory; this.resultReader = resultReader; this.log = log; } public Collection readResultSet(Connection con, PreparedStatement ps, ResultSet rs, int limit, int count, GenericEntityObjectFactory factory) throws FinderException { Collection result; try { if((limit == 0 || count-- > 0) && rs.next()) { result = collectionFactory.newCollection(); Object instance = resultReader.readRow(rs, factory); result.add(instance); while((limit == 0 || count-- > 0) && rs.next()) { instance = resultReader.readRow(rs, factory); result.add(instance); } } else { result = Collections.EMPTY_SET; } } catch(Exception e) { log.error("Finder failed: " + e.getMessage(), e); throw new FinderException(e.getMessage()); } finally { JDBCUtil.safeClose(rs); JDBCUtil.safeClose(ps); JDBCUtil.safeClose(con); } return result; } } }