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.List;
028: import javax.ejb.EJBException;
029: import javax.ejb.NoSuchEntityException;
030:
031: import org.jboss.ejb.EntityEnterpriseContext;
032: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
033: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
034: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
035: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
036: import org.jboss.logging.Logger;
037: import org.jboss.deployment.DeploymentException;
038:
039: /**
040: * JDBCLoadEntityCommand loads the data for an instance from the table.
041: * This command implements specified eager loading. For CMP 2.x, the
042: * entity can be configured to only load some of the fields, which is
043: * helpful for entitys with lots of data.
044: *
045: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
046: * @author <a href="mailto:on@ibis.odessa.ua">Oleg Nitz</a>
047: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard �berg</a>
048: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
049: * @author <a href="mailto:shevlandj@kpi.com.au">Joe Shevland</a>
050: * @author <a href="mailto:justin@j-m-f.demon.co.uk">Justin Forder</a>
051: * @author <a href="mailto:dirk@jboss.de">Dirk Zimmermann</a>
052: * @author <a href="mailto:danch@nvisia.com">danch (Dan Christopherson)</a>
053: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
054: * @version $Revision: 57209 $
055: */
056: public final class JDBCLoadEntityCommand {
057: private final JDBCStoreManager manager;
058: private final JDBCEntityBridge entity;
059: private final Logger log;
060: private final JDBCFunctionMappingMetaData rowLockingTemplate;
061:
062: public JDBCLoadEntityCommand(JDBCStoreManager manager)
063: throws DeploymentException {
064: this .manager = manager;
065: entity = (JDBCEntityBridge) manager.getEntityBridge();
066: boolean rawLocking = entity.getMetaData().hasRowLocking();
067: rowLockingTemplate = rawLocking ? entity.getMetaData()
068: .getTypeMapping().getRowLockingTemplate() : null;
069:
070: // Create the Log
071: log = Logger.getLogger(this .getClass().getName() + "."
072: + manager.getMetaData().getName());
073: }
074:
075: /**
076: * Loads entity.
077: * If failIfNotFound is true and entity wasn't found then NoSuchEntityException is thrown.
078: * Otherwise, if entity wasn't found, returns false.
079: * If entity was loaded successfully return true.
080: * @param ctx - entity context;
081: * @param failIfNotFound - whether to fail if entity wasn't found;
082: * @return true if entity was loaded, false - otherwise.
083: */
084: public boolean execute(EntityEnterpriseContext ctx,
085: boolean failIfNotFound) {
086: return execute(null, ctx, failIfNotFound);
087: }
088:
089: /**
090: * Loads entity or required field. If entity not found throws NoSuchEntityException.
091: * @param requiredField - required field or null;
092: * @param ctx - the corresponding context;
093: */
094: public void execute(JDBCCMPFieldBridge requiredField,
095: EntityEnterpriseContext ctx) {
096: execute(requiredField, ctx, true);
097: }
098:
099: /**
100: * Loads entity or required field.
101: * If failIfNotFound is set to true, then NoSuchEntityException is thrown if the
102: * entity wasn't found.
103: * If failIfNotFound is false then if the entity wasn't found returns false,
104: * if the entity was loaded successfully, returns true.
105: * @param requiredField - required field;
106: * @param ctx - entity context;
107: * @param failIfNotFound - whether to fail if entity wasn't loaded.
108: * @return true if entity was loaded, false - otherwise.
109: */
110: private boolean execute(JDBCCMPFieldBridge requiredField,
111: EntityEnterpriseContext ctx, boolean failIfNotFound) {
112: // load the instance primary key fields into the context
113: Object id = ctx.getId();
114: // TODO: when exactly do I need to do the following?
115: entity.injectPrimaryKeyIntoInstance(ctx, id);
116:
117: // get the read ahead cache
118: ReadAheadCache readAheadCache = manager.getReadAheadCache();
119:
120: // load any preloaded fields into the context
121: if (readAheadCache.load(ctx)) {
122: if (requiredField == null
123: || (requiredField != null && requiredField
124: .isLoaded(ctx))) {
125: return true;
126: }
127: }
128:
129: // get the finder results associated with this context, if it exists
130: ReadAheadCache.EntityReadAheadInfo info = readAheadCache
131: .getEntityReadAheadInfo(id);
132:
133: // determine the fields to load
134: JDBCEntityBridge.FieldIterator loadIter = entity
135: .getLoadIterator(requiredField, info.getReadAhead(),
136: ctx);
137: if (!loadIter.hasNext())
138: return true;
139:
140: // get the keys to load
141: List loadKeys = info.getLoadKeys();
142:
143: // generate the sql
144: String sql = (rowLockingTemplate != null ? getRawLockingSQL(
145: loadIter, loadKeys.size()) : getSQL(loadIter, loadKeys
146: .size()));
147:
148: Connection con = null;
149: PreparedStatement ps = null;
150: ResultSet rs = null;
151: try {
152: // create the statement
153: if (log.isDebugEnabled()) {
154: log.debug("Executing SQL: " + sql);
155: }
156:
157: // get the connection
158: con = entity.getDataSource().getConnection();
159: ps = con.prepareStatement(sql);
160:
161: // Set the fetch size of the statement
162: if (entity.getFetchSize() > 0) {
163: ps.setFetchSize(entity.getFetchSize());
164: }
165:
166: // set the parameters
167: int paramIndex = 1;
168: for (int i = 0; i < loadKeys.size(); i++) {
169: paramIndex = entity.setPrimaryKeyParameters(ps,
170: paramIndex, loadKeys.get(i));
171: }
172:
173: // execute statement
174: rs = ps.executeQuery();
175:
176: // load results
177: boolean mainEntityLoaded = false;
178: Object[] ref = new Object[1];
179: while (rs.next()) {
180: // reset the column index for this row
181: int index = 1;
182:
183: // ref must be reset to null before load
184: ref[0] = null;
185:
186: // if we are loading more then one entity, load the pk from the row
187: Object pk = null;
188: if (loadKeys.size() > 1) {
189: // load the pk
190: index = entity
191: .loadPrimaryKeyResults(rs, index, ref);
192: pk = ref[0];
193: }
194:
195: // is this the main entity or a preload entity
196: if (loadKeys.size() == 1 || pk.equals(id)) {
197: // main entity; load the values into the context
198: loadIter.reset();
199: while (loadIter.hasNext()) {
200: JDBCCMPFieldBridge field = loadIter.next();
201: index = field.loadInstanceResults(rs, index,
202: ctx);
203: field.setClean(ctx);
204: }
205: mainEntityLoaded = true;
206: } else {
207: // preload entity; load the values into the read ahead cahce
208: loadIter.reset();
209: while (loadIter.hasNext()) {
210: JDBCCMPFieldBridge field = loadIter.next();
211: // ref must be reset to null before load
212: ref[0] = null;
213:
214: // load the result of the field
215: index = field.loadArgumentResults(rs, index,
216: ref);
217:
218: // cache the field value
219: readAheadCache
220: .addPreloadData(pk, field, ref[0]);
221: }
222: }
223: }
224:
225: // clear LOAD_REQUIRED flag
226: loadIter.removeAll();
227:
228: // did we load the main results
229: if (!mainEntityLoaded) {
230: if (failIfNotFound)
231: throw new NoSuchEntityException(
232: "Entity not found: primaryKey="
233: + ctx.getId());
234: else
235: return false;
236: } else
237: return true;
238: } catch (EJBException e) {
239: throw e;
240: } catch (Exception e) {
241: throw new EJBException("Load failed", e);
242: } finally {
243: JDBCUtil.safeClose(rs);
244: JDBCUtil.safeClose(ps);
245: JDBCUtil.safeClose(con);
246: }
247: }
248:
249: private String getSQL(JDBCEntityBridge.FieldIterator loadIter,
250: int keyCount) {
251: StringBuffer sql = new StringBuffer(250);
252: sql.append(SQLUtil.SELECT);
253:
254: // if we are loading more then one entity we need to add the primry
255: // key to the load fields to match up the results with the correct entity.
256: JDBCFieldBridge[] primaryKeyFields = entity
257: .getPrimaryKeyFields();
258: if (keyCount > 1) {
259: SQLUtil.getColumnNamesClause(primaryKeyFields, sql);
260: sql.append(SQLUtil.COMMA);
261: }
262: SQLUtil.getColumnNamesClause(loadIter, sql);
263: sql.append(SQLUtil.FROM).append(entity.getQualifiedTableName())
264: .append(SQLUtil.WHERE);
265:
266: //
267: // where clause
268: String pkWhere = SQLUtil.getWhereClause(primaryKeyFields,
269: new StringBuffer(50)).toString();
270: sql.append('(').append(pkWhere).append(')');
271: for (int i = 1; i < keyCount; i++) {
272: sql.append(SQLUtil.OR).append('(').append(pkWhere).append(
273: ')');
274: }
275:
276: return sql.toString();
277: }
278:
279: private String getRawLockingSQL(
280: JDBCEntityBridge.FieldIterator loadIter, int keyCount) {
281: //
282: // column names clause
283: StringBuffer columnNamesClause = new StringBuffer(250);
284: // if we are loading more then one entity we need to add the primry
285: // key to the load fields to match up the results with the correct
286: // entity.
287: if (keyCount > 1) {
288: SQLUtil.getColumnNamesClause(entity.getPrimaryKeyFields(),
289: columnNamesClause);
290: columnNamesClause.append(SQLUtil.COMMA);
291: }
292:
293: SQLUtil.getColumnNamesClause(loadIter, columnNamesClause);
294:
295: //
296: // table name clause
297: String tableName = entity.getQualifiedTableName();
298:
299: //
300: // where clause
301: String whereClause = SQLUtil.getWhereClause(
302: entity.getPrimaryKeyFields(), new StringBuffer(50))
303: .toString();
304: if (keyCount > 0) {
305: StringBuffer sb = new StringBuffer(
306: (whereClause.length() + 6) * keyCount + 4);
307: for (int i = 0; i < keyCount; i++) {
308: if (i > 0)
309: sb.append(SQLUtil.OR);
310: sb.append('(').append(whereClause).append(')');
311: }
312: whereClause = sb.toString();
313: }
314:
315: String[] args = new String[] { columnNamesClause.toString(),
316: tableName, whereClause, null // order by
317: };
318: return rowLockingTemplate.getFunctionSql(args,
319: new StringBuffer(300)).toString();
320: }
321: }
|