001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.cmp.jdbc2;
023:
024: import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCEntityBridge2;
025: import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCCMPFieldBridge2;
026: import org.jboss.ejb.plugins.cmp.jdbc2.schema.Schema;
027: import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil;
028: import org.jboss.ejb.plugins.cmp.jdbc.QueryParameter;
029: import org.jboss.ejb.plugins.cmp.ejbql.SelectFunction;
030: import org.jboss.ejb.GenericEntityObjectFactory;
031: import org.jboss.logging.Logger;
032:
033: import javax.ejb.FinderException;
034: import javax.ejb.ObjectNotFoundException;
035: import java.util.Collection;
036: import java.util.ArrayList;
037: import java.util.Collections;
038: import java.util.List;
039: import java.util.Set;
040: import java.util.HashSet;
041: import java.sql.Connection;
042: import java.sql.PreparedStatement;
043: import java.sql.ResultSet;
044: import java.sql.SQLException;
045:
046: /**
047: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
048: * @version <tt>$Revision: 63030 $</tt>
049: */
050: public abstract class AbstractQueryCommand implements QueryCommand {
051: static final CollectionFactory COLLECTION_FACTORY = new CollectionFactory() {
052: public Collection newCollection() {
053: return new ArrayList();
054: }
055: };
056:
057: static final CollectionFactory SET_FACTORY = new CollectionFactory() {
058: public Collection newCollection() {
059: return new HashSet();
060: }
061: };
062:
063: protected String sql;
064: protected Logger log;
065: protected JDBCEntityBridge2 entity;
066: protected QueryParameter[] params = null;
067: private CollectionFactory collectionFactory;
068: private CollectionStrategy collectionStrategy;
069: private ResultReader resultReader;
070: private int offsetParam;
071: private int offsetValue;
072: private int limitParam;
073: private int limitValue;
074:
075: // Protected
076:
077: protected void setResultType(Class clazz) {
078: if (Set.class.isAssignableFrom(clazz)) {
079: collectionFactory = SET_FACTORY;
080: } else if (Collection.class.isAssignableFrom(clazz)) {
081: collectionFactory = COLLECTION_FACTORY;
082: }
083: initCollectionStrategy();
084: }
085:
086: protected void setFieldReader(JDBCCMPFieldBridge2 field) {
087: this .resultReader = new FieldReader(field);
088: initCollectionStrategy();
089: }
090:
091: protected void setFunctionReader(SelectFunction func) {
092: this .resultReader = new FunctionReader(func);
093: initCollectionStrategy();
094: }
095:
096: protected void setEntityReader(JDBCEntityBridge2 entity,
097: boolean searchableOnly) {
098: this .entity = entity;
099: this .resultReader = new EntityReader(entity, searchableOnly);
100: initCollectionStrategy();
101: }
102:
103: private void initCollectionStrategy() {
104: if (collectionFactory != null && resultReader != null) {
105: collectionStrategy = new EagerCollectionStrategy(
106: collectionFactory, resultReader, log);
107: }
108: }
109:
110: // QueryCommand implementation
111:
112: public JDBCStoreManager2 getStoreManager() {
113: return (JDBCStoreManager2) entity.getManager();
114: }
115:
116: public Collection fetchCollection(Schema schema,
117: GenericEntityObjectFactory factory, Object[] args)
118: throws FinderException {
119: int offset = toInt(args, offsetParam, offsetValue);
120: int limit = toInt(args, limitParam, limitValue);
121: return fetchCollection(entity, sql, params, offset, limit,
122: collectionStrategy, schema, factory, args, log);
123: }
124:
125: public Object fetchOne(Schema schema,
126: GenericEntityObjectFactory factory, Object[] args)
127: throws FinderException {
128: schema.flush();
129: return executeFetchOne(args, factory);
130: }
131:
132: public void setOffsetValue(int offsetValue) {
133: this .offsetValue = offsetValue;
134: }
135:
136: public void setLimitValue(int limitValue) {
137: this .limitValue = limitValue;
138: }
139:
140: public void setOffsetParam(int offsetParam) {
141: this .offsetParam = offsetParam;
142: }
143:
144: public void setLimitParam(int limitParam) {
145: this .limitParam = limitParam;
146: }
147:
148: // Protected
149:
150: protected static int toInt(Object[] params, int paramNumber,
151: int defaultValue) {
152: if (paramNumber == 0) {
153: return defaultValue;
154: }
155: Integer arg = (Integer) params[paramNumber - 1];
156: return arg.intValue();
157: }
158:
159: protected Object executeFetchOne(Object[] args,
160: GenericEntityObjectFactory factory) throws FinderException {
161: return fetchOne(entity, sql, params, resultReader, args,
162: factory, log);
163: }
164:
165: static Collection fetchCollection(JDBCEntityBridge2 entity,
166: String sql, QueryParameter[] params, int offset, int limit,
167: CollectionStrategy collectionStrategy, Schema schema,
168: GenericEntityObjectFactory factory, Object[] args,
169: Logger log) throws FinderException {
170: schema.flush();
171:
172: int count = offset;
173: Collection result;
174:
175: Connection con = null;
176: PreparedStatement ps = null;
177: ResultSet rs = null;
178: boolean throwRuntimeExceptions = entity.getMetaData()
179: .getThrowRuntimeExceptions();
180:
181: // if metadata is true, the getconnection is done inside
182: // its own try catch block to throw a runtime exception (EJBException)
183: if (throwRuntimeExceptions) {
184: try {
185: con = entity.getDataSource().getConnection();
186: } catch (SQLException sqle) {
187: javax.ejb.EJBException ejbe = new javax.ejb.EJBException(
188: "Could not get a connection; " + sqle);
189: ejbe.initCause(sqle);
190: throw ejbe;
191: }
192: }
193: try {
194: if (log.isDebugEnabled()) {
195: log.debug("executing: " + sql);
196: }
197:
198: // if metadata is false, the getconnection is done inside this try catch block
199: if (!throwRuntimeExceptions) {
200: con = entity.getDataSource().getConnection();
201: }
202: ps = con.prepareStatement(sql);
203:
204: if (params != null) {
205: for (int i = 0; i < params.length; i++) {
206: params[i].set(log, ps, i + 1, args);
207: }
208: }
209:
210: rs = ps.executeQuery();
211:
212: // skip 'offset' results
213: while (count > 0 && rs.next()) {
214: count--;
215: }
216:
217: count = limit;
218: } catch (Exception e) {
219: JDBCUtil.safeClose(rs);
220: JDBCUtil.safeClose(ps);
221: JDBCUtil.safeClose(con);
222:
223: log.error("Finder failed: " + e.getMessage(), e);
224: FinderException fe = new FinderException(e.getMessage());
225: fe.initCause(e);
226: throw fe;
227: }
228:
229: result = collectionStrategy.readResultSet(con, ps, rs, limit,
230: count, factory);
231:
232: return result;
233: }
234:
235: static Object fetchOne(JDBCEntityBridge2 entity, String sql,
236: QueryParameter[] params, ResultReader resultReader,
237: Object[] args, GenericEntityObjectFactory factory,
238: Logger log) throws FinderException {
239: Object pk;
240: Connection con = null;
241: PreparedStatement ps = null;
242: ResultSet rs = null;
243: boolean throwRuntimeExceptions = entity.getMetaData()
244: .getThrowRuntimeExceptions();
245:
246: // if metadata is true, the getconnection is done inside
247: // its own try catch block to throw a runtime exception (EJBException)
248: if (throwRuntimeExceptions) {
249: try {
250: con = entity.getDataSource().getConnection();
251: } catch (SQLException sqle) {
252: javax.ejb.EJBException ejbe = new javax.ejb.EJBException(
253: "Could not get a connection; " + sqle);
254: //ejbe.initCause(sqle); only for JBoss 4 and +
255: throw ejbe;
256: }
257: }
258: try {
259: if (log.isDebugEnabled()) {
260: log.debug("executing: " + sql);
261: }
262:
263: // if metadata is false, the getconnection is done inside this try catch block
264: if (!throwRuntimeExceptions) {
265: con = entity.getDataSource().getConnection();
266: }
267: ps = con.prepareStatement(sql);
268:
269: if (params != null) {
270: for (int i = 0; i < params.length; i++) {
271: params[i].set(log, ps, i + 1, args);
272: }
273: }
274:
275: rs = ps.executeQuery();
276: if (rs.next()) {
277: pk = resultReader.readRow(rs, factory);
278: if (rs.next()) {
279: List list = new ArrayList();
280: list.add(pk);
281: list.add(resultReader.readRow(rs, factory));
282: while (rs.next()) {
283: list.add(resultReader.readRow(rs, factory));
284: }
285: throw new FinderException(
286: "More than one instance matches the single-object finder criteria: "
287: + list);
288: }
289: } else {
290: throw new ObjectNotFoundException();
291: }
292: } catch (FinderException e) {
293: throw e;
294: } catch (Exception e) {
295: FinderException fe = new FinderException(e.getMessage());
296: fe.initCause(e);
297: throw fe;
298: } finally {
299: JDBCUtil.safeClose(rs);
300: JDBCUtil.safeClose(ps);
301: JDBCUtil.safeClose(con);
302: }
303:
304: return pk;
305: }
306:
307: protected void setParameters(List p) {
308: if (p.size() > 0) {
309: params = new QueryParameter[p.size()];
310: for (int i = 0; i < p.size(); i++) {
311: Object pi = p.get(i);
312: if (!(pi instanceof QueryParameter)) {
313: throw new IllegalArgumentException(
314: "Element "
315: + i
316: + " of list is not an instance of QueryParameter, but "
317: + p.get(i).getClass().getName());
318: }
319: params[i] = (QueryParameter) pi;
320: }
321: }
322: }
323:
324: // Inner
325:
326: static interface CollectionFactory {
327: Collection newCollection();
328: }
329:
330: static interface ResultReader {
331: Object readRow(ResultSet rs, GenericEntityObjectFactory factory)
332: throws SQLException;
333: }
334:
335: static class EntityReader implements ResultReader {
336: private final JDBCEntityBridge2 entity;
337: private final boolean searchableOnly;
338:
339: public EntityReader(JDBCEntityBridge2 entity,
340: boolean searchableOnly) {
341: this .entity = entity;
342: this .searchableOnly = searchableOnly;
343: }
344:
345: public Object readRow(ResultSet rs,
346: GenericEntityObjectFactory factory) {
347: final Object pk = entity.getTable().loadRow(rs,
348: searchableOnly);
349: return pk == null ? null : factory.getEntityEJBObject(pk);
350: }
351: };
352:
353: static class FieldReader implements ResultReader {
354: private final JDBCCMPFieldBridge2 field;
355:
356: public FieldReader(JDBCCMPFieldBridge2 field) {
357: this .field = field;
358: }
359:
360: public Object readRow(ResultSet rs,
361: GenericEntityObjectFactory factory) throws SQLException {
362: return field.loadArgumentResults(rs, 1);
363: }
364: }
365:
366: static class FunctionReader implements ResultReader {
367: private final SelectFunction function;
368:
369: public FunctionReader(SelectFunction function) {
370: this .function = function;
371: }
372:
373: public Object readRow(ResultSet rs,
374: GenericEntityObjectFactory factory) throws SQLException {
375: return function.readResult(rs);
376: }
377: }
378:
379: interface CollectionStrategy {
380: Collection readResultSet(Connection con, PreparedStatement ps,
381: ResultSet rs, int limit, int count,
382: GenericEntityObjectFactory factory)
383: throws FinderException;
384: }
385:
386: static class EagerCollectionStrategy implements CollectionStrategy {
387: private final CollectionFactory collectionFactory;
388: private final ResultReader resultReader;
389: private final Logger log;
390:
391: public EagerCollectionStrategy(
392: CollectionFactory collectionFactory,
393: ResultReader resultReader, Logger log) {
394: this .collectionFactory = collectionFactory;
395: this .resultReader = resultReader;
396: this .log = log;
397: }
398:
399: public Collection readResultSet(Connection con,
400: PreparedStatement ps, ResultSet rs, int limit,
401: int count, GenericEntityObjectFactory factory)
402: throws FinderException {
403: Collection result;
404: try {
405: if ((limit == 0 || count-- > 0) && rs.next()) {
406: result = collectionFactory.newCollection();
407: Object instance = resultReader.readRow(rs, factory);
408: result.add(instance);
409: while ((limit == 0 || count-- > 0) && rs.next()) {
410: instance = resultReader.readRow(rs, factory);
411: result.add(instance);
412: }
413: } else {
414: result = Collections.EMPTY_SET;
415: }
416: } catch (Exception e) {
417: log.error("Finder failed: " + e.getMessage(), e);
418: throw new FinderException(e.getMessage());
419: } finally {
420: JDBCUtil.safeClose(rs);
421: JDBCUtil.safeClose(ps);
422: JDBCUtil.safeClose(con);
423: }
424: return result;
425: }
426: }
427: }
|