/*
* 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.as.cmp.jdbc.bridge;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.ejb.EJBException;
import javax.ejb.RemoveException;
import javax.sql.DataSource;
import org.jboss.as.cmp.bridge.FieldBridge;
import org.jboss.as.cmp.context.CmpEntityBeanContext;
import org.jboss.as.cmp.jdbc.JDBCContext;
import org.jboss.as.cmp.jdbc.JDBCEntityPersistenceStore;
import org.jboss.as.cmp.jdbc.JDBCStoreManager;
import org.jboss.as.cmp.jdbc.JDBCTypeFactory;
import org.jboss.as.cmp.jdbc.LockingStrategy;
import org.jboss.as.cmp.jdbc.SQLUtil;
import org.jboss.as.cmp.jdbc.metadata.JDBCAuditMetaData;
import org.jboss.as.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
import org.jboss.as.cmp.jdbc.metadata.JDBCEntityMetaData;
import org.jboss.as.cmp.jdbc.metadata.JDBCOptimisticLockingMetaData;
import org.jboss.as.cmp.jdbc.metadata.JDBCQueryMetaData;
import org.jboss.as.cmp.jdbc.metadata.JDBCReadAheadMetaData;
import org.jboss.as.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
import org.jboss.logging.Logger;
/**
* JDBCEntityBridge follows the Bridge pattern [Gamma et. al, 1995].
* The main job of this class is to construct the bridge from entity meta data.
* <p/>
* Life-cycle:
* Undefined. Should be tied to CMPStoreManager.
* <p/>
* Multiplicity:
* One per cmp entity bean type.
*
* @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 $
*/
public class JDBCEntityBridge implements JDBCAbstractEntityBridge {
public static final byte LOADED = 1;
public static final byte LOAD_REQUIRED = 2;
public static final byte DIRTY = 4;
public static final byte CHECK_DIRTY = 8;
public static final byte LOCKED = 16;
public static final byte ADD_TO_SET_ON_UPDATE = 32;
public static final byte ADD_TO_WHERE_ON_UPDATE = 64;
private static final String DEFAULT_LOADGROUP_NAME = "*";
private JDBCEntityMetaData metadata;
private JDBCStoreManager manager;
private DataSource dataSource;
private String qualifiedTableName;
private String tableName;
/**
* primary key fields (not added to cmpFields)
*/
private final String primaryKeyFieldName;
private final Class<?> primaryKeyClass;
private JDBCCMPFieldBridge[] primaryKeyFields;
/**
* CMP fields
*/
private JDBCCMPFieldBridge[] cmpFields;
/**
* CMR fields
*/
private JDBCCMRFieldBridge[] cmrFields;
/**
* table fields
*/
private JDBCCMPFieldBridge[] tableFields;
/**
* used for optimistic locking. (added to cmpFields)
*/
private JDBCCMPFieldBridge versionField;
// Audit fields (added to cmpFields)
private JDBCCMPFieldBridge createdPrincipalField;
private JDBCCMPFieldBridge createdTimeField;
private JDBCCMPFieldBridge updatedPrincipalField;
private JDBCCMPFieldBridge updatedTimeField;
private Map<Method, JDBCSelectorBridge> selectorsByMethod;
/**
* Load group is a boolean array with tableFields.length elements. True means the element is in the group.
*/
private Map loadGroupMasks;
private List lazyLoadGroupMasks;
private boolean[] eagerLoadGroupMask;
private boolean[] defaultLockGroupMask;
private int jdbcContextSize;
private final Logger log;
public JDBCEntityBridge(JDBCEntityMetaData metadata, JDBCStoreManager manager) throws Exception {
this.metadata = metadata;
this.manager = manager;
primaryKeyFieldName = metadata.getPrimaryKeyFieldName();
primaryKeyClass = metadata.getPrimaryKeyClass();
log = Logger.getLogger(this.getClass().getName() + "." + metadata.getName());
}
public void init() throws Exception {
dataSource = manager.getDataSource(metadata.getDataSourceName());
qualifiedTableName = SQLUtil.fixTableName(metadata.getDefaultTableName(), dataSource);
int dotIndex = qualifiedTableName.indexOf('.');
tableName = dotIndex == -1 ? qualifiedTableName : qualifiedTableName.substring(dotIndex + 1);
// CMP fields
loadCMPFields(metadata);
// CMR fields
loadCMRFields(metadata);
// create locking field
JDBCOptimisticLockingMetaData lockMetaData = metadata.getOptimisticLocking();
if (lockMetaData != null && lockMetaData.getLockingField() != null) {
JDBCOptimisticLockingMetaData.LockingStrategy strategy = lockMetaData.getLockingStrategy();
JDBCCMPFieldMetaData versionMD = lockMetaData.getLockingField();
versionField = getCMPFieldByName(versionMD.getFieldName());
boolean hidden = versionField == null;
switch (strategy) {
case VERSION_COLUMN_STRATEGY: {
if (hidden)
versionField = new JDBCLongVersionFieldBridge(manager, versionMD);
else
versionField = new JDBCLongVersionFieldBridge((JDBCCMP2xFieldBridge) versionField);
break;
}
case TIMESTAMP_COLUMN_STRATEGY: {
if (hidden)
versionField = new JDBCTimestampVersionFieldBridge(manager, versionMD);
else
versionField = new JDBCTimestampVersionFieldBridge((JDBCCMP2xFieldBridge) versionField);
break;
}
case KEYGENERATOR_COLUMN_STRATEGY: {
if (hidden)
versionField = new JDBCKeyGenVersionFieldBridge(manager, versionMD, lockMetaData.getKeyGeneratorFactory());
else
versionField = new JDBCKeyGenVersionFieldBridge((JDBCCMP2xFieldBridge) versionField, lockMetaData.getKeyGeneratorFactory());
break;
}
}
if (hidden)
addCMPField(versionField);
else
tableFields[versionField.getTableIndex()] = versionField;
}
// audit fields
JDBCAuditMetaData auditMetaData = metadata.getAudit();
if (auditMetaData != null) {
JDBCCMPFieldMetaData auditField = auditMetaData.getCreatedPrincipalField();
if (auditField != null) {
createdPrincipalField = getCMPFieldByName(auditField.getFieldName());
if (createdPrincipalField == null) {
createdPrincipalField = new JDBCCMP2xFieldBridge(manager, auditField);
addCMPField(createdPrincipalField);
}
} else {
createdPrincipalField = null;
}
auditField = auditMetaData.getCreatedTimeField();
if (auditField != null) {
createdTimeField = getCMPFieldByName(auditField.getFieldName());
if (createdTimeField == null) {
createdTimeField = new JDBCCMP2xFieldBridge(manager, auditField, JDBCTypeFactory.EQUALS, false);
addCMPField(createdTimeField);
} else {
// just to override state factory and check-dirty-after-get
createdTimeField = new JDBCCMP2xFieldBridge(
(JDBCCMP2xFieldBridge) createdTimeField, JDBCTypeFactory.EQUALS, false);
tableFields[createdTimeField.getTableIndex()] = createdTimeField;
}
} else {
createdTimeField = null;
}
auditField = auditMetaData.getUpdatedPrincipalField();
if (auditField != null) {
updatedPrincipalField = getCMPFieldByName(auditField.getFieldName());
if (updatedPrincipalField == null) {
updatedPrincipalField = new JDBCCMP2xUpdatedPrincipalFieldBridge(manager, auditField);
addCMPField(updatedPrincipalField);
} else {
updatedPrincipalField = new JDBCCMP2xUpdatedPrincipalFieldBridge(
(JDBCCMP2xFieldBridge) updatedPrincipalField);
tableFields[updatedPrincipalField.getTableIndex()] = updatedPrincipalField;
}
} else {
updatedPrincipalField = null;
}
auditField = auditMetaData.getUpdatedTimeField();
if (auditField != null) {
updatedTimeField = getCMPFieldByName(auditField.getFieldName());
if (updatedTimeField == null) {
updatedTimeField = new JDBCCMP2xUpdatedTimeFieldBridge(manager, auditField);
addCMPField(updatedTimeField);
} else {
updatedTimeField = new JDBCCMP2xUpdatedTimeFieldBridge((JDBCCMP2xFieldBridge) updatedTimeField);
tableFields[updatedTimeField.getTableIndex()] = updatedTimeField;
}
} else {
updatedTimeField = null;
}
}
// ejbSelect methods
loadSelectors(metadata);
}
public void resolveRelationships() {
for (int i = 0; i < cmrFields.length; ++i)
cmrFields[i].resolveRelationship();
// load groups: cannot be created until relationships have
// been resolved because loadgroups must check for foreign keys
loadLoadGroups(metadata);
loadEagerLoadGroup(metadata);
loadLazyLoadGroups(metadata);
}
/**
* The third phase of deployment. The method is called when relationships are already resolved.
*/
public void start() {
for (int i = 0; i < cmrFields.length; ++i) {
cmrFields[i].start();
}
}
public boolean removeFromRelations(CmpEntityBeanContext ctx, Object[] oldRelations) {
boolean removed = false;
for (int i = 0; i < cmrFields.length; ++i) {
if (cmrFields[i].removeFromRelations(ctx, oldRelations))
removed = true;
}
return removed;
}
public void cascadeDelete(CmpEntityBeanContext ctx, Map oldRelations) throws RemoveException, RemoteException {
for (int i = 0; i < cmrFields.length; ++i) {
JDBCCMRFieldBridge cmrField = cmrFields[i];
Object value = oldRelations.get(cmrField);
if (value != null)
cmrField.cascadeDelete(ctx, (List) value);
}
}
public String getEntityName() {
return metadata.getName();
}
public String getAbstractSchemaName() {
return metadata.getAbstractSchemaName();
}
public Class getRemoteInterface() {
return metadata.getRemoteClass();
}
public Class getLocalInterface() {
return metadata.getLocalClass();
}
public JDBCEntityMetaData getMetaData() {
return metadata;
}
public JDBCEntityPersistenceStore getManager() {
return manager;
}
/**
* Returns the datasource for this entity.
*/
public DataSource getDataSource() {
return dataSource;
}
public String getTableName() {
return tableName;
}
public String getQualifiedTableName() {
return qualifiedTableName;
}
public Class getPrimaryKeyClass() {
return primaryKeyClass;
}
public int getListCacheMax() {
return metadata.getListCacheMax();
}
public int getFetchSize() {
return metadata.getFetchSize();
}
public Object createPrimaryKeyInstance() {
if (primaryKeyFieldName == null) {
try {
return primaryKeyClass.newInstance();
} catch (Exception e) {
throw new EJBException("Error creating primary key instance: ", e);
}
}
return null;
}
public JDBCFieldBridge[] getPrimaryKeyFields() {
return primaryKeyFields;
}
/**
* This method is called only at deployment time, not called at runtime.
*
* @return the list of all the fields.
*/
public List<FieldBridge> getFields() {
int fieldsTotal = primaryKeyFields.length + cmpFields.length + cmrFields.length;
FieldBridge[] fields = new FieldBridge[fieldsTotal];
int position = 0;
// primary key fields
System.arraycopy(primaryKeyFields, 0, fields, position, primaryKeyFields.length);
position += primaryKeyFields.length;
// cmp fields
System.arraycopy(cmpFields, 0, fields, position, cmpFields.length);
position += cmpFields.length;
// cmr fields
System.arraycopy(cmrFields, 0, fields, position, cmrFields.length);
return Arrays.asList(fields);
}
public FieldBridge getFieldByName(String name) {
FieldBridge field = null;
for (int i = 0; i < primaryKeyFields.length; ++i) {
JDBCCMPFieldBridge primaryKeyField = primaryKeyFields[i];
if (primaryKeyField.getFieldName().equals(name)) {
field = primaryKeyField;
break;
}
}
if (field == null) {
field = getCMPFieldByName(name);
}
if (field == null) {
field = getCMRFieldByName(name);
}
return field;
}
public boolean[] getEagerLoadMask() {
return eagerLoadGroupMask;
}
public Iterator getLazyLoadGroupMasks() {
return lazyLoadGroupMasks.iterator();
}
public boolean[] getLoadGroupMask(String name) {
boolean[] mask = (boolean[]) loadGroupMasks.get(name);
if (mask == null) {
throw new IllegalStateException("Load group '" + name + "' is not defined. Defined load groups: " + loadGroupMasks.keySet());
}
return mask;
}
public FieldIterator getLoadIterator(JDBCCMPFieldBridge requiredField,
JDBCReadAheadMetaData readahead,
CmpEntityBeanContext ctx) {
boolean[] loadGroup;
if (requiredField == null) {
if (readahead != null && !readahead.isNone()) {
if (log.isTraceEnabled()) {
log.trace("Eager-load for entity: readahead=" + readahead);
}
loadGroup = getLoadGroupMask(readahead.getEagerLoadGroup());
} else {
if (log.isTraceEnabled()) {
log.trace("Default eager-load for entity: readahead=" + readahead);
}
loadGroup = eagerLoadGroupMask;
}
} else {
loadGroup = new boolean[tableFields.length];
int requiredInd = requiredField.getTableIndex();
loadGroup[requiredInd] = true;
for (Iterator groups = lazyLoadGroupMasks.iterator(); groups.hasNext(); ) {
boolean[] lazyGroup = (boolean[]) groups.next();
if (lazyGroup[requiredInd]) {
for (int i = 0; i < loadGroup.length; ++i)
loadGroup[i] = loadGroup[i] || lazyGroup[i];
}
}
}
FieldIterator loadIter;
if (loadGroup != null) {
// filter
int fieldsToLoad = 0;
EntityState entityState = getEntityState(ctx);
for (int i = 0; i < tableFields.length; ++i) {
JDBCCMPFieldBridge field = tableFields[i];
if (loadGroup[i] && !field.isPrimaryKeyMember() && !field.isLoaded(ctx)) {
entityState.setLoadRequired(i);
++fieldsToLoad;
}
}
loadIter = (fieldsToLoad > 0 ? entityState.getLoadIterator(ctx) : EMPTY_FIELD_ITERATOR);
} else {
loadIter = EMPTY_FIELD_ITERATOR;
}
return loadIter;
}
/**
* @param name CMP field name
* @return JDBCCMPFieldBridge instance or null if no field found.
*/
public JDBCCMPFieldBridge getCMPFieldByName(String name) {
for (int i = 0; i < primaryKeyFields.length; ++i) {
JDBCCMPFieldBridge cmpField = primaryKeyFields[i];
if (cmpField.getFieldName().equals(name))
return cmpField;
}
for (int i = 0; i < cmpFields.length; ++i) {
JDBCCMPFieldBridge cmpField = cmpFields[i];
if (cmpField.getFieldName().equals(name))
return cmpField;
}
return null;
}
public JDBCAbstractCMRFieldBridge[] getCMRFields() {
return cmrFields;
}
public JDBCCMRFieldBridge getCMRFieldByName(String name) {
for (int i = 0; i < cmrFields.length; ++i) {
JDBCCMRFieldBridge cmrField = cmrFields[i];
if (cmrField.getFieldName().equals(name))
return cmrField;
}
return null;
}
public JDBCCMPFieldBridge getVersionField() {
return versionField;
}
public JDBCCMPFieldBridge getCreatedPrincipalField() {
return createdPrincipalField;
}
public JDBCCMPFieldBridge getCreatedTimeField() {
return createdTimeField;
}
public JDBCCMPFieldBridge getUpdatedPrincipalField() {
return updatedPrincipalField;
}
public JDBCCMPFieldBridge getUpdatedTimeField() {
return updatedTimeField;
}
public Collection<JDBCSelectorBridge> getSelectors() {
return selectorsByMethod.values();
}
public void initInstance(CmpEntityBeanContext ctx) {
for (int i = 0; i < tableFields.length; ++i)
tableFields[i].initInstance(ctx);
//for(int i = 0; i < primaryKeyFields.length; ++i)
// primaryKeyFields[i].initInstance(ctx);
//for(int i = 0; i < cmpFields.length; ++i)
// cmpFields[i].initInstance(ctx);
for (int i = 0; i < cmrFields.length; ++i) {
JDBCCMRFieldBridge cmrField = cmrFields[i];
cmrField.initInstance(ctx);
}
}
public static boolean isEjbCreateDone(CmpEntityBeanContext ctx) {
return getEntityState(ctx).ejbCreateDone;
}
public static void setCreated(CmpEntityBeanContext ctx) {
getEntityState(ctx).setCreated();
}
public static void setEjbCreateDone(CmpEntityBeanContext ctx) {
getEntityState(ctx).ejbCreateDone = true;
}
/**
* This method is used to determined whether the instance was modified.
* NOTE, even if the method returns true the isStoreRequired for this same instance
* might return false, e.g. a CMR field that doesn't have a foreign key was modified.
*
* @param ctx
* @return
*/
public boolean isModified(CmpEntityBeanContext ctx) {
boolean invalidateCache = false;
final EntityState entityState = getEntityState(ctx);
if (entityState.isCreated()) {
invalidateCache = areCmpFieldsDirty(ctx, entityState);
if (!invalidateCache) {
for (int i = 0; i < cmrFields.length; ++i) {
if (cmrFields[i].invalidateCache(ctx)) {
invalidateCache = true;
break;
}
}
}
}
return invalidateCache;
}
public boolean isStoreRequired(CmpEntityBeanContext ctx) {
boolean modified = false;
final EntityState entityState = getEntityState(ctx);
if (entityState.isCreated()) {
modified = areCmpFieldsDirty(ctx, entityState);
if (!modified) {
for (int i = 0; i < cmrFields.length; ++i) {
if (cmrFields[i].isDirty(ctx)) {
modified = true;
break;
}
}
}
}
return modified;
}
private boolean areCmpFieldsDirty(final CmpEntityBeanContext ctx,
final EntityState entityState) {
for (int i = 0; i < tableFields.length; ++i) {
final JDBCCMPFieldBridge field = tableFields[i];
if (entityState.isCheckDirty(i) && field.isDirty(ctx)) {
return true;
}
}
return false;
}
public FieldIterator getDirtyIterator(CmpEntityBeanContext ctx) {
int dirtyFields = 0;
final EntityState entityState = getEntityState(ctx);
for (int i = 0; i < tableFields.length; ++i) {
JDBCCMPFieldBridge field = tableFields[i];
if (entityState.isCheckDirty(i) && field.isDirty(ctx)) {
entityState.setUpdateRequired(i);
++dirtyFields;
}
}
return dirtyFields > 0 ? getEntityState(ctx).getDirtyIterator(ctx) : EMPTY_FIELD_ITERATOR;
}
public boolean hasLockedFields(CmpEntityBeanContext ctx) {
return getEntityState(ctx).hasLockedFields();
}
public FieldIterator getLockedIterator(CmpEntityBeanContext ctx) {
return getEntityState(ctx).getLockedIterator(ctx);
}
public void initPersistenceContext(CmpEntityBeanContext ctx) {
ctx.setPersistenceContext(new JDBCContext(jdbcContextSize, new EntityState()));
}
/**
* This is only called in commit option B
*/
public void resetPersistenceContext(CmpEntityBeanContext ctx) {
for (int i = 0; i < primaryKeyFields.length; ++i)
primaryKeyFields[i].resetPersistenceContext(ctx);
for (int i = 0; i < cmpFields.length; ++i)
cmpFields[i].resetPersistenceContext(ctx);
for (int i = 0; i < cmrFields.length; ++i)
cmrFields[i].resetPersistenceContext(ctx);
}
public static void destroyPersistenceContext(CmpEntityBeanContext ctx) {
ctx.setPersistenceContext(null);
}
//
// Commands to handle primary keys
//
public int setPrimaryKeyParameters(PreparedStatement ps, int parameterIndex, Object primaryKey) {
for (int i = 0; i < primaryKeyFields.length; ++i)
parameterIndex = primaryKeyFields[i].setPrimaryKeyParameters(ps, parameterIndex, primaryKey);
return parameterIndex;
}
public int loadPrimaryKeyResults(ResultSet rs, int parameterIndex, Object[] pkRef) {
pkRef[0] = createPrimaryKeyInstance();
for (int i = 0; i < primaryKeyFields.length; ++i)
parameterIndex = primaryKeyFields[i].loadPrimaryKeyResults(rs, parameterIndex, pkRef);
return parameterIndex;
}
public Object extractPrimaryKeyFromInstance(CmpEntityBeanContext ctx) {
try {
Object pk = null;
for (int i = 0; i < primaryKeyFields.length; ++i) {
JDBCCMPFieldBridge pkField = primaryKeyFields[i];
Object fieldValue = pkField.getInstanceValue(ctx);
// updated pk object with return form set primary key value to
// handle single valued non-composit pks and more complicated behivors.
pk = pkField.setPrimaryKeyValue(pk, fieldValue);
}
return pk;
} catch (EJBException e) {
// to avoid double wrap of EJBExceptions
throw e;
} catch (Exception e) {
// Non recoverable internal exception
throw new EJBException("Internal error extracting primary key from " +
"instance", e);
}
}
public void injectPrimaryKeyIntoInstance(CmpEntityBeanContext ctx, Object pk) {
for (int i = 0; i < primaryKeyFields.length; ++i) {
JDBCCMPFieldBridge pkField = primaryKeyFields[i];
Object fieldValue = pkField.getPrimaryKeyValue(pk);
pkField.setInstanceValue(ctx, fieldValue);
}
}
int getNextJDBCContextIndex() {
return jdbcContextSize++;
}
int addTableField(JDBCCMPFieldBridge field) {
JDBCCMPFieldBridge[] tmpFields = tableFields;
if (tableFields == null) {
tableFields = new JDBCCMPFieldBridge[1];
} else {
tableFields = new JDBCCMPFieldBridge[tableFields.length + 1];
System.arraycopy(tmpFields, 0, tableFields, 0, tmpFields.length);
}
int index = tableFields.length - 1;
tableFields[index] = field;
return index;
}
public JDBCFieldBridge[] getTableFields() {
return tableFields;
}
/**
* Marks the context as removed.
*
* @param ctx instance's context
*/
public void setRemoved(CmpEntityBeanContext ctx) {
getEntityState(ctx).setRemoved();
}
/**
* @param ctx instance's context.
* @return true if instance was removed.
*/
public boolean isRemoved(CmpEntityBeanContext ctx) {
return getEntityState(ctx).isRemoved();
}
/**
* Marks an instance as being removed
*/
public void setIsBeingRemoved(CmpEntityBeanContext ctx) {
getEntityState(ctx).setIsBeingRemoved();
}
/**
* @param ctx instance's context.
* @return true if instance is being removed.
*/
public boolean isBeingRemoved(CmpEntityBeanContext ctx) {
return getEntityState(ctx).isBeingRemoved();
}
/**
* Marks the instance as scheduled for cascade delete (not for batch cascade delete)
*
* @param ctx instance's context.
*/
public void scheduleForCascadeDelete(CmpEntityBeanContext ctx) {
getEntityState(ctx).scheduleForCascadeDelete();
if (log.isTraceEnabled())
log.trace("Scheduled for cascade-delete: " + ctx.getPrimaryKey());
}
/**
* @param ctx instance's context.
* @return true if instance was scheduled for cascade delete (not for batch cascade delete)
*/
public boolean isScheduledForCascadeDelete(CmpEntityBeanContext ctx) {
return getEntityState(ctx).isScheduledForCascadeDelete();
}
/**
* Marks the instance as scheduled for batch cascade delete (not for cascade delete)
*
* @param ctx instance's context.
*/
public void scheduleForBatchCascadeDelete(CmpEntityBeanContext ctx) {
getEntityState(ctx).scheduleForBatchCascadeDelete();
if (log.isTraceEnabled())
log.trace("Scheduled for batch-cascade-delete: " + ctx.getPrimaryKey());
}
/**
* @param ctx instance's context.
* @return true if instance was scheduled for batch cascade delete (not for cascade delete)
*/
public boolean isScheduledForBatchCascadeDelete(CmpEntityBeanContext ctx) {
return getEntityState(ctx).isScheduledForBatchCascadeDelete();
}
private static EntityState getEntityState(CmpEntityBeanContext ctx) {
JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
EntityState entityState = jdbcCtx.getEntityState();
if (entityState == null)
throw new IllegalStateException("Entity state is null.");
return entityState;
}
private void loadCMPFields(JDBCEntityMetaData metadata) {
// only non pk fields are stored here at first and then later
// the pk fields are added to the front (makes sql easier to read)
List<JDBCCMPFieldMetaData> cmpFieldsMD = metadata.getCMPFields();
List<JDBCCMPFieldBridge> cmpFieldsList = new ArrayList<JDBCCMPFieldBridge>();
// primary key cmp fields
List<JDBCCMPFieldBridge> pkFieldsList = new ArrayList<JDBCCMPFieldBridge>();
// create pk fields
for (JDBCCMPFieldMetaData fieldMD : cmpFieldsMD) {
if (fieldMD.isPrimaryKeyMember()) {
pkFieldsList.add(createCMPField(metadata, fieldMD));
}
}
for (JDBCCMPFieldMetaData fieldMD : cmpFieldsMD) {
if (!fieldMD.isPrimaryKeyMember()) {
cmpFieldsList.add(createCMPField(metadata, fieldMD));
}
}
// save the pk fields in the pk field array
primaryKeyFields = pkFieldsList.toArray(new JDBCCMPFieldBridge[pkFieldsList.size()]);
// add the pk fields to the front of the cmp list, per guarantee above
cmpFields = cmpFieldsList.toArray(new JDBCCMPFieldBridge[cmpFieldsList.size()]);
}
private void loadCMRFields(JDBCEntityMetaData metadata) {
cmrFields = new JDBCCMRFieldBridge[metadata.getRelationshipRoles().size()];
// create each field
int cmrFieldIndex = 0;
for (Iterator iter = metadata.getRelationshipRoles().iterator(); iter.hasNext(); ) {
JDBCRelationshipRoleMetaData relationshipRole = (JDBCRelationshipRoleMetaData) iter.next();
JDBCCMRFieldBridge cmrField = new JDBCCMRFieldBridge(this, manager, relationshipRole);
cmrFields[cmrFieldIndex++] = cmrField;
}
}
private void loadLoadGroups(JDBCEntityMetaData metadata) {
loadGroupMasks = new HashMap();
// load optimistic locking mask and add it to all the load group masks
JDBCOptimisticLockingMetaData olMD = metadata.getOptimisticLocking();
if (olMD != null) {
if (versionField != null) {
defaultLockGroupMask = new boolean[tableFields.length];
defaultLockGroupMask[versionField.getTableIndex()] = true;
versionField.setLockingStrategy(LockingStrategy.VERSION);
} else if (olMD.getGroupName() != null) {
defaultLockGroupMask = loadGroupMask(olMD.getGroupName(), null);
for (int i = 0; i < tableFields.length; ++i) {
if (defaultLockGroupMask[i]) {
JDBCCMPFieldBridge tableField = tableFields[i];
tableField.setLockingStrategy(LockingStrategy.GROUP);
tableField.addDefaultFlag(ADD_TO_WHERE_ON_UPDATE);
}
}
} else {// read or modified strategy
LockingStrategy strategy =
(olMD.getLockingStrategy() == JDBCOptimisticLockingMetaData.LockingStrategy.READ_STRATEGY ?
LockingStrategy.READ : LockingStrategy.MODIFIED
);
for (int i = 0; i < tableFields.length; ++i) {
JDBCCMPFieldBridge field = tableFields[i];
if (!field.isPrimaryKeyMember())
field.setLockingStrategy(strategy);
}
}
}
// add the * load group
boolean[] defaultLoadGroup = new boolean[tableFields.length];
Arrays.fill(defaultLoadGroup, true);
for (int i = 0; i < primaryKeyFields.length; ++i) {
int tableIndex = primaryKeyFields[i].getTableIndex();
defaultLoadGroup[tableIndex] = false;
}
loadGroupMasks.put(DEFAULT_LOADGROUP_NAME, defaultLoadGroup);
// put each group in the load groups map by group name
Iterator groupNames = metadata.getLoadGroups().keySet().iterator();
while (groupNames.hasNext()) {
// get the group name
String groupName = (String) groupNames.next();
boolean[] loadGroup = loadGroupMask(groupName, defaultLockGroupMask);
loadGroupMasks.put(groupName, loadGroup);
}
loadGroupMasks = Collections.unmodifiableMap(loadGroupMasks);
}
private boolean[] loadGroupMask(String groupName, boolean[] defaultGroup) {
List<String> fieldNames = metadata.getLoadGroup(groupName);
boolean[] group = new boolean[tableFields.length];
if (defaultGroup != null)
System.arraycopy(defaultGroup, 0, group, 0, group.length);
for (String fieldName : fieldNames) {
JDBCFieldBridge field = (JDBCFieldBridge) getFieldByName(fieldName);
if (field == null)
throw new RuntimeException(
"Field " + fieldName + " not found for entity " + getEntityName());
if (field instanceof JDBCCMRFieldBridge) {
JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) field;
if (cmrField.hasForeignKey()) {
JDBCCMPFieldBridge[] fkFields = (JDBCCMPFieldBridge[]) cmrField.getForeignKeyFields();
for (int i = 0; i < fkFields.length; ++i) {
group[fkFields[i].getTableIndex()] = true;
}
} else {
throw new RuntimeException("Only CMR fields that have " +
"a foreign-key may be a member of a load group: " +
"fieldName=" + fieldName);
}
} else {
group[((JDBCCMPFieldBridge) field).getTableIndex()] = true;
}
}
return group;
}
private void loadEagerLoadGroup(JDBCEntityMetaData metadata) {
String eagerLoadGroupName = metadata.getEagerLoadGroup();
if (eagerLoadGroupName == null) {
// can be null in case of <eager-load-group/>, meaning empty load group
eagerLoadGroupMask = defaultLockGroupMask;
} else
eagerLoadGroupMask = (boolean[]) loadGroupMasks.get(eagerLoadGroupName);
}
private void loadLazyLoadGroups(JDBCEntityMetaData metadata) {
List lazyGroupNames = metadata.getLazyLoadGroups();
lazyLoadGroupMasks = new ArrayList(lazyGroupNames.size());
for (Iterator lazyLoadGroupNames = lazyGroupNames.iterator(); lazyLoadGroupNames.hasNext(); ) {
String lazyLoadGroupName = (String) lazyLoadGroupNames.next();
lazyLoadGroupMasks.add(loadGroupMasks.get(lazyLoadGroupName));
}
lazyLoadGroupMasks = Collections.unmodifiableList(lazyLoadGroupMasks);
}
private JDBCCMPFieldBridge createCMPField(JDBCEntityMetaData metadata,
JDBCCMPFieldMetaData cmpFieldMetaData) {
JDBCCMPFieldBridge cmpField;
if (metadata.isCMP1x())
cmpField = new JDBCCMP1xFieldBridge(manager, cmpFieldMetaData);
else
cmpField = new JDBCCMP2xFieldBridge(manager, cmpFieldMetaData);
return cmpField;
}
private void loadSelectors(JDBCEntityMetaData metadata) {
// Don't know if this is the best way to do this. Another way would be
// to deligate seletors to the JDBCFindEntitiesCommand, but this is
// easier now.
selectorsByMethod = new HashMap(metadata.getQueries().size());
Iterator definedFinders = manager.getMetaData().getQueries().iterator();
while (definedFinders.hasNext()) {
JDBCQueryMetaData q = (JDBCQueryMetaData) definedFinders.next();
if (q.getMethod().getName().startsWith("ejbSelect"))
selectorsByMethod.put(q.getMethod(), new JDBCSelectorBridge(manager, q));
}
selectorsByMethod = Collections.unmodifiableMap(selectorsByMethod);
}
private void addCMPField(JDBCCMPFieldBridge field) {
JDBCCMPFieldBridge[] tmpCMPFields = cmpFields;
cmpFields = new JDBCCMPFieldBridge[cmpFields.length + 1];
System.arraycopy(tmpCMPFields, 0, cmpFields, 0, tmpCMPFields.length);
cmpFields[tmpCMPFields.length] = field;
}
public class EntityState {
private static final byte REMOVED = 1;
private static final byte SCHEDULED_FOR_CASCADE_DELETE = 2;
private static final byte SCHEDULED_FOR_BATCH_CASCADE_DELETE = 4;
private static final byte IS_BEING_REMOVED = 8;
/**
* indicates whether ejbCreate method was executed
*/
private boolean ejbCreateDone = false;
/**
* indicates whether ejbPostCreate method was executed
*/
private boolean ejbPostCreateDone = false;
private byte entityFlags;
/**
* array of field flags
*/
private final byte[] fieldFlags = new byte[tableFields.length];
public EntityState() {
for (int i = 0; i < tableFields.length; ++i) {
fieldFlags[i] = tableFields[i].getDefaultFlags();
}
}
public void setRemoved() {
entityFlags |= REMOVED;
entityFlags &= ~(SCHEDULED_FOR_CASCADE_DELETE | SCHEDULED_FOR_BATCH_CASCADE_DELETE | IS_BEING_REMOVED);
}
public boolean isRemoved() {
return (entityFlags & REMOVED) > 0;
}
public void setIsBeingRemoved() {
entityFlags |= IS_BEING_REMOVED;
}
public boolean isBeingRemoved() {
return (entityFlags & IS_BEING_REMOVED) > 0;
}
public void scheduleForCascadeDelete() {
entityFlags |= SCHEDULED_FOR_CASCADE_DELETE;
}
public boolean isScheduledForCascadeDelete() {
return (entityFlags & SCHEDULED_FOR_CASCADE_DELETE) > 0;
}
public void scheduleForBatchCascadeDelete() {
entityFlags |= SCHEDULED_FOR_BATCH_CASCADE_DELETE | SCHEDULED_FOR_CASCADE_DELETE;
}
public boolean isScheduledForBatchCascadeDelete() {
return (entityFlags & SCHEDULED_FOR_BATCH_CASCADE_DELETE) > 0;
}
public void setCreated() {
ejbCreateDone = true;
ejbPostCreateDone = true;
}
public boolean isCreated() {
return ejbCreateDone && ejbPostCreateDone;
}
/**
* @param fieldIndex index of the field
* @return true if the field is loaded
*/
public boolean isLoaded(int fieldIndex) {
return (fieldFlags[fieldIndex] & LOADED) > 0;
}
/**
* Marks the field as loaded.
*
* @param fieldIndex index of the field.
*/
public void setLoaded(int fieldIndex) {
fieldFlags[fieldIndex] |= LOADED;
}
/**
* Marks the field to be loaded.
*
* @param fieldIndex index of the field.
*/
public void setLoadRequired(int fieldIndex) {
fieldFlags[fieldIndex] |= LOAD_REQUIRED;
}
/**
* Marks the field to be updated.
*
* @param fieldIndex index of the field.
*/
public void setUpdateRequired(int fieldIndex) {
fieldFlags[fieldIndex] |= DIRTY;
}
/**
* The field will be checked for dirty state at commit.
*
* @param fieldIndex index of the field.
*/
public void setCheckDirty(int fieldIndex) {
fieldFlags[fieldIndex] |= CHECK_DIRTY;
}
/**
* @param fieldIndex the index of the field that should be checked for dirty state.
* @return true if the field should be checked for dirty state.
*/
public boolean isCheckDirty(int fieldIndex) {
return (fieldFlags[fieldIndex] & CHECK_DIRTY) > 0;
}
/**
* Marks the field as clean.
*
* @param fieldIndex nextIndex of the field.
*/
public void setClean(int fieldIndex) {
fieldFlags[fieldIndex] &= ~(CHECK_DIRTY | DIRTY | LOCKED);
}
/**
* Resets field flags.
*
* @param fieldIndex nextIndex of the field.
*/
public void resetFlags(int fieldIndex) {
fieldFlags[fieldIndex] = tableFields[fieldIndex].getDefaultFlags();
}
public FieldIterator getDirtyIterator(CmpEntityBeanContext ctx) {
return new MaskFieldIterator((byte) (DIRTY | ADD_TO_SET_ON_UPDATE));
}
public boolean hasLockedFields() {
boolean result = false;
for (int i = 0; i < fieldFlags.length; ++i) {
if ((fieldFlags[i] & (LOCKED | ADD_TO_WHERE_ON_UPDATE)) > 0) {
result = true;
break;
}
}
return result;
}
public FieldIterator getLockedIterator(CmpEntityBeanContext ctx) {
return new MaskFieldIterator((byte) (LOCKED | ADD_TO_WHERE_ON_UPDATE));
}
public boolean lockValue(int fieldIndex) {
boolean lock = false;
byte fieldFlag = fieldFlags[fieldIndex];
if ((fieldFlag & LOADED) > 0 && (fieldFlag & LOCKED) == 0) {
fieldFlags[fieldIndex] |= LOCKED;
lock = true;
}
return lock;
}
public FieldIterator getLoadIterator(CmpEntityBeanContext ctx) {
return new MaskFieldIterator(LOAD_REQUIRED);
}
// Inner
private class MaskFieldIterator implements FieldIterator {
private final byte flagMask;
private int nextIndex = 0;
private int curIndex = -1;
public MaskFieldIterator(byte flagMask) {
this.flagMask = flagMask;
}
public boolean hasNext() {
while (nextIndex < fieldFlags.length) {
if ((fieldFlags[nextIndex] & flagMask) > 0) {
return true;
}
++nextIndex;
}
return false;
}
public JDBCCMPFieldBridge next() {
if (!hasNext())
throw new NoSuchElementException();
curIndex = nextIndex;
return tableFields[nextIndex++];
}
public void remove() {
fieldFlags[curIndex] &= ~flagMask;
}
public void removeAll() {
int inversedMask = ~flagMask;
for (int i = 0; i < fieldFlags.length; ++i)
fieldFlags[i] &= inversedMask;
}
public void reset() {
nextIndex = 0;
curIndex = -1;
}
}
}
public static final FieldIterator EMPTY_FIELD_ITERATOR = new FieldIterator() {
public boolean hasNext() {
return false;
}
public JDBCCMPFieldBridge next() {
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
public void removeAll() {
throw new UnsupportedOperationException();
}
public void reset() {
}
};
public static interface FieldIterator {
/**
* @return true if there are more fields to iterate through.
*/
boolean hasNext();
/**
* @return the next field.
*/
JDBCCMPFieldBridge next();
/**
* Removes the current field from the iterator (not from the underlying array or another source)
*/
void remove();
/**
* Removes all the fields from the iterator (not from the underlying array or another source).
*/
void removeAll();
/**
* Resets the current position to the first field.
*/
void reset();
}
}