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.sql.Connection;
025: import java.sql.PreparedStatement;
026: import java.sql.ResultSet;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.Iterator;
030: import java.util.HashMap;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Collections;
034: import javax.ejb.EJBException;
035:
036: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
037: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
038: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
039: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
040: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
041: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
042: import org.jboss.logging.Logger;
043: import org.jboss.deployment.DeploymentException;
044:
045: /**
046: * Loads relations for a particular entity from a relation table.
047: *
048: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
049: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
050: * @version $Revision: 57209 $
051: */
052: public final class JDBCLoadRelationCommand {
053: private final JDBCStoreManager manager;
054: private final JDBCEntityBridge entity;
055: private final Logger log;
056:
057: public JDBCLoadRelationCommand(JDBCStoreManager manager) {
058: this .manager = manager;
059: this .entity = (JDBCEntityBridge) manager.getEntityBridge();
060:
061: // Create the Log
062: log = Logger.getLogger(this .getClass().getName() + "."
063: + manager.getMetaData().getName());
064: }
065:
066: public Collection execute(JDBCCMRFieldBridge cmrField, Object pk) {
067: JDBCCMRFieldBridge relatedCMRField = (JDBCCMRFieldBridge) cmrField
068: .getRelatedCMRField();
069:
070: // get the read ahead cahces
071: ReadAheadCache readAheadCache = manager.getReadAheadCache();
072: ReadAheadCache relatedReadAheadCache = cmrField
073: .getRelatedManager().getReadAheadCache();
074:
075: // get the finder results associated with this context, if it exists
076: ReadAheadCache.EntityReadAheadInfo info = readAheadCache
077: .getEntityReadAheadInfo(pk);
078: List loadKeys = info.getLoadKeys();
079:
080: Connection con = null;
081: PreparedStatement ps = null;
082: ResultSet rs = null;
083: try {
084: // generate the sql
085: boolean[] preloadMask = getPreloadMask(cmrField);
086: String sql = getSQL(cmrField, preloadMask, loadKeys.size());
087:
088: // create the statement
089: if (log.isDebugEnabled())
090: log.debug("load relation SQL: " + sql);
091:
092: // get the connection
093: con = cmrField.getDataSource().getConnection();
094: ps = con.prepareStatement(sql.toString());
095:
096: // Set the fetch size of the statement
097: if (entity.getFetchSize() > 0) {
098: ps.setFetchSize(entity.getFetchSize());
099: }
100:
101: // get the load fields
102: JDBCCMPFieldBridge[] myKeyFields = getMyKeyFields(cmrField);
103: JDBCCMPFieldBridge[] relatedKeyFields = getRelatedKeyFields(cmrField);
104:
105: // set the parameters
106: int paramIndex = 1;
107: for (int i = 0; i < loadKeys.size(); i++) {
108: Object key = loadKeys.get(i);
109: for (int j = 0; j < myKeyFields.length; ++j)
110: paramIndex = myKeyFields[j]
111: .setPrimaryKeyParameters(ps, paramIndex,
112: key);
113: }
114:
115: // execute statement
116: rs = ps.executeQuery();
117:
118: // initialize the results map
119: Map resultsMap = new HashMap(loadKeys.size());
120: for (int i = 0; i < loadKeys.size(); ++i) {
121: resultsMap.put(loadKeys.get(i), new ArrayList());
122: }
123:
124: // load the results
125: Object[] ref = new Object[1];
126: while (rs.next()) {
127: // reset the column index for this row
128: int index = 1;
129:
130: // ref must be reset to null before each load
131: ref[0] = null;
132:
133: // if we are loading more then one entity, load the pk from the row
134: Object loadedPk = pk;
135: if (loadKeys.size() > 1) {
136: // load the pk
137: for (int i = 0; i < myKeyFields.length; ++i) {
138: index = myKeyFields[i].loadPrimaryKeyResults(
139: rs, index, ref);
140: if (ref[0] == null) {
141: break;
142: }
143: }
144: loadedPk = ref[0];
145: }
146:
147: // load the fk
148: ref[0] = null;
149: for (int i = 0; i < relatedKeyFields.length; ++i) {
150: index = relatedKeyFields[i].loadPrimaryKeyResults(
151: rs, index, ref);
152: if (ref[0] == null) {
153: break;
154: }
155: }
156: Object loadedFk = ref[0];
157:
158: if (loadedFk != null) {
159: // add this value to the list for loadedPk
160: List results = (List) resultsMap.get(loadedPk);
161: results.add(loadedFk);
162:
163: // if the related cmr field is single valued we can pre-load
164: // the reverse relationship
165: if (relatedCMRField.isSingleValued()) {
166: relatedReadAheadCache.addPreloadData(loadedFk,
167: relatedCMRField, Collections
168: .singletonList(loadedPk));
169: }
170:
171: // read the preload fields
172: if (preloadMask != null) {
173: JDBCFieldBridge[] relatedFields = cmrField
174: .getRelatedJDBCEntity()
175: .getTableFields();
176: for (int i = 0; i < relatedFields.length; ++i) {
177: if (preloadMask[i]) {
178: JDBCFieldBridge field = relatedFields[i];
179: ref[0] = null;
180:
181: // read the value and store it in the readahead cache
182: index = field.loadArgumentResults(rs,
183: index, ref);
184: relatedReadAheadCache.addPreloadData(
185: loadedFk, field, ref[0]);
186: }
187: }
188: }
189: }
190: }
191:
192: // set all of the preloaded values
193: JDBCReadAheadMetaData readAhead = relatedCMRField
194: .getReadAhead();
195: for (Iterator iter = resultsMap.keySet().iterator(); iter
196: .hasNext();) {
197: Object key = iter.next();
198:
199: // get the results for this key
200: List results = (List) resultsMap.get(key);
201:
202: // store the results list for readahead on-load
203: relatedReadAheadCache.addFinderResults(results,
204: readAhead);
205:
206: // store the preloaded relationship (unless this is the realts we
207: // are actually after)
208: if (!key.equals(pk)) {
209: readAheadCache.addPreloadData(key, cmrField,
210: results);
211: }
212: }
213:
214: // success, return the results
215: return (List) resultsMap.get(pk);
216: } catch (EJBException e) {
217: throw e;
218: } catch (Exception e) {
219: throw new EJBException("Load relation failed", e);
220: } finally {
221: JDBCUtil.safeClose(rs);
222: JDBCUtil.safeClose(ps);
223: JDBCUtil.safeClose(con);
224: }
225: }
226:
227: private String getSQL(JDBCCMRFieldBridge cmrField,
228: boolean[] preloadMask, int keyCount)
229: throws DeploymentException {
230: JDBCCMPFieldBridge[] myKeyFields = getMyKeyFields(cmrField);
231: JDBCCMPFieldBridge[] relatedKeyFields = getRelatedKeyFields(cmrField);
232: String relationTable = getQualifiedRelationTable(cmrField);
233: JDBCEntityBridge relatedEntity = cmrField
234: .getRelatedJDBCEntity();
235: String relatedTable = relatedEntity.getQualifiedTableName();
236:
237: // do we need to join the relation table and the related table
238: boolean join = ((preloadMask != null) || cmrField
239: .allFkFieldsMappedToPkFields())
240: && (relatedKeyFields != relatedEntity
241: .getPrimaryKeyFields());
242:
243: // aliases for the tables, only required if we are joining the tables
244: String relationTableAlias;
245: String relatedTableAlias;
246: if (join) {
247: relationTableAlias = getRelationTable(cmrField);
248: relatedTableAlias = (relatedTable.equals(relationTable) ? getRelationTable(cmrField)
249: + '_' + cmrField.getFieldName()
250: : relatedEntity.getTableName());
251: } else {
252: relationTableAlias = "";
253: relatedTableAlias = "";
254: }
255:
256: JDBCFunctionMappingMetaData selectTemplate = getSelectTemplate(cmrField);
257: return selectTemplate == null ? getPlainSQL(keyCount,
258: myKeyFields, relationTableAlias, relatedKeyFields,
259: preloadMask, cmrField, relatedTableAlias,
260: relationTable, join, relatedTable) : getSQLByTemplate(
261: keyCount, myKeyFields, relationTableAlias,
262: relatedKeyFields, preloadMask, cmrField,
263: relatedTableAlias, relationTable, join, relatedTable,
264: selectTemplate);
265: }
266:
267: private JDBCCMPFieldBridge[] getMyKeyFields(
268: JDBCCMRFieldBridge cmrField) {
269: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
270: // relation table
271: return (JDBCCMPFieldBridge[]) cmrField.getTableKeyFields();
272: } else if (cmrField.getRelatedCMRField().hasForeignKey()) {
273: // related has foreign key
274: return (JDBCCMPFieldBridge[]) cmrField.getRelatedCMRField()
275: .getForeignKeyFields();
276: } else {
277: // i have foreign key
278: return (JDBCCMPFieldBridge[]) entity.getPrimaryKeyFields();
279: }
280: }
281:
282: private static JDBCCMPFieldBridge[] getRelatedKeyFields(
283: JDBCCMRFieldBridge cmrField) {
284: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
285: // relation table
286: return (JDBCCMPFieldBridge[]) cmrField.getRelatedCMRField()
287: .getTableKeyFields();
288: } else if (cmrField.getRelatedCMRField().hasForeignKey()) {
289: // related has foreign key
290: return (JDBCCMPFieldBridge[]) cmrField
291: .getRelatedJDBCEntity().getPrimaryKeyFields();
292: } else {
293: // i have foreign key
294: return (JDBCCMPFieldBridge[]) cmrField
295: .getForeignKeyFields();
296: }
297: }
298:
299: private static boolean[] getPreloadMask(JDBCCMRFieldBridge cmrField) {
300: boolean[] preloadMask = null;
301: if (cmrField.getReadAhead().isOnFind()) {
302: JDBCEntityBridge relatedEntity = cmrField
303: .getRelatedJDBCEntity();
304: String eagerLoadGroup = cmrField.getReadAhead()
305: .getEagerLoadGroup();
306: preloadMask = relatedEntity
307: .getLoadGroupMask(eagerLoadGroup);
308: }
309: return preloadMask;
310: }
311:
312: private String getQualifiedRelationTable(JDBCCMRFieldBridge cmrField) {
313: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
314: // relation table
315: return cmrField.getQualifiedTableName();
316: } else if (cmrField.getRelatedCMRField().hasForeignKey()) {
317: // related has foreign key
318: return cmrField.getRelatedJDBCEntity()
319: .getQualifiedTableName();
320: } else {
321: // i have foreign key
322: return entity.getQualifiedTableName();
323: }
324: }
325:
326: private String getRelationTable(JDBCCMRFieldBridge cmrField) {
327: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
328: // relation table
329: return cmrField.getTableName();
330: } else if (cmrField.getRelatedCMRField().hasForeignKey()) {
331: // related has foreign key
332: return cmrField.getRelatedJDBCEntity().getTableName();
333: } else {
334: // i have foreign key
335: return entity.getTableName();
336: }
337: }
338:
339: private JDBCFunctionMappingMetaData getSelectTemplate(
340: JDBCCMRFieldBridge cmrField) throws DeploymentException {
341:
342: JDBCFunctionMappingMetaData selectTemplate = null;
343: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
344: // relation table
345: if (cmrField.getRelationMetaData().hasRowLocking()) {
346: selectTemplate = cmrField.getRelationMetaData()
347: .getTypeMapping().getRowLockingTemplate();
348: if (selectTemplate == null) {
349: throw new IllegalStateException(
350: "row-locking is not allowed for this type of datastore");
351: }
352: }
353: } else if (cmrField.getRelatedCMRField().hasForeignKey()) {
354: // related has foreign key
355: if (cmrField.getRelatedJDBCEntity().getMetaData()
356: .hasRowLocking()) {
357: selectTemplate = cmrField.getRelatedJDBCEntity()
358: .getMetaData().getTypeMapping()
359: .getRowLockingTemplate();
360: if (selectTemplate == null) {
361: throw new IllegalStateException(
362: "row-locking is not allowed for this type of datastore");
363: }
364: }
365: } else {
366: // i have foreign key
367: if (entity.getMetaData().hasRowLocking()) {
368: selectTemplate = entity.getMetaData().getTypeMapping()
369: .getRowLockingTemplate();
370: if (selectTemplate == null) {
371: throw new IllegalStateException(
372: "row-locking is not allowed for this type of datastore");
373: }
374: }
375: }
376: return selectTemplate;
377: }
378:
379: private static String getPlainSQL(int keyCount,
380: JDBCCMPFieldBridge[] myKeyFields,
381: String relationTableAlias,
382: JDBCCMPFieldBridge[] relatedKeyFields,
383: boolean[] preloadMask, JDBCCMRFieldBridge cmrField,
384: String relatedTableAlias, String relationTable,
385: boolean join, String relatedTable) {
386: //
387: // column names clause
388: //
389: StringBuffer sql = new StringBuffer(400);
390: sql.append(SQLUtil.SELECT);
391:
392: if (keyCount > 1) {
393: SQLUtil.getColumnNamesClause(myKeyFields,
394: relationTableAlias, sql).append(SQLUtil.COMMA);
395: }
396: SQLUtil.getColumnNamesClause(relatedKeyFields,
397: relationTableAlias, sql);
398:
399: if (preloadMask != null) {
400: SQLUtil.appendColumnNamesClause(cmrField
401: .getRelatedJDBCEntity().getTableFields(),
402: preloadMask, relatedTableAlias, sql);
403: }
404:
405: //
406: // from clause
407: //
408: sql.append(SQLUtil.FROM).append(relationTable);
409: if (join) {
410: sql.append(' ').append(relationTableAlias).append(
411: SQLUtil.COMMA).append(relatedTable).append(' ')
412: .append(relatedTableAlias);
413: }
414:
415: //
416: // where clause
417: //
418: sql.append(SQLUtil.WHERE);
419: // add the join
420: if (join) {
421: // join the tables
422: sql.append('(');
423: SQLUtil.getJoinClause(
424: relatedKeyFields,
425: relationTableAlias,
426: cmrField.getRelatedJDBCEntity()
427: .getPrimaryKeyFields(), relatedTableAlias,
428: sql).append(')').append(SQLUtil.AND).append('(');
429: }
430:
431: // add the keys
432: String pkWhere = SQLUtil.getWhereClause(myKeyFields,
433: relationTableAlias, new StringBuffer(50)).toString();
434: for (int i = 0; i < keyCount; i++) {
435: if (i > 0)
436: sql.append(SQLUtil.OR);
437: sql.append('(').append(pkWhere).append(')');
438: }
439:
440: if (join)
441: sql.append(')');
442:
443: return sql.toString();
444: }
445:
446: private static String getSQLByTemplate(int keyCount,
447: JDBCCMPFieldBridge[] myKeyFields,
448: String relationTableAlias,
449: JDBCCMPFieldBridge[] relatedKeyFields,
450: boolean[] preloadMask, JDBCCMRFieldBridge cmrField,
451: String relatedTableAlias, String relationTable,
452: boolean join, String relatedTable,
453: JDBCFunctionMappingMetaData selectTemplate) {
454: //
455: // column names clause
456: //
457: StringBuffer columnNamesClause = new StringBuffer(100);
458: if (keyCount > 1) {
459: SQLUtil.getColumnNamesClause(myKeyFields,
460: relationTableAlias, columnNamesClause).append(
461: SQLUtil.COMMA);
462: }
463: SQLUtil.getColumnNamesClause(relatedKeyFields,
464: relationTableAlias, columnNamesClause);
465: if (preloadMask != null) {
466: SQLUtil.appendColumnNamesClause(cmrField
467: .getRelatedJDBCEntity().getTableFields(),
468: preloadMask, relatedTableAlias, columnNamesClause);
469: }
470:
471: //
472: // from clause
473: //
474: StringBuffer fromClause = new StringBuffer(100);
475: fromClause.append(relationTable);
476: if (join) {
477: fromClause.append(' ').append(relationTableAlias).append(
478: SQLUtil.COMMA).append(relatedTable).append(' ')
479: .append(relatedTableAlias);
480: }
481:
482: //
483: // where clause
484: //
485: StringBuffer whereClause = new StringBuffer(150);
486: // add the join
487: if (join) {
488: // join the tables
489: whereClause.append('(');
490: SQLUtil.getJoinClause(
491: relatedKeyFields,
492: relationTableAlias,
493: cmrField.getRelatedJDBCEntity()
494: .getPrimaryKeyFields(), relatedTableAlias,
495: whereClause).append(')').append(SQLUtil.AND)
496: .append('(');
497: }
498:
499: // add the keys
500: String pkWhere = SQLUtil.getWhereClause(myKeyFields,
501: relationTableAlias, new StringBuffer(50)).toString();
502: for (int i = 0; i < keyCount; i++) {
503: if (i > 0) {
504: whereClause.append(SQLUtil.OR);
505: }
506: whereClause.append('(').append(pkWhere).append(')');
507: }
508:
509: if (join) {
510: whereClause.append(')');
511: }
512:
513: //
514: // assemble pieces into final statement
515: //
516: String[] args = new String[] { columnNamesClause.toString(),
517: fromClause.toString(), whereClause.toString(), null // order by
518: };
519: return selectTemplate.getFunctionSql(args,
520: new StringBuffer(500)).toString();
521: }
522: }
|