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.bridge;
023:
024: import java.lang.reflect.Field;
025:
026: import java.sql.PreparedStatement;
027: import java.sql.ResultSet;
028: import java.sql.SQLException;
029:
030: import javax.ejb.EJBException;
031:
032: import org.jboss.deployment.DeploymentException;
033: import org.jboss.ejb.EntityEnterpriseContext;
034:
035: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
036:
037: import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
038: import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
039: import org.jboss.ejb.plugins.cmp.jdbc.CMPFieldStateFactory;
040: import org.jboss.ejb.plugins.cmp.jdbc.JDBCTypeFactory;
041: import org.jboss.ejb.plugins.cmp.jdbc.LockingStrategy;
042: import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore;
043: import org.jboss.ejb.plugins.cmp.jdbc.JDBCResultSetReader;
044:
045: import org.jboss.logging.Logger;
046:
047: /**
048: * JDBCAbstractCMPFieldBridge is the default implementation of
049: * JDBCCMPFieldBridge. Most of the heavy lifting of this command is handled
050: * by JDBCUtil. It is left to subclasses to implement the logic for getting
051: * and setting instance values and dirty checking, as this is dependent on
052: * the CMP version used.
053: *
054: * Life-cycle:
055: * Tied to the EntityBridge.
056: *
057: * Multiplicity:
058: * One for each entity bean cmp field.
059: *
060: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
061: * @author <a href="mailto:loubyansky@ua.fm">Alex Loubyansky</a>
062: * @author <a href="mailto:heiko.rupp@cellent.de">Heiko W.Rupp</a>
063: * @version $Revision: 57209 $
064: *
065: * <p><b>Revisions:</b>
066: *
067: * <p><b>20021023 Steve Coy:</b>
068: * <ul>
069: * <li>Changed {@link #loadArgumentResults} so that it passes the jdbc type to
070: * </ul>
071: */
072: public abstract class JDBCAbstractCMPFieldBridge implements
073: JDBCCMPFieldBridge {
074: protected final Logger log;
075: protected final JDBCStoreManager manager;
076: private final JDBCType jdbcType;
077: protected final String fieldName;
078: private final Class fieldType;
079: protected final boolean readOnly;
080: protected final long readTimeOut;
081: protected final boolean primaryKeyMember;
082: private final Class primaryKeyClass;
083: private final Field primaryKeyField;
084: protected final int jdbcContextIndex;
085: protected final int tableIndex;
086: protected CMPFieldStateFactory stateFactory;
087: protected boolean checkDirtyAfterGet;
088:
089: protected byte defaultFlags = 0;
090:
091: private LockingStrategy lockingStrategy = LockingStrategy.NONE;
092:
093: public JDBCAbstractCMPFieldBridge(JDBCStoreManager manager,
094: JDBCCMPFieldMetaData metadata) throws DeploymentException {
095: this (manager, metadata, manager.getJDBCTypeFactory()
096: .getJDBCType(metadata));
097: }
098:
099: public JDBCAbstractCMPFieldBridge(JDBCStoreManager manager,
100: JDBCCMPFieldMetaData metadata, JDBCType jdbcType)
101: throws DeploymentException {
102: this .manager = manager;
103: this .fieldName = metadata.getFieldName();
104: this .fieldType = metadata.getFieldType();
105: this .jdbcType = jdbcType;
106: this .readOnly = metadata.isReadOnly();
107: this .readTimeOut = metadata.getReadTimeOut();
108: this .primaryKeyMember = metadata.isPrimaryKeyMember();
109: this .primaryKeyClass = metadata.getEntity()
110: .getPrimaryKeyClass();
111: this .primaryKeyField = metadata.getPrimaryKeyField();
112:
113: final JDBCEntityBridge entityBridge = (JDBCEntityBridge) manager
114: .getEntityBridge();
115: this .jdbcContextIndex = entityBridge.getNextJDBCContextIndex();
116:
117: if (!metadata.isRelationTableField())
118: tableIndex = entityBridge.addTableField(this );
119: else
120: tableIndex = -1;
121:
122: final JDBCTypeFactory typeFactory = manager
123: .getJDBCTypeFactory();
124: stateFactory = JDBCTypeFactory.getCMPFieldStateFactory(
125: typeFactory, metadata.getStateFactory(), fieldType);
126: checkDirtyAfterGet = JDBCTypeFactory.checkDirtyAfterGet(
127: typeFactory, metadata.getCheckDirtyAfterGet(),
128: fieldType);
129:
130: this .log = createLogger(manager, fieldName);
131: }
132:
133: public JDBCAbstractCMPFieldBridge(JDBCStoreManager manager,
134: String fieldName, Class fieldType, JDBCType jdbcType,
135: boolean readOnly, long readTimeOut, Class primaryKeyClass,
136: Field primaryKeyField, int jdbcContextIndex,
137: int tableIndex, boolean checkDirtyAfterGet,
138: CMPFieldStateFactory stateFactory) {
139: this .manager = manager;
140: this .fieldName = fieldName;
141: this .fieldType = fieldType;
142: this .jdbcType = jdbcType;
143: this .readOnly = readOnly;
144: this .readTimeOut = readTimeOut;
145: this .primaryKeyMember = false;
146: this .primaryKeyClass = primaryKeyClass;
147: this .primaryKeyField = primaryKeyField;
148: this .jdbcContextIndex = jdbcContextIndex;
149: this .tableIndex = tableIndex;
150: this .stateFactory = stateFactory;
151: this .checkDirtyAfterGet = checkDirtyAfterGet;
152: this .log = createLogger(manager, fieldName);
153: }
154:
155: public byte getDefaultFlags() {
156: return defaultFlags;
157: }
158:
159: /** get rid of it later */
160: public void addDefaultFlag(byte flag) {
161: defaultFlags |= flag;
162: }
163:
164: public JDBCEntityPersistenceStore getManager() {
165: return manager;
166: }
167:
168: public String getFieldName() {
169: return fieldName;
170: }
171:
172: public JDBCType getJDBCType() {
173: return jdbcType;
174: }
175:
176: public Class getFieldType() {
177: return fieldType;
178: }
179:
180: public boolean isPrimaryKeyMember() {
181: return primaryKeyMember;
182: }
183:
184: public Field getPrimaryKeyField() {
185: return primaryKeyField;
186: }
187:
188: public boolean isReadOnly() {
189: return readOnly;
190: }
191:
192: public long getReadTimeOut() {
193: return readTimeOut;
194: }
195:
196: public Object getValue(EntityEnterpriseContext ctx) {
197: Object value = getInstanceValue(ctx);
198: if (ctx.isValid()) {
199: lockingStrategy.accessed(this , ctx);
200: if (checkDirtyAfterGet) {
201: setDirtyAfterGet(ctx);
202: }
203: }
204: return value;
205: }
206:
207: public void setValue(EntityEnterpriseContext ctx, Object value) {
208: if (isReadOnly()) {
209: throw new EJBException("Field is read-only: fieldName="
210: + fieldName);
211: }
212: if (primaryKeyMember && JDBCEntityBridge.isEjbCreateDone(ctx)) {
213: throw new IllegalStateException(
214: "A CMP field that is a member "
215: + "of the primary key can only be set in ejbCreate "
216: + "[EJB 2.0 Spec. 10.3.5].");
217: }
218:
219: if (ctx.isValid()) {
220: if (!isLoaded(ctx)) {
221: // the field must be loaded for dirty cheking to work properly
222: manager.loadField(this , ctx);
223: }
224: lockingStrategy.changed(this , ctx);
225: }
226: setInstanceValue(ctx, value);
227: }
228:
229: public Object getPrimaryKeyValue(Object primaryKey)
230: throws IllegalArgumentException {
231: try {
232: if (primaryKeyField != null) {
233: if (primaryKey == null) {
234: return null;
235: }
236:
237: // Extract this field's value from the primary key.
238: return primaryKeyField.get(primaryKey);
239: } else {
240: // This field is the primary key, so no extraction is necessary.
241: return primaryKey;
242: }
243: } catch (Exception e) {
244: // Non recoverable internal exception
245: throw new EJBException(
246: "Internal error getting primary key "
247: + "field member " + getFieldName(), e);
248: }
249: }
250:
251: public Object setPrimaryKeyValue(Object primaryKey, Object value)
252: throws IllegalArgumentException {
253: try {
254: if (primaryKeyField != null) {
255: // if we are tring to set a null value
256: // into a null pk, we are already done.
257: if (value == null && primaryKey == null) {
258: return null;
259: }
260:
261: // if we don't have a pk object yet create one
262: if (primaryKey == null) {
263: primaryKey = primaryKeyClass.newInstance();
264: }
265:
266: // Set this field's value into the primary key object.
267: primaryKeyField.set(primaryKey, value);
268: return primaryKey;
269: } else {
270: // This field is the primary key, so no extraction is necessary.
271: return value;
272: }
273: } catch (Exception e) {
274: // Non recoverable internal exception
275: throw new EJBException(
276: "Internal error setting instance field "
277: + getFieldName(), e);
278: }
279: }
280:
281: public abstract void resetPersistenceContext(
282: EntityEnterpriseContext ctx);
283:
284: /**
285: * Set CMPFieldValue to Java default value (i.e., 0 or null).
286: */
287: public void initInstance(EntityEnterpriseContext ctx) {
288: if (!readOnly) {
289: Object value;
290: if (fieldType == boolean.class)
291: value = Boolean.FALSE;
292: else if (fieldType == byte.class)
293: value = new Byte((byte) 0);
294: else if (fieldType == int.class)
295: value = new Integer(0);
296: else if (fieldType == long.class)
297: value = new Long(0L);
298: else if (fieldType == short.class)
299: value = new Short((short) 0);
300: else if (fieldType == char.class)
301: value = new Character('\u0000');
302: else if (fieldType == double.class)
303: value = new Double(0d);
304: else if (fieldType == float.class)
305: value = new Float(0f);
306: else
307: value = null;
308: setInstanceValue(ctx, value);
309: }
310: }
311:
312: public int setInstanceParameters(PreparedStatement ps,
313: int parameterIndex, EntityEnterpriseContext ctx) {
314: Object instanceValue = getInstanceValue(ctx);
315: return setArgumentParameters(ps, parameterIndex, instanceValue);
316: }
317:
318: public int setPrimaryKeyParameters(PreparedStatement ps,
319: int parameterIndex, Object primaryKey)
320: throws IllegalArgumentException {
321: Object primaryKeyValue = getPrimaryKeyValue(primaryKey);
322: return setArgumentParameters(ps, parameterIndex,
323: primaryKeyValue);
324: }
325:
326: public int setArgumentParameters(PreparedStatement ps,
327: int parameterIndex, Object arg) {
328: try {
329: int[] jdbcTypes = jdbcType.getJDBCTypes();
330: for (int i = 0; i < jdbcTypes.length; i++) {
331: Object columnValue = jdbcType.getColumnValue(i, arg);
332: jdbcType.getParameterSetter()[i].set(ps,
333: parameterIndex++, jdbcTypes[i], columnValue,
334: log);
335: //JDBCUtil.setParameter(log, ps, parameterIndex++, jdbcTypes[i], columnValue);
336: }
337: return parameterIndex;
338: } catch (SQLException e) {
339: // Non recoverable internal exception
340: throw new EJBException(
341: "Internal error setting parameters for field "
342: + getFieldName(), e);
343: }
344: }
345:
346: public int loadInstanceResults(ResultSet rs, int parameterIndex,
347: EntityEnterpriseContext ctx) {
348: try {
349: // value of this field, will be filled in below
350: Object[] argumentRef = new Object[1];
351:
352: // load the cmp field value from the result set
353: parameterIndex = loadArgumentResults(rs, parameterIndex,
354: argumentRef);
355:
356: // set the value into the context
357: setInstanceValue(ctx, argumentRef[0]);
358:
359: lockingStrategy.loaded(this , ctx);
360:
361: return parameterIndex;
362: } catch (EJBException e) {
363: // to avoid double wrap of EJBExceptions
364: throw e;
365: } catch (Exception e) {
366: // Non recoverable internal exception
367: throw new EJBException(
368: "Internal error getting results for field "
369: + getFieldName(), e);
370: }
371: }
372:
373: public int loadPrimaryKeyResults(ResultSet rs, int parameterIndex,
374: Object[] pkRef) throws IllegalArgumentException {
375: // value of this field, will be filled in below
376: Object[] argumentRef = new Object[1];
377:
378: parameterIndex = loadArgumentResults(rs, parameterIndex,
379: argumentRef, true);
380:
381: // set the value of this field into the pk
382: pkRef[0] = argumentRef[0] == null ? null : setPrimaryKeyValue(
383: pkRef[0], argumentRef[0]);
384:
385: // retrun the updated parameterIndex
386: return parameterIndex;
387: }
388:
389: public int loadArgumentResults(ResultSet rs, int parameterIndex,
390: Object[] argumentRef) throws IllegalArgumentException {
391: return loadArgumentResults(rs, parameterIndex, argumentRef,
392: false);
393: }
394:
395: public boolean isRelationTableField() {
396: return tableIndex < 0;
397: }
398:
399: public final int getFieldIndex() {
400: return jdbcContextIndex;
401: }
402:
403: public Class getPrimaryKeyClass() {
404: return primaryKeyClass;
405: }
406:
407: public int getTableIndex() {
408: return tableIndex;
409: }
410:
411: public void setLockingStrategy(LockingStrategy lockingStrategy) {
412: this .lockingStrategy = lockingStrategy;
413: }
414:
415: protected abstract void setDirtyAfterGet(EntityEnterpriseContext ctx);
416:
417: public boolean isCMPField() {
418: return true;
419: }
420:
421: private int loadArgumentResults(ResultSet rs, int parameterIndex,
422: Object[] argumentRef, boolean nullColumnNullifiesResult)
423: throws IllegalArgumentException {
424: try {
425: // value of this field, will be filled in below
426: // set the value of this field into the pk
427: argumentRef[0] = null;
428:
429: // update the value from the result set
430: Class[] javaTypes = jdbcType.getJavaTypes();
431: JDBCResultSetReader[] rsReaders = jdbcType
432: .getResultSetReaders();
433: for (int i = 0; i < javaTypes.length; i++) {
434: Object columnValue = rsReaders[i].get(rs,
435: parameterIndex++, javaTypes[i], log);
436: if (nullColumnNullifiesResult && columnValue == null) {
437: argumentRef[0] = null;
438: parameterIndex += javaTypes.length - i - 1;
439: break;
440: }
441: argumentRef[0] = jdbcType.setColumnValue(i,
442: argumentRef[0], columnValue);
443: }
444:
445: // retrun the updated parameterIndex
446: return parameterIndex;
447: } catch (SQLException e) {
448: // Non recoverable internal exception
449: throw new EJBException("Internal error getting results "
450: + "for field member " + getFieldName(), e);
451: }
452: }
453:
454: private Logger createLogger(JDBCStoreManager manager,
455: String fieldName) {
456: return Logger.getLogger(this .getClass().getName() + "."
457: + manager.getMetaData().getName() + "#" + fieldName);
458: }
459: }
|