/*
* 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);
}
}