/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb.plugins.cmp.jdbc.bridge; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.ejb.EJBException; import org.jboss.deployment.DeploymentException; import org.jboss.ejb.EntityEnterpriseContext; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData; import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager; import org.jboss.ejb.plugins.cmp.jdbc.JDBCType; import org.jboss.ejb.plugins.cmp.jdbc.CMPFieldStateFactory; import org.jboss.ejb.plugins.cmp.jdbc.JDBCTypeFactory; import org.jboss.ejb.plugins.cmp.jdbc.LockingStrategy; import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore; import org.jboss.ejb.plugins.cmp.jdbc.JDBCResultSetReader; import org.jboss.logging.Logger; /** * JDBCAbstractCMPFieldBridge is the default implementation of * JDBCCMPFieldBridge. Most of the heavy lifting of this command is handled * by JDBCUtil. It is left to subclasses to implement the logic for getting * and setting instance values and dirty checking, as this is dependent on * the CMP version used. * * Life-cycle: * Tied to the EntityBridge. * * Multiplicity: * One for each entity bean cmp field. * * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a> * @author <a href="mailto:loubyansky@ua.fm">Alex Loubyansky</a> * @author <a href="mailto:heiko.rupp@cellent.de">Heiko W.Rupp</a> * @version $Revision: 81030 $ * * <p><b>Revisions:</b> * * <p><b>20021023 Steve Coy:</b> * <ul> * <li>Changed {@link #loadArgumentResults} so that it passes the jdbc type to * </ul> */ public abstract class JDBCAbstractCMPFieldBridge implements JDBCCMPFieldBridge { protected final Logger log; protected final JDBCStoreManager manager; private final JDBCType jdbcType; protected final String fieldName; private final Class fieldType; protected final boolean readOnly; protected final long readTimeOut; protected final boolean primaryKeyMember; private final Class primaryKeyClass; private final Field primaryKeyField; protected final int jdbcContextIndex; protected final int tableIndex; protected CMPFieldStateFactory stateFactory; protected boolean checkDirtyAfterGet; protected byte defaultFlags = 0; private LockingStrategy lockingStrategy = LockingStrategy.NONE; public JDBCAbstractCMPFieldBridge(JDBCStoreManager manager, JDBCCMPFieldMetaData metadata) throws DeploymentException { this(manager, metadata, manager.getJDBCTypeFactory().getJDBCType(metadata)); } public JDBCAbstractCMPFieldBridge(JDBCStoreManager manager, JDBCCMPFieldMetaData metadata, JDBCType jdbcType) throws DeploymentException { this.manager = manager; this.fieldName = metadata.getFieldName(); this.fieldType = metadata.getFieldType(); this.jdbcType = jdbcType; this.readOnly = metadata.isReadOnly(); this.readTimeOut = metadata.getReadTimeOut(); this.primaryKeyMember = metadata.isPrimaryKeyMember(); this.primaryKeyClass = metadata.getEntity().getPrimaryKeyClass(); this.primaryKeyField = metadata.getPrimaryKeyField(); final JDBCEntityBridge entityBridge = (JDBCEntityBridge)manager.getEntityBridge(); this.jdbcContextIndex = entityBridge.getNextJDBCContextIndex(); if(!metadata.isRelationTableField()) tableIndex = entityBridge.addTableField(this); else tableIndex = -1; final JDBCTypeFactory typeFactory = manager.getJDBCTypeFactory(); stateFactory = JDBCTypeFactory.getCMPFieldStateFactory( typeFactory, metadata.getStateFactory(), fieldType ); checkDirtyAfterGet = JDBCTypeFactory.checkDirtyAfterGet( typeFactory, metadata.getCheckDirtyAfterGet(), fieldType ); this.log = createLogger(manager, fieldName); } public JDBCAbstractCMPFieldBridge(JDBCStoreManager manager, String fieldName, Class fieldType, JDBCType jdbcType, boolean readOnly, long readTimeOut, Class primaryKeyClass, Field primaryKeyField, int jdbcContextIndex, int tableIndex, boolean checkDirtyAfterGet, CMPFieldStateFactory stateFactory) { this.manager = manager; this.fieldName = fieldName; this.fieldType = fieldType; this.jdbcType = jdbcType; this.readOnly = readOnly; this.readTimeOut = readTimeOut; this.primaryKeyMember = false; this.primaryKeyClass = primaryKeyClass; this.primaryKeyField = primaryKeyField; this.jdbcContextIndex = jdbcContextIndex; this.tableIndex = tableIndex; this.stateFactory = stateFactory; this.checkDirtyAfterGet = checkDirtyAfterGet; this.log = createLogger(manager, fieldName); } public byte getDefaultFlags() { return defaultFlags; } /** get rid of it later */ public void addDefaultFlag(byte flag) { defaultFlags |= flag; } public JDBCEntityPersistenceStore getManager() { return manager; } public String getFieldName() { return fieldName; } public JDBCType getJDBCType() { return jdbcType; } public Class getFieldType() { return fieldType; } public boolean isPrimaryKeyMember() { return primaryKeyMember; } public Field getPrimaryKeyField() { return primaryKeyField; } public boolean isReadOnly() { return readOnly; } public long getReadTimeOut() { return readTimeOut; } public Object getValue(EntityEnterpriseContext ctx) { Object value = getInstanceValue(ctx); if(ctx.isValid()) { lockingStrategy.accessed(this, ctx); if(checkDirtyAfterGet) { setDirtyAfterGet(ctx); } } return value; } public void setValue(EntityEnterpriseContext ctx, Object value) { if(isReadOnly()) { throw new EJBException("Field is read-only: fieldName=" + fieldName); } if(primaryKeyMember && JDBCEntityBridge.isEjbCreateDone(ctx)) { throw new IllegalStateException("A CMP field that is a member " + "of the primary key can only be set in ejbCreate " + "[EJB 2.0 Spec. 10.3.5]."); } if(ctx.isValid()) { if(!isLoaded(ctx)) { // the field must be loaded for dirty cheking to work properly manager.loadField(this, ctx); } lockingStrategy.changed(this, ctx); } setInstanceValue(ctx, value); } public Object getPrimaryKeyValue(Object primaryKey) throws IllegalArgumentException { try { if(primaryKeyField != null) { if(primaryKey == null) { return null; } // Extract this field's value from the primary key. return primaryKeyField.get(primaryKey); } else { // This field is the primary key, so no extraction is necessary. return primaryKey; } } catch(Exception e) { // Non recoverable internal exception throw new EJBException("Internal error getting primary key " + "field member " + getFieldName(), e); } } public Object setPrimaryKeyValue(Object primaryKey, Object value) throws IllegalArgumentException { try { if(primaryKeyField != null) { // if we are tring to set a null value // into a null pk, we are already done. if(value == null && primaryKey == null) { return null; } // if we don't have a pk object yet create one if(primaryKey == null) { primaryKey = primaryKeyClass.newInstance(); } // Set this field's value into the primary key object. primaryKeyField.set(primaryKey, value); return primaryKey; } else { // This field is the primary key, so no extraction is necessary. return value; } } catch(Exception e) { // Non recoverable internal exception throw new EJBException("Internal error setting instance field " + getFieldName(), e); } } public abstract void resetPersistenceContext(EntityEnterpriseContext ctx); /** * Set CMPFieldValue to Java default value (i.e., 0 or null). */ public void initInstance(EntityEnterpriseContext ctx) { if(!readOnly) { Object value; if(fieldType == boolean.class) value = Boolean.FALSE; else if(fieldType == byte.class) value = new Byte((byte)0); else if(fieldType == int.class) value = new Integer(0); else if(fieldType == long.class) value = new Long(0L); else if(fieldType == short.class) value = new Short((short)0); else if(fieldType == char.class) value = new Character('\u0000'); else if(fieldType == double.class) value = new Double(0d); else if(fieldType == float.class) value = new Float(0f); else value = null; setInstanceValue(ctx, value); } } public int setInstanceParameters(PreparedStatement ps, int parameterIndex, EntityEnterpriseContext ctx) { Object instanceValue = getInstanceValue(ctx); return setArgumentParameters(ps, parameterIndex, instanceValue); } public int setPrimaryKeyParameters(PreparedStatement ps, int parameterIndex, Object primaryKey) throws IllegalArgumentException { Object primaryKeyValue = getPrimaryKeyValue(primaryKey); return setArgumentParameters(ps, parameterIndex, primaryKeyValue); } public int setArgumentParameters(PreparedStatement ps, int parameterIndex, Object arg) { try { int[] jdbcTypes = jdbcType.getJDBCTypes(); for(int i = 0; i < jdbcTypes.length; i++) { Object columnValue = jdbcType.getColumnValue(i, arg); jdbcType.getParameterSetter()[i].set(ps, parameterIndex++, jdbcTypes[i], columnValue, log); //JDBCUtil.setParameter(log, ps, parameterIndex++, jdbcTypes[i], columnValue); } return parameterIndex; } catch(SQLException e) { // Non recoverable internal exception throw new EJBException("Internal error setting parameters for field " + getFieldName(), e); } } public int loadInstanceResults(ResultSet rs, int parameterIndex, EntityEnterpriseContext ctx) { try { // value of this field, will be filled in below Object[] argumentRef = new Object[1]; // load the cmp field value from the result set parameterIndex = loadArgumentResults(rs, parameterIndex, argumentRef); // set the value into the context setInstanceValue(ctx, argumentRef[0]); lockingStrategy.loaded(this, ctx); return parameterIndex; } catch(EJBException e) { // to avoid double wrap of EJBExceptions throw e; } catch(Exception e) { // Non recoverable internal exception throw new EJBException("Internal error getting results for field " + getFieldName(), e); } } public int loadPrimaryKeyResults(ResultSet rs, int parameterIndex, Object[] pkRef) throws IllegalArgumentException { // value of this field, will be filled in below Object[] argumentRef = new Object[1]; parameterIndex = loadArgumentResults(rs, parameterIndex, argumentRef, true); // set the value of this field into the pk pkRef[0] = argumentRef[0] == null ? null : setPrimaryKeyValue(pkRef[0], argumentRef[0]); // retrun the updated parameterIndex return parameterIndex; } public int loadArgumentResults(ResultSet rs, int parameterIndex, Object[] argumentRef) throws IllegalArgumentException { return loadArgumentResults(rs, parameterIndex, argumentRef, false); } public boolean isRelationTableField() { return tableIndex < 0; } public final int getFieldIndex() { return jdbcContextIndex; } public Class getPrimaryKeyClass() { return primaryKeyClass; } public int getTableIndex() { return tableIndex; } public void setLockingStrategy(LockingStrategy lockingStrategy) { this.lockingStrategy = lockingStrategy; } protected abstract void setDirtyAfterGet(EntityEnterpriseContext ctx); public boolean isCMPField() { return true; } private int loadArgumentResults(ResultSet rs, int parameterIndex, Object[] argumentRef, boolean nullColumnNullifiesResult) throws IllegalArgumentException { try { // value of this field, will be filled in below // set the value of this field into the pk argumentRef[0] = null; // update the value from the result set Class[] javaTypes = jdbcType.getJavaTypes(); JDBCResultSetReader[] rsReaders = jdbcType.getResultSetReaders(); for(int i = 0; i < javaTypes.length; i++) { Object columnValue = rsReaders[i].get(rs, parameterIndex++, javaTypes[i], log); if(nullColumnNullifiesResult && columnValue == null) { argumentRef[0] = null; parameterIndex += javaTypes.length - i - 1; break; } argumentRef[0] = jdbcType.setColumnValue(i, argumentRef[0], columnValue); } // retrun the updated parameterIndex return parameterIndex; } catch(SQLException e) { // Non recoverable internal exception throw new EJBException("Internal error getting results " + "for field member " + getFieldName(), e); } } private Logger createLogger(JDBCStoreManager manager, String fieldName) { return Logger.getLogger( this.getClass().getName() + "." + manager.getMetaData().getName() + "#" + fieldName); } }