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.jdbc;
023:
024: import java.lang.reflect.Method;
025: import java.sql.Connection;
026: import java.sql.PreparedStatement;
027: import java.sql.ResultSet;
028: import java.sql.SQLException;
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.List;
032: import java.util.StringTokenizer;
033: import java.util.Collections;
034: import java.util.Iterator;
035: import java.util.AbstractCollection;
036: import java.util.NoSuchElementException;
037: import java.util.Set;
038: import javax.ejb.FinderException;
039: import javax.ejb.EJBException;
040: import javax.transaction.Synchronization;
041:
042: import org.jboss.deployment.DeploymentException;
043: import org.jboss.ejb.EntityEnterpriseContext;
044: import org.jboss.ejb.GenericEntityObjectFactory;
045: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
046: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
047: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
048: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
049: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCQueryMetaData;
050: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCLeftJoinMetaData;
051: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationMetaData;
052: import org.jboss.ejb.plugins.cmp.ejbql.SelectFunction;
053: import org.jboss.logging.Logger;
054:
055: /**
056: * Abstract superclass of finder commands that return collections.
057: * Provides the handleResult() implementation that these all need.
058: *
059: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
060: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard �berg</a>
061: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
062: * @author <a href="mailto:shevlandj@kpi.com.au">Joe Shevland</a>
063: * @author <a href="mailto:justin@j-m-f.demon.co.uk">Justin Forder</a>
064: * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
065: * @version $Revision: 57209 $
066: */
067: public abstract class JDBCAbstractQueryCommand implements
068: JDBCQueryCommand {
069: private JDBCQueryMetaData queryMetaData;
070: protected Logger log;
071:
072: private JDBCStoreManager selectManager;
073: private JDBCEntityBridge selectEntity;
074: private JDBCCMPFieldBridge selectField;
075: private SelectFunction selectFunction;
076: private boolean[] eagerLoadMask;
077: private String eagerLoadGroup;
078: private String sql;
079: private int offsetParam;
080: private int offsetValue;
081: private int limitParam;
082: private int limitValue;
083: private List parameters = new ArrayList(0);
084: private List onFindCMRList = Collections.EMPTY_LIST;
085: private QueryCollectionFactory collectionFactory;
086:
087: public JDBCAbstractQueryCommand(JDBCStoreManager manager,
088: JDBCQueryMetaData q) throws DeploymentException {
089: this .log = Logger.getLogger(this .getClass().getName() + "."
090: + manager.getMetaData().getName() + "#"
091: + q.getMethod().getName());
092:
093: queryMetaData = q;
094: collectionFactory = q.isLazyResultSetLoading() ? new LazyCollectionFactory()
095: : (QueryCollectionFactory) new EagerCollectionFactory();
096:
097: // setDefaultOffset(q.getOffsetParam());
098: // setDefaultLimit(q.getLimitParam());
099: setSelectEntity((JDBCEntityBridge) manager.getEntityBridge());
100: }
101:
102: public void setOffsetValue(int offsetValue) {
103: this .offsetValue = offsetValue;
104: }
105:
106: public void setLimitValue(int limitValue) {
107: this .limitValue = limitValue;
108: }
109:
110: public void setOffsetParam(int offsetParam) {
111: this .offsetParam = offsetParam;
112: }
113:
114: public void setLimitParam(int limitParam) {
115: this .limitParam = limitParam;
116: }
117:
118: public void setOnFindCMRList(List onFindCMRList) {
119: this .onFindCMRList = onFindCMRList;
120: }
121:
122: public JDBCStoreManager getSelectManager() {
123: return selectManager;
124: }
125:
126: public Collection execute(Method finderMethod, Object[] args,
127: EntityEnterpriseContext ctx,
128: GenericEntityObjectFactory factory) throws FinderException {
129: int offset = toInt(args, offsetParam, offsetValue);
130: int limit = toInt(args, limitParam, limitValue);
131: return execute(sql, args, offset, limit, selectEntity,
132: selectField, selectFunction, selectManager,
133: eagerLoadMask, parameters, onFindCMRList,
134: queryMetaData, factory, log);
135: }
136:
137: protected static int toInt(Object[] params, int paramNumber,
138: int defaultValue) {
139: if (paramNumber == 0) {
140: return defaultValue;
141: }
142: Integer arg = (Integer) params[paramNumber - 1];
143: return arg.intValue();
144: }
145:
146: protected Collection execute(String sql, Object[] args, int offset,
147: int limit, JDBCEntityBridge selectEntity,
148: JDBCCMPFieldBridge selectField,
149: SelectFunction selectFunction,
150: JDBCStoreManager selectManager, boolean[] eagerLoadMask,
151: List parameters, List onFindCMRList,
152: JDBCQueryMetaData queryMetaData,
153: GenericEntityObjectFactory factory, Logger log)
154: throws FinderException {
155: int count = offset;
156: Connection con = null;
157: PreparedStatement ps = null;
158: ResultSet rs = null;
159: final JDBCEntityBridge entityBridge = (JDBCEntityBridge) selectManager
160: .getEntityBridge();
161: boolean throwRuntimeExceptions = entityBridge.getMetaData()
162: .getThrowRuntimeExceptions();
163:
164: // if metadata is true, the getconnection is done inside
165: // its own try catch block to throw a runtime exception (EJBException)
166: if (throwRuntimeExceptions) {
167: try {
168: con = entityBridge.getDataSource().getConnection();
169: } catch (SQLException sqle) {
170: javax.ejb.EJBException ejbe = new javax.ejb.EJBException(
171: "Could not get a connection; " + sqle);
172: ejbe.initCause(sqle);
173: throw ejbe;
174: }
175: }
176:
177: try {
178: // create the statement
179: if (log.isDebugEnabled()) {
180: log.debug("Executing SQL: " + sql);
181: if (limit != 0 || offset != 0) {
182: log.debug("Query offset=" + offset + ", limit="
183: + limit);
184: }
185: }
186:
187: // if metadata is false, the getconnection is done inside this try catch block
188: if (!throwRuntimeExceptions) {
189: con = entityBridge.getDataSource().getConnection();
190: }
191: ps = con.prepareStatement(sql);
192:
193: // Set the fetch size of the statement
194: if (entityBridge.getFetchSize() > 0) {
195: ps.setFetchSize(entityBridge.getFetchSize());
196: }
197:
198: // set the parameters
199: for (int i = 0; i < parameters.size(); i++) {
200: QueryParameter parameter = (QueryParameter) parameters
201: .get(i);
202: parameter.set(log, ps, i + 1, args);
203: }
204:
205: // execute statement
206: rs = ps.executeQuery();
207:
208: // skip 'offset' results
209: while (count > 0 && rs.next()) {
210: count--;
211: }
212:
213: count = limit;
214: } catch (Exception e) {
215: JDBCUtil.safeClose(rs);
216: JDBCUtil.safeClose(ps);
217: JDBCUtil.safeClose(con);
218:
219: log.error("Find failed", e);
220: FinderException fe = new FinderException("Find failed: "
221: + e);
222: fe.initCause(e);
223: throw fe;
224: }
225:
226: return collectionFactory.createCollection(con, ps, rs, limit,
227: count, selectEntity, selectField, selectFunction,
228: selectManager, onFindCMRList, eagerLoadMask, factory);
229: }
230:
231: protected Logger getLog() {
232: return log;
233: }
234:
235: protected void setSQL(String sql) {
236: this .sql = sql;
237: if (log.isDebugEnabled()) {
238: log.debug("SQL: " + sql);
239: }
240: }
241:
242: protected void setParameterList(List p) {
243: for (int i = 0; i < p.size(); i++) {
244: if (!(p.get(i) instanceof QueryParameter)) {
245: throw new IllegalArgumentException("Element " + i
246: + " of list "
247: + "is not an instance of QueryParameter, but "
248: + p.get(i).getClass().getName());
249: }
250: }
251: parameters = new ArrayList(p);
252: }
253:
254: protected JDBCEntityBridge getSelectEntity() {
255: return selectEntity;
256: }
257:
258: protected void setSelectEntity(JDBCEntityBridge selectEntity)
259: throws DeploymentException {
260: if (queryMetaData.getMethod().getName().startsWith("find")
261: && this .selectEntity != null
262: && this .selectEntity != selectEntity) {
263: throw new DeploymentException("Finder "
264: + queryMetaData.getMethod().getName()
265: + " defined on "
266: + this .selectEntity.getEntityName()
267: + " should return only instances of "
268: + this .selectEntity.getEntityName()
269: + " but the query results in instances of "
270: + selectEntity.getEntityName());
271: }
272:
273: this .selectField = null;
274: this .selectFunction = null;
275: this .selectEntity = selectEntity;
276: this .selectManager = (JDBCStoreManager) selectEntity
277: .getManager();
278: }
279:
280: protected JDBCCMPFieldBridge getSelectField() {
281: return selectField;
282: }
283:
284: protected void setSelectField(JDBCCMPFieldBridge selectField) {
285: this .selectEntity = null;
286: this .selectFunction = null;
287: this .selectField = selectField;
288: this .selectManager = (JDBCStoreManager) selectField
289: .getManager();
290: }
291:
292: protected void setSelectFunction(SelectFunction func,
293: JDBCStoreManager manager) {
294: this .selectEntity = null;
295: this .selectField = null;
296: this .selectFunction = func;
297: this .selectManager = manager;
298: }
299:
300: protected void setEagerLoadGroup(String eagerLoadGroup) {
301: this .eagerLoadGroup = eagerLoadGroup;
302: this .eagerLoadMask = selectEntity
303: .getLoadGroupMask(eagerLoadGroup);
304: }
305:
306: protected String getEagerLoadGroup() {
307: return eagerLoadGroup;
308: }
309:
310: protected boolean[] getEagerLoadMask() {
311: return this .eagerLoadMask;
312: }
313:
314: /**
315: * Replaces the parameters in the specifiec sql with question marks, and
316: * initializes the parameter setting code. Parameters are encoded in curly
317: * brackets use a zero based index.
318: *
319: * @param sql the sql statement that is parsed for parameters
320: * @return the original sql statement with the parameters replaced with a
321: * question mark
322: * @throws DeploymentException if a error occures while parsing the sql
323: */
324: protected String parseParameters(String sql)
325: throws DeploymentException {
326: StringBuffer sqlBuf = new StringBuffer();
327: ArrayList params = new ArrayList();
328:
329: // Replace placeholders {0} with ?
330: if (sql != null) {
331: sql = sql.trim();
332:
333: StringTokenizer tokens = new StringTokenizer(sql, "{}",
334: true);
335: while (tokens.hasMoreTokens()) {
336: String token = tokens.nextToken();
337: if (token.equals("{")) {
338: token = tokens.nextToken();
339: if (Character.isDigit(token.charAt(0))) {
340: QueryParameter parameter = new QueryParameter(
341: selectManager, queryMetaData
342: .getMethod(), token);
343:
344: // of if we are here we can assume that we have
345: // a parameter and not a function
346: sqlBuf.append("?");
347: params.add(parameter);
348:
349: if (!tokens.nextToken().equals("}")) {
350: throw new DeploymentException(
351: "Invalid parameter - missing closing '}' : "
352: + sql);
353: }
354: } else {
355: // ok we don't have a parameter, we have a function
356: // push the tokens on the buffer and continue
357: sqlBuf.append("{").append(token);
358: }
359: } else {
360: // not parameter... just append it
361: sqlBuf.append(token);
362: }
363: }
364: }
365:
366: parameters = params;
367:
368: return sqlBuf.toString();
369: }
370:
371: // Static
372:
373: public static List getLeftJoinCMRNodes(JDBCEntityBridge entity,
374: String path, Iterator leftJoinIter, Set declaredPaths)
375: throws DeploymentException {
376: List leftJoinCMRNodes;
377:
378: if (leftJoinIter.hasNext()) {
379: leftJoinCMRNodes = new ArrayList();
380: while (leftJoinIter.hasNext()) {
381: JDBCLeftJoinMetaData leftJoin = (JDBCLeftJoinMetaData) leftJoinIter
382: .next();
383: JDBCCMRFieldBridge cmrField = entity
384: .getCMRFieldByName(leftJoin.getCmrField());
385: if (cmrField == null) {
386: throw new DeploymentException(
387: "cmr-field in left-join was not found: cmr-field="
388: + leftJoin.getCmrField()
389: + ", entity="
390: + entity.getEntityName());
391: }
392:
393: List subNodes;
394: JDBCEntityBridge relatedEntity = cmrField
395: .getRelatedJDBCEntity();
396: String childPath = path + '.' + cmrField.getFieldName();
397: if (declaredPaths != null) {
398: declaredPaths.add(childPath);
399: }
400:
401: subNodes = getLeftJoinCMRNodes(relatedEntity,
402: childPath, leftJoin.getLeftJoins(),
403: declaredPaths);
404:
405: boolean[] mask = relatedEntity
406: .getLoadGroupMask(leftJoin.getEagerLoadGroup());
407: LeftJoinCMRNode node = new LeftJoinCMRNode(childPath,
408: cmrField, mask, subNodes);
409: leftJoinCMRNodes.add(node);
410: }
411: } else {
412: leftJoinCMRNodes = Collections.EMPTY_LIST;
413: }
414:
415: return leftJoinCMRNodes;
416: }
417:
418: public static final void leftJoinCMRNodes(String alias,
419: List onFindCMRNodes, AliasManager aliasManager,
420: StringBuffer sb) {
421: for (int i = 0; i < onFindCMRNodes.size(); ++i) {
422: LeftJoinCMRNode node = (LeftJoinCMRNode) onFindCMRNodes
423: .get(i);
424: JDBCCMRFieldBridge cmrField = node.cmrField;
425: JDBCEntityBridge relatedEntity = cmrField
426: .getRelatedJDBCEntity();
427: String relatedAlias = aliasManager.getAlias(node.path);
428:
429: JDBCRelationMetaData relation = cmrField.getMetaData()
430: .getRelationMetaData();
431: if (relation.isTableMappingStyle()) {
432: String relTableAlias = aliasManager
433: .getRelationTableAlias(node.path);
434: sb.append(" LEFT OUTER JOIN ").append(
435: cmrField.getQualifiedTableName()).append(' ')
436: .append(relTableAlias).append(" ON ");
437: SQLUtil.getRelationTableJoinClause(cmrField, alias,
438: relTableAlias, sb);
439:
440: sb.append(" LEFT OUTER JOIN ").append(
441: relatedEntity.getQualifiedTableName()).append(
442: ' ').append(relatedAlias).append(" ON ");
443: SQLUtil.getRelationTableJoinClause(cmrField
444: .getRelatedCMRField(), relatedAlias,
445: relTableAlias, sb);
446: } else {
447: // foreign key mapping style
448: sb.append(" LEFT OUTER JOIN ").append(
449: relatedEntity.getQualifiedTableName()).append(
450: ' ').append(relatedAlias).append(" ON ");
451: SQLUtil
452: .getJoinClause(cmrField, alias, relatedAlias,
453: sb);
454: }
455:
456: List subNodes = node.onFindCMRNodes;
457: if (!subNodes.isEmpty()) {
458: leftJoinCMRNodes(relatedAlias, subNodes, aliasManager,
459: sb);
460: }
461: }
462: }
463:
464: public static final void appendLeftJoinCMRColumnNames(
465: List onFindCMRNodes, AliasManager aliasManager,
466: StringBuffer sb) {
467: for (int i = 0; i < onFindCMRNodes.size(); ++i) {
468: LeftJoinCMRNode node = (LeftJoinCMRNode) onFindCMRNodes
469: .get(i);
470: JDBCCMRFieldBridge cmrField = node.cmrField;
471: JDBCEntityBridge relatedEntity = cmrField
472: .getRelatedJDBCEntity();
473: String childAlias = aliasManager.getAlias(node.path);
474:
475: // primary key fields
476: SQLUtil.appendColumnNamesClause(relatedEntity
477: .getPrimaryKeyFields(), childAlias, sb);
478:
479: // eager load group
480: if (node.eagerLoadMask != null) {
481: SQLUtil.appendColumnNamesClause(relatedEntity
482: .getTableFields(), node.eagerLoadMask,
483: childAlias, sb);
484: }
485:
486: List subNodes = node.onFindCMRNodes;
487: if (!subNodes.isEmpty()) {
488: appendLeftJoinCMRColumnNames(subNodes, aliasManager, sb);
489: }
490: }
491: }
492:
493: private static int loadOnFindCMRFields(Object pk,
494: List onFindCMRNodes, ResultSet rs, int index, Logger log) {
495: Object[] ref = new Object[1];
496: for (int nodeInd = 0; nodeInd < onFindCMRNodes.size(); ++nodeInd) {
497: LeftJoinCMRNode node = (LeftJoinCMRNode) onFindCMRNodes
498: .get(nodeInd);
499: JDBCCMRFieldBridge cmrField = node.cmrField;
500: ReadAheadCache myCache = cmrField.getJDBCStoreManager()
501: .getReadAheadCache();
502: JDBCEntityBridge relatedEntity = cmrField
503: .getRelatedJDBCEntity();
504: ReadAheadCache relatedCache = cmrField.getRelatedManager()
505: .getReadAheadCache();
506:
507: // load related id
508: ref[0] = null;
509: index = relatedEntity.loadPrimaryKeyResults(rs, index, ref);
510: Object relatedId = ref[0];
511: boolean cacheRelatedData = relatedId != null;
512:
513: if (pk != null) {
514: if (cmrField.getMetaData().getRelatedRole()
515: .isMultiplicityOne()) {
516: // cacheRelatedData the value
517: myCache.addPreloadData(pk, cmrField,
518: relatedId == null ? Collections.EMPTY_LIST
519: : Collections
520: .singletonList(relatedId));
521: } else {
522: Collection cachedValue = myCache.getCachedCMRValue(
523: pk, cmrField);
524: if (cachedValue == null) {
525: cachedValue = new ArrayList();
526: myCache.addPreloadData(pk, cmrField,
527: cachedValue);
528: }
529:
530: if (relatedId != null) {
531: if (cachedValue.contains(relatedId)) {
532: cacheRelatedData = false;
533: } else {
534: cachedValue.add(relatedId);
535: }
536: }
537: }
538: }
539:
540: // load eager load group
541: if (node.eagerLoadMask != null) {
542: JDBCFieldBridge[] tableFields = relatedEntity
543: .getTableFields();
544: for (int fieldInd = 0; fieldInd < tableFields.length; ++fieldInd) {
545: if (node.eagerLoadMask[fieldInd]) {
546: JDBCFieldBridge field = tableFields[fieldInd];
547: ref[0] = null;
548: index = field.loadArgumentResults(rs, index,
549: ref);
550:
551: if (cacheRelatedData) {
552: if (log.isTraceEnabled()) {
553: log.trace("Caching "
554: + relatedEntity.getEntityName()
555: + '[' + relatedId + "]."
556: + field.getFieldName() + "="
557: + ref[0]);
558: }
559: relatedCache.addPreloadData(relatedId,
560: field, ref[0]);
561: }
562: }
563: }
564: }
565:
566: List subNodes = node.onFindCMRNodes;
567: if (!subNodes.isEmpty()) {
568: index = loadOnFindCMRFields(relatedId, subNodes, rs,
569: index, log);
570: }
571: }
572:
573: return index;
574: }
575:
576: public static final class LeftJoinCMRNode {
577: public final String path;
578: public final JDBCCMRFieldBridge cmrField;
579: public final boolean[] eagerLoadMask;
580: public final List onFindCMRNodes;
581:
582: public LeftJoinCMRNode(String path,
583: JDBCCMRFieldBridge cmrField, boolean[] eagerLoadMask,
584: List onFindCMRNodes) {
585: this .path = path;
586: this .cmrField = cmrField;
587: this .eagerLoadMask = eagerLoadMask;
588: this .onFindCMRNodes = onFindCMRNodes;
589: }
590:
591: public boolean equals(Object o) {
592: boolean result;
593: if (o == this ) {
594: result = true;
595: } else if (o instanceof LeftJoinCMRNode) {
596: LeftJoinCMRNode other = (LeftJoinCMRNode) o;
597: result = cmrField == other.cmrField;
598: } else {
599: result = false;
600: }
601: return result;
602: }
603:
604: public int hashCode() {
605: return cmrField == null ? Integer.MIN_VALUE : cmrField
606: .hashCode();
607: }
608:
609: public String toString() {
610: return '[' + cmrField.getFieldName() + ": "
611: + onFindCMRNodes + ']';
612: }
613: }
614:
615: interface QueryCollectionFactory {
616: Collection createCollection(Connection con,
617: PreparedStatement ps, ResultSet rs, int limit,
618: int count, JDBCEntityBridge selectEntity,
619: JDBCCMPFieldBridge selectField,
620: SelectFunction selectFunction,
621: JDBCStoreManager selectManager, List onFindCMRList,
622: boolean[] eagerLoadMask,
623: GenericEntityObjectFactory factory)
624: throws FinderException;
625: }
626:
627: class EagerCollectionFactory implements QueryCollectionFactory {
628: public Collection createCollection(Connection con,
629: PreparedStatement ps, ResultSet rs, int limit,
630: int count, JDBCEntityBridge selectEntity,
631: JDBCCMPFieldBridge selectField,
632: SelectFunction selectFunction,
633: JDBCStoreManager selectManager, List onFindCMRList,
634: boolean[] eagerLoadMask,
635: GenericEntityObjectFactory factory)
636: throws FinderException {
637: try {
638: List results = new ArrayList();
639:
640: if (selectEntity != null) {
641: ReadAheadCache selectReadAheadCache = selectManager
642: .getReadAheadCache();
643: List ids = new ArrayList();
644:
645: boolean loadOnFindCmr = !onFindCMRList.isEmpty();
646: Object[] ref = new Object[1];
647: Object prevPk = null;
648:
649: while ((limit == 0 || count-- > 0) && rs.next()) {
650: int index = 1;
651:
652: // get the pk
653: index = selectEntity.loadPrimaryKeyResults(rs,
654: index, ref);
655: Object pk = ref[0];
656:
657: boolean addPk = (loadOnFindCmr ? !pk
658: .equals(prevPk) : true);
659: if (addPk) {
660: ids.add(pk);
661: results.add(factory.getEntityEJBObject(pk));
662: prevPk = pk;
663: }
664:
665: // read the preload fields
666: if (eagerLoadMask != null) {
667: JDBCFieldBridge[] tableFields = selectEntity
668: .getTableFields();
669: for (int i = 0; i < eagerLoadMask.length; i++) {
670: if (eagerLoadMask[i]) {
671: JDBCFieldBridge field = tableFields[i];
672: ref[0] = null;
673:
674: // read the value and store it in the readahead cache
675: index = field.loadArgumentResults(
676: rs, index, ref);
677:
678: if (addPk) {
679: selectReadAheadCache
680: .addPreloadData(pk,
681: field, ref[0]);
682: }
683: }
684: }
685:
686: if (!onFindCMRList.isEmpty()) {
687: index = loadOnFindCMRFields(pk,
688: onFindCMRList, rs, index, log);
689: }
690: }
691: }
692:
693: // add the results list to the cache
694: selectReadAheadCache.addFinderResults(ids,
695: queryMetaData.getReadAhead());
696: } else if (selectField != null) {
697: // load the field
698: Object[] valueRef = new Object[1];
699: while ((limit == 0 || count-- > 0) && rs.next()) {
700: valueRef[0] = null;
701: selectField
702: .loadArgumentResults(rs, 1, valueRef);
703: results.add(valueRef[0]);
704: }
705: } else {
706: while (rs.next()) {
707: results.add(selectFunction.readResult(rs));
708: }
709: }
710:
711: if (log.isDebugEnabled() && limit != 0 && count == 0) {
712: log.debug("Query result was limited to " + limit
713: + " row(s)");
714: }
715:
716: return results;
717: } catch (Exception e) {
718: log.error("Find failed", e);
719: throw new FinderException("Find failed: " + e);
720: } finally {
721: JDBCUtil.safeClose(rs);
722: JDBCUtil.safeClose(ps);
723: JDBCUtil.safeClose(con);
724: }
725: }
726:
727: }
728:
729: class LazyCollectionFactory implements QueryCollectionFactory {
730: public Collection createCollection(Connection con,
731: PreparedStatement ps, ResultSet rs, int limit,
732: int count, JDBCEntityBridge selectEntity,
733: JDBCCMPFieldBridge selectField,
734: SelectFunction selectFunction,
735: JDBCStoreManager selectManager, List onFindCMRList,
736: boolean[] eagerLoadMask,
737: GenericEntityObjectFactory factory)
738: throws FinderException {
739: return new LazyCollection(con, ps, rs, limit, count,
740: selectEntity, selectField, selectFunction,
741: selectManager, eagerLoadMask, factory);
742: }
743:
744: private class LazyCollection extends AbstractCollection {
745: private final Connection con;
746: private final PreparedStatement ps;
747: private final ResultSet rs;
748: private final int limit;
749: private int count;
750: private final JDBCEntityBridge selectEntity;
751: private final JDBCCMPFieldBridge selectField;
752: private final SelectFunction selectFunction;
753: private final JDBCStoreManager selectManager;
754: private final boolean[] eagerLoadMask;
755: private final GenericEntityObjectFactory factory;
756:
757: private Object prevPk;
758: private Object curPk;
759: private Object currentResult;
760:
761: Object[] ref = new Object[1];
762:
763: boolean loadOnFindCmr;
764:
765: private List results = null;
766: private Iterator firstIterator;
767: private int size;
768: private boolean resourcesClosed;
769:
770: public LazyCollection(final Connection con,
771: final PreparedStatement ps, final ResultSet rs,
772: int limit, int count,
773: JDBCEntityBridge selectEntity,
774: JDBCCMPFieldBridge selectField,
775: SelectFunction selectFunction,
776: JDBCStoreManager selectManager,
777: boolean[] eagerLoadMask,
778: GenericEntityObjectFactory factory) {
779: this .con = con;
780: this .ps = ps;
781: this .rs = rs;
782: this .limit = limit;
783: this .count = count;
784: this .selectEntity = selectEntity;
785: this .selectField = selectField;
786: this .selectFunction = selectFunction;
787: this .selectManager = selectManager;
788: this .eagerLoadMask = eagerLoadMask;
789: this .factory = factory;
790: loadOnFindCmr = !onFindCMRList.isEmpty();
791:
792: firstIterator = getFirstIterator();
793: if (firstIterator.hasNext()) {
794: try {
795: size = rs.getInt(1);
796: } catch (SQLException e) {
797: throw new EJBException(
798: "Failed to read ResultSet.", e);
799: }
800:
801: if (limit > 0 && size > limit) {
802: size = limit;
803: }
804: }
805:
806: if (size < 1) {
807: firstIterator = null;
808: results = new ArrayList(0);
809: closeResources();
810: } else {
811: results = new ArrayList(size);
812: try {
813: selectManager.getContainer()
814: .getTransactionManager()
815: .getTransaction()
816: .registerSynchronization(
817: new Synchronization() {
818: public void beforeCompletion() {
819: closeResources();
820: }
821:
822: public void afterCompletion(
823: int status) {
824: closeResources();
825: }
826: });
827: } catch (Exception e) {
828: throw new EJBException(
829: "Failed to obtain current transaction",
830: e);
831: }
832: }
833: }
834:
835: private void closeResources() {
836: if (!resourcesClosed) {
837: JDBCUtil.safeClose(rs);
838: JDBCUtil.safeClose(ps);
839: JDBCUtil.safeClose(con);
840: resourcesClosed = true;
841: }
842: }
843:
844: public Iterator iterator() {
845: return firstIterator != null ? firstIterator : results
846: .iterator();
847: }
848:
849: public int size() {
850: return firstIterator != null ? size : results.size();
851: }
852:
853: public boolean add(Object o) {
854: if (firstIterator == null) {
855: return results.add(o);
856: }
857: throw new IllegalStateException(
858: "Can't modify collection while the first iterator is not exhausted.");
859: }
860:
861: public boolean remove(Object o) {
862: if (firstIterator == null) {
863: return results.remove(o);
864: }
865: throw new IllegalStateException(
866: "Can't modify collection while the first iterator is not exhausted.");
867: }
868:
869: private boolean hasNextResult() {
870: try {
871: boolean has = (limit == 0 || count-- > 0)
872: && rs.next();
873: if (!has) {
874: if (log.isTraceEnabled()) {
875: log.trace("first iterator exhausted!");
876: }
877: firstIterator = null;
878: closeResources();
879: }
880: return has;
881: } catch (Exception e) {
882: log.error("Failed to read ResultSet.", e);
883: throw new EJBException("Failed to read ResultSet: "
884: + e.getMessage());
885: }
886: }
887:
888: private Object readNext() {
889: try {
890: if (selectEntity != null) {
891: ReadAheadCache selectReadAheadCache = selectManager
892: .getReadAheadCache();
893:
894: // first one is size
895: int index = 2;
896:
897: // get the pk
898: index = selectEntity.loadPrimaryKeyResults(rs,
899: index, ref);
900: curPk = ref[0];
901:
902: boolean addPk = (loadOnFindCmr ? !curPk
903: .equals(prevPk) : true);
904: if (addPk) {
905: prevPk = curPk;
906: currentResult = factory
907: .getEntityEJBObject(curPk);
908: }
909:
910: // read the preload fields
911: if (eagerLoadMask != null) {
912: JDBCFieldBridge[] tableFields = selectEntity
913: .getTableFields();
914: for (int i = 0; i < eagerLoadMask.length; i++) {
915: if (eagerLoadMask[i]) {
916: JDBCFieldBridge field = tableFields[i];
917: ref[0] = null;
918:
919: // read the value and store it in the readahead cache
920: index = field.loadArgumentResults(
921: rs, index, ref);
922:
923: if (addPk) {
924: selectReadAheadCache
925: .addPreloadData(curPk,
926: field, ref[0]);
927: }
928: }
929: }
930:
931: if (!onFindCMRList.isEmpty()) {
932: index = loadOnFindCMRFields(curPk,
933: onFindCMRList, rs, index, log);
934: }
935: }
936: } else if (selectField != null) {
937: // load the field
938: selectField.loadArgumentResults(rs, 2, ref);
939: currentResult = ref[0];
940: } else {
941: currentResult = selectFunction.readResult(rs);
942: }
943:
944: if (log.isTraceEnabled() && limit != 0
945: && count == 0) {
946: log.trace("Query result was limited to "
947: + limit + " row(s)");
948: }
949:
950: return currentResult;
951: } catch (Exception e) {
952: log.error("Failed to read ResultSet", e);
953: throw new EJBException("Failed to read ResultSet: "
954: + e.getMessage());
955: }
956: }
957:
958: private Iterator getFirstIterator() {
959: return new Iterator() {
960: private boolean hasNext;
961: private Object cursor;
962:
963: public boolean hasNext() {
964: return hasNext ? hasNext
965: : (hasNext = hasNextResult());
966: }
967:
968: public Object next() {
969: if (!hasNext()) {
970: throw new NoSuchElementException();
971: }
972: hasNext = false;
973:
974: cursor = readNext();
975: results.add(cursor);
976:
977: return cursor;
978: }
979:
980: public void remove() {
981: --size;
982: results.remove(cursor);
983: }
984: };
985: }
986: }
987: }
988: }
|