package org.jboss.ejb.plugins.cmp.jdbc.bridge;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.Arrays;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Principal;
import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.EJBLocalObject;
import javax.ejb.EJBLocalHome;
import javax.ejb.RemoveException;
import javax.ejb.NoSuchObjectLocalException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.RollbackException;
import org.jboss.deployment.DeploymentException;
import org.jboss.ejb.EntityCache;
import org.jboss.ejb.EntityContainer;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.LocalProxyFactory;
import org.jboss.ejb.plugins.cmp.bridge.EntityBridge;
import org.jboss.ejb.plugins.cmp.bridge.FieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCContext;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
import org.jboss.ejb.plugins.cmp.jdbc.CascadeDeleteStrategy;
import org.jboss.ejb.plugins.cmp.jdbc.RelationData;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCParameterSetter;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCResultSetReader;
import org.jboss.tm.TransactionLocal;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
import org.jboss.ejb.plugins.lock.Entrancy;
import org.jboss.invocation.InvocationType;
import org.jboss.logging.Logger;
import org.jboss.security.SecurityAssociation;
public final class JDBCCMRFieldBridge extends JDBCAbstractCMRFieldBridge
{
private final JDBCEntityBridge entity;
private final JDBCStoreManager manager;
private final JDBCRelationshipRoleMetaData metadata;
private DataSource dataSource;
private String qualifiedTableName;
private String tableName;
private JDBCCMP2xFieldBridge[] tableKeyFields;
private JDBCType jdbcType;
private WeakReference relatedContainerRef;
private JDBCStoreManager relatedManager;
private JDBCEntityBridge relatedEntity;
private JDBCCMRFieldBridge relatedCMRField;
private final Logger log;
private JDBCCMP2xFieldBridge[] foreignKeyFields;
private boolean allFKFieldsMappedToPKFields;
private final Map relatedPKFieldsByMyPKFields = new HashMap();
private final Map relatedPKFieldsByMyFKFields = new HashMap();
private boolean hasFKFieldsMappedToCMPFields;
private final TransactionLocal relatedPKValuesWaitingForMyPK = new TransactionLocal()
{
protected Object initialValue()
{
return new HashMap();
}
};
private Method relatedFindByPrimaryKey;
private final int jdbcContextIndex;
private CascadeDeleteStrategy cascadeDeleteStrategy;
private RelationDataManager relationManager;
public JDBCCMRFieldBridge(JDBCEntityBridge entity,
JDBCStoreManager manager,
JDBCRelationshipRoleMetaData metadata)
throws DeploymentException
{
this.entity = entity;
this.manager = manager;
this.metadata = metadata;
this.jdbcContextIndex = ((JDBCEntityBridge) manager.getEntityBridge()).getNextJDBCContextIndex();
String categoryName = this.getClass().getName() +
"." + manager.getMetaData().getName() + ".";
if(metadata.getCMRFieldName() != null)
{
categoryName += metadata.getCMRFieldName();
}
else
{
categoryName += metadata.getRelatedRole().getEntity().getName() +
"-" + metadata.getRelatedRole().getCMRFieldName();
}
this.log = Logger.getLogger(categoryName);
}
public RelationDataManager getRelationDataManager()
{
return relationManager;
}
public void resolveRelationship() throws DeploymentException
{
String relatedEntityName = metadata.getRelatedRole().getEntity().getName();
Catalog catalog = (Catalog) manager.getApplicationData("CATALOG");
relatedEntity = (JDBCEntityBridge) catalog.getEntityByEJBName(relatedEntityName);
if(relatedEntity == null)
{
throw new DeploymentException("Related entity not found: " +
"entity=" +
entity.getEntityName() +
", " +
"cmrField=" +
getFieldName() +
", " +
"relatedEntity=" + relatedEntityName);
}
JDBCCMRFieldBridge[] cmrFields = (JDBCCMRFieldBridge[]) relatedEntity.getCMRFields();
for(int i = 0; i < cmrFields.length; ++i)
{
JDBCCMRFieldBridge cmrField = cmrFields[i];
if(metadata.getRelatedRole() == cmrField.getMetaData())
{
relatedCMRField = cmrField;
break;
}
}
if(relatedCMRField == null)
{
String message = "Related CMR field not found in " +
relatedEntity.getEntityName() + " for relationship from";
message += entity.getEntityName() + ".";
if(getFieldName() != null)
{
message += getFieldName();
}
else
{
message += "<no-field>";
}
message += " to ";
message += relatedEntityName + ".";
if(metadata.getRelatedRole().getCMRFieldName() != null)
{
message += metadata.getRelatedRole().getCMRFieldName();
}
else
{
message += "<no-field>";
}
throw new DeploymentException(message);
}
relatedManager = (JDBCStoreManager) relatedEntity.getManager();
EntityContainer relatedContainer = relatedManager.getContainer();
this.relatedContainerRef = new WeakReference(relatedContainer);
Class homeClass = (relatedContainer.getLocalHomeClass() != null ?
relatedContainer.getLocalHomeClass() : relatedContainer.getHomeClass());
try
{
relatedFindByPrimaryKey =
homeClass.getMethod("findByPrimaryKey", new Class[]{relatedEntity.getPrimaryKeyClass()});
}
catch(Exception e)
{
throw new DeploymentException("findByPrimaryKey(" +
relatedEntity.getPrimaryKeyClass().getName()
+ " pk) was not found in " + homeClass.getName());
}
if(metadata.getRelationMetaData().isTableMappingStyle())
{
dataSource = metadata.getRelationMetaData().getDataSource();
}
else
{
dataSource = hasForeignKey() ? entity.getDataSource() : relatedEntity.getDataSource();
}
qualifiedTableName = SQLUtil.fixTableName(metadata.getRelationMetaData().getDefaultTableName(), dataSource);
tableName = SQLUtil.getTableNameWithoutSchema(qualifiedTableName);
if(metadata.getRelationMetaData().isTableMappingStyle())
{
Collection tableKeys = metadata.getKeyFields();
List keyFieldsList = new ArrayList(tableKeys.size());
Map pkFieldsToFKFields = new HashMap(tableKeys.size());
for(Iterator i = tableKeys.iterator(); i.hasNext();)
{
JDBCCMPFieldMetaData cmpFieldMetaData = (JDBCCMPFieldMetaData) i.next();
FieldBridge pkField = entity.getFieldByName(cmpFieldMetaData.getFieldName());
if(pkField == null)
{
throw new DeploymentException("Primary key not found for key-field " + cmpFieldMetaData.getFieldName());
}
pkFieldsToFKFields.put(pkField, new JDBCCMP2xFieldBridge(manager, cmpFieldMetaData));
}
JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
for(int i = 0; i < pkFields.length; ++i)
{
Object fkField = pkFieldsToFKFields.get(pkFields[i]);
if(fkField == null)
{
throw new DeploymentException("Primary key " + pkFields[i].getFieldName() + " is not mapped.");
}
keyFieldsList.add(fkField);
}
tableKeyFields = (JDBCCMP2xFieldBridge[]) keyFieldsList.toArray(
new JDBCCMP2xFieldBridge[keyFieldsList.size()]);
}
else
{
initializeForeignKeyFields();
}
relationManager = relatedCMRField.initRelationManager(this);
}
public void start() throws DeploymentException
{
cascadeDeleteStrategy = CascadeDeleteStrategy.getCascadeDeleteStrategy(this);
}
public boolean removeFromRelations(EntityEnterpriseContext ctx, Object[] oldRelationsRef)
{
load(ctx);
FieldState fieldState = getFieldState(ctx);
List value = fieldState.getValue();
boolean removed;
if(!value.isEmpty())
{
cascadeDeleteStrategy.removedIds(ctx, oldRelationsRef, value);
removed = true;
}
else
{
removed = false;
}
return removed;
}
public void cascadeDelete(EntityEnterpriseContext ctx, List oldValues)
throws RemoveException, RemoteException
{
cascadeDeleteStrategy.cascadeDelete(ctx, oldValues);
}
public boolean isBatchCascadeDelete()
{
return (cascadeDeleteStrategy instanceof CascadeDeleteStrategy.BatchCascadeDeleteStrategy);
}
public JDBCStoreManager getJDBCStoreManager()
{
return manager;
}
public JDBCAbstractEntityBridge getEntity()
{
return entity;
}
public JDBCRelationshipRoleMetaData getMetaData()
{
return metadata;
}
public JDBCRelationMetaData getRelationMetaData()
{
return metadata.getRelationMetaData();
}
public String getFieldName()
{
return metadata.getCMRFieldName();
}
public String getQualifiedTableName()
{
return qualifiedTableName;
}
public String getTableName()
{
return tableName;
}
public DataSource getDataSource()
{
return dataSource;
}
public JDBCReadAheadMetaData getReadAhead()
{
return metadata.getReadAhead();
}
public JDBCType getJDBCType()
{
return jdbcType;
}
public boolean isPrimaryKeyMember()
{
return false;
}
public boolean hasForeignKey()
{
return foreignKeyFields != null;
}
public boolean allFkFieldsMappedToPkFields()
{
return allFKFieldsMappedToPKFields;
}
public boolean isCollectionValued()
{
return metadata.getRelatedRole().isMultiplicityMany();
}
public boolean isSingleValued()
{
return metadata.getRelatedRole().isMultiplicityOne();
}
public JDBCFieldBridge[] getTableKeyFields()
{
return tableKeyFields;
}
public JDBCFieldBridge[] getForeignKeyFields()
{
return foreignKeyFields;
}
public JDBCAbstractCMRFieldBridge getRelatedCMRField()
{
return relatedCMRField;
}
public JDBCStoreManager getRelatedManager()
{
return relatedManager;
}
public EntityBridge getRelatedEntity()
{
return relatedEntity;
}
public JDBCEntityBridge getRelatedJDBCEntity()
{
return relatedEntity;
}
private final EntityContainer getRelatedContainer()
{
return (EntityContainer) relatedContainerRef.get();
}
public final Class getRelatedLocalInterface()
{
return getRelatedContainer().getLocalClass();
}
public final LocalProxyFactory getRelatedInvoker()
{
return getRelatedContainer().getLocalProxyFactory();
}
public boolean isLoaded(EntityEnterpriseContext ctx)
{
return getFieldState(ctx).isLoaded;
}
public void addRelatedPKsWaitedForMe(EntityEnterpriseContext ctx)
{
final Map relatedPKsMap = getRelatedPKsWaitingForMyPK();
synchronized(relatedPKsMap)
{
List relatedPKsWaitingForMe = (List) relatedPKsMap.get(ctx.getId());
if(relatedPKsWaitingForMe != null)
{
for(Iterator waitingPKsIter = relatedPKsWaitingForMe.iterator(); waitingPKsIter.hasNext();)
{
Object waitingPK = waitingPKsIter.next();
waitingPKsIter.remove();
if(isForeignKeyValid(waitingPK))
{
createRelationLinks(ctx, waitingPK);
}
}
}
}
}
public boolean isReadOnly()
{
return getRelationMetaData().isReadOnly();
}
public boolean isReadTimedOut(EntityEnterpriseContext ctx)
{
if(!isReadOnly())
{
return true;
}
if(getRelationMetaData().getReadTimeOut() == -1)
{
return false;
}
long readInterval = System.currentTimeMillis() - getFieldState(ctx).getLastRead();
return readInterval > getRelationMetaData().getReadTimeOut();
}
public Object getValue(EntityEnterpriseContext ctx)
{
return getInstanceValue(ctx);
}
public void setValue(EntityEnterpriseContext ctx, Object value)
{
if(isReadOnly())
{
throw new EJBException("Field is read-only: fieldName=" + getFieldName());
}
if(!JDBCEntityBridge.isEjbCreateDone(ctx))
{
throw new IllegalStateException("A CMR field cannot be set " +
"in ejbCreate; this should be done in the ejbPostCreate " +
"method instead [EJB 2.0 Spec. 10.5.2].");
}
if(isCollectionValued() && value == null)
{
throw new IllegalArgumentException("null cannot be assigned to a " +
"collection-valued cmr-field [EJB 2.0 Spec. 10.3.8].");
}
setInstanceValue(ctx, value);
}
public Object getInstanceValue(EntityEnterpriseContext myCtx)
{
load(myCtx);
FieldState fieldState = getFieldState(myCtx);
if(isCollectionValued())
{
return fieldState.getRelationSet();
}
try
{
List value = fieldState.getValue();
if(!value.isEmpty())
{
Object fk = value.get(0);
return getRelatedEntityByFK(fk);
}
else if(foreignKeyFields != null)
{
Object relatedId = getRelatedIdFromContext(myCtx);
if(relatedId != null)
{
return getRelatedEntityByFK(relatedId);
}
}
return null;
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException(e);
}
}
public EJBLocalObject getRelatedEntityByFK(Object fk)
{
EJBLocalObject relatedLocalObject = null;
final EntityContainer relatedContainer = getRelatedContainer();
if(hasFKFieldsMappedToCMPFields
&& relatedManager.getReadAheadCache().getPreloadDataMap(fk, false) == null )
{
EJBLocalHome relatedHome = relatedContainer.getLocalProxyFactory().getEJBLocalHome();
try
{
relatedLocalObject = (EJBLocalObject) relatedFindByPrimaryKey.invoke(relatedHome, new Object[]{fk});
}
catch(Exception ignore)
{
}
}
else
{
relatedLocalObject = relatedContainer.getLocalProxyFactory().getEntityEJBLocalObject(fk);
}
return relatedLocalObject;
}
public boolean isForeignKeyValid(Object fk)
{
boolean valid;
if(relatedManager.getReadAheadCache().getPreloadDataMap(fk, false) != null)
{
valid = true;
}
else
{
EJBLocalHome relatedHome = getRelatedContainer().getLocalProxyFactory().getEJBLocalHome();
try
{
relatedFindByPrimaryKey.invoke(relatedHome, new Object[]{fk});
valid = true;
}
catch(Exception ignore)
{
valid = false;
}
}
return valid;
}
public void setInstanceValue(EntityEnterpriseContext myCtx, Object newValue)
{
List newPks;
if(newValue instanceof Collection)
{
Collection col = (Collection) newValue;
if(!col.isEmpty())
{
newPks = new ArrayList(col.size());
for(Iterator iter = col.iterator(); iter.hasNext();)
{
Object localObject = iter.next();
if(localObject != null)
{
Object relatedId = getRelatedPrimaryKey(localObject);
if(relatedPKFieldsByMyPKFields.size() > 0)
{
checkSetForeignKey(myCtx, relatedId);
}
newPks.add(relatedId);
}
}
}
else
{
newPks = Collections.EMPTY_LIST;
}
}
else
{
if(newValue != null)
{
newPks = Collections.singletonList(getRelatedPrimaryKey(newValue));
}
else
{
newPks = Collections.EMPTY_LIST;
}
}
load(myCtx);
FieldState fieldState = getFieldState(myCtx);
if(newValue == fieldState.getRelationSet())
{
return;
}
try
{
List value = fieldState.getValue();
if(!value.isEmpty())
{
Object[] curPks = value.toArray(new Object[value.size()]);
for(int i = 0; i < curPks.length; ++i)
{
destroyRelationLinks(myCtx, curPks[i]);
}
}
for(int i = 0; i < newPks.size(); ++i)
{
createRelationLinks(myCtx, newPks.get(i));
}
}
catch(RuntimeException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException(e);
}
}
private void checkSetForeignKey(EntityEnterpriseContext myCtx, Object newValue)
throws IllegalStateException
{
JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
for(int i = 0; i < pkFields.length; ++i)
{
JDBCCMP2xFieldBridge pkField = (JDBCCMP2xFieldBridge) pkFields[i];
JDBCCMP2xFieldBridge relatedPkField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyPKFields.get(pkField);
if(relatedPkField != null)
{
Object comingValue = relatedPkField.getPrimaryKeyValue(newValue);
Object currentValue = pkField.getInstanceValue(myCtx);
if(!comingValue.equals(currentValue))
{
throw new IllegalStateException("Can't create relationship: CMR field "
+
entity.getEntityName() +
"." +
getFieldName()
+
" has foreign key fields mapped to the primary key columns."
+
" Primary key may only be set once in ejbCreate [EJB 2.0 Spec. 10.3.5]."
+
" primary key value is " +
currentValue
+ " overriding value is " + comingValue);
}
}
}
}
public void createRelationLinks(EntityEnterpriseContext myCtx, Object relatedId)
{
createRelationLinks(myCtx, relatedId, true);
}
public void createRelationLinks(EntityEnterpriseContext myCtx, Object relatedId, boolean updateForeignKey)
{
if(isReadOnly())
{
throw new EJBException("Field is read-only: " + getFieldName());
}
Transaction tx = getTransaction();
if(metadata.isMultiplicityOne())
{
Object oldRelatedId = relatedCMRField.invokeGetRelatedId(tx, relatedId);
if(oldRelatedId != null)
{
invokeRemoveRelation(tx, oldRelatedId, relatedId);
relatedCMRField.invokeRemoveRelation(tx, relatedId, oldRelatedId);
}
}
addRelation(myCtx, relatedId, updateForeignKey);
relatedCMRField.invokeAddRelation(tx, relatedId, myCtx.getId());
}
public void destroyRelationLinks(EntityEnterpriseContext myCtx, Object relatedId)
{
destroyRelationLinks(myCtx, relatedId, true);
}
public void destroyRelationLinks(EntityEnterpriseContext myCtx,
Object relatedId,
boolean updateValueCollection)
{
destroyRelationLinks(myCtx, relatedId, updateValueCollection, true);
}
public void destroyRelationLinks(EntityEnterpriseContext myCtx,
Object relatedId,
boolean updateValueCollection,
boolean updateForeignKey)
{
if(isReadOnly())
{
throw new EJBException("Field is read-only: " + getFieldName());
}
removeRelation(myCtx, relatedId, updateValueCollection, updateForeignKey);
relatedCMRField.invokeRemoveRelation(getTransaction(), relatedId, myCtx.getId());
}
public void scheduleChildrenForCascadeDelete(EntityEnterpriseContext ctx)
{
load(ctx);
FieldState fieldState = getFieldState(ctx);
List value = fieldState.getValue();
if(!value.isEmpty())
{
Transaction tx = getTransaction();
for(int i = 0; i < value.size(); ++i)
{
relatedCMRField.invokeScheduleForCascadeDelete(tx, value.get(i));
}
}
}
public void scheduleChildrenForBatchCascadeDelete(EntityEnterpriseContext ctx)
{
load(ctx);
FieldState fieldState = getFieldState(ctx);
List value = fieldState.getValue();
if(!value.isEmpty())
{
Transaction tx = getTransaction();
for(int i = 0; i < value.size(); ++i)
{
relatedCMRField.invokeScheduleForBatchCascadeDelete(tx, value.get(i));
}
}
}
private Object invokeScheduleForCascadeDelete(Transaction tx, Object myId)
{
try
{
EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
CMRInvocation invocation = new CMRInvocation();
invocation.setCmrMessage(CMRMessage.SCHEDULE_FOR_CASCADE_DELETE);
invocation.setEntrancy(Entrancy.NON_ENTRANT);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this});
invocation.setTransaction(tx);
invocation.setPrincipal(actions.getPrincipal());
invocation.setCredential(actions.getCredential());
invocation.setType(InvocationType.LOCAL);
return manager.getContainer().invoke(invocation);
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException("Error in scheduleForCascadeDelete()", e);
}
}
private Object invokeScheduleForBatchCascadeDelete(Transaction tx, Object myId)
{
try
{
EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
CMRInvocation invocation = new CMRInvocation();
invocation.setCmrMessage(CMRMessage.SCHEDULE_FOR_BATCH_CASCADE_DELETE);
invocation.setEntrancy(Entrancy.NON_ENTRANT);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this});
invocation.setTransaction(tx);
invocation.setPrincipal(actions.getPrincipal());
invocation.setCredential(actions.getCredential());
invocation.setType(InvocationType.LOCAL);
return manager.getContainer().invoke(invocation);
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException("Error in scheduleForBatchCascadeDelete()", e);
}
}
private Object invokeGetRelatedId(Transaction tx, Object myId)
{
try
{
EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
CMRInvocation invocation = new CMRInvocation();
invocation.setCmrMessage(CMRMessage.GET_RELATED_ID);
invocation.setEntrancy(Entrancy.NON_ENTRANT);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this});
invocation.setTransaction(tx);
invocation.setPrincipal(actions.getPrincipal());
invocation.setCredential(actions.getCredential());
invocation.setType(InvocationType.LOCAL);
return manager.getContainer().invoke(invocation);
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException("Error in getRelatedId", e);
}
}
private void invokeAddRelation(Transaction tx, Object myId, Object relatedId)
{
try
{
EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
CMRInvocation invocation = new CMRInvocation();
invocation.setCmrMessage(CMRMessage.ADD_RELATION);
invocation.setEntrancy(Entrancy.NON_ENTRANT);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this, relatedId});
invocation.setTransaction(tx);
invocation.setPrincipal(actions.getPrincipal());
invocation.setCredential(actions.getCredential());
invocation.setType(InvocationType.LOCAL);
manager.getContainer().invoke(invocation);
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException("Error in addRelation", e);
}
}
private void invokeRemoveRelation(Transaction tx, Object myId, Object relatedId)
{
try
{
EntityCache instanceCache = (EntityCache) manager.getContainer().getInstanceCache();
SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
CMRInvocation invocation = new CMRInvocation();
invocation.setCmrMessage(CMRMessage.REMOVE_RELATION);
invocation.setEntrancy(Entrancy.NON_ENTRANT);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this, relatedId});
invocation.setTransaction(tx);
invocation.setPrincipal(actions.getPrincipal());
invocation.setCredential(actions.getCredential());
invocation.setType(InvocationType.LOCAL);
manager.getContainer().invoke(invocation);
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException("Error in removeRelation", e);
}
}
public Object getRelatedId(EntityEnterpriseContext myCtx)
{
if(isCollectionValued())
{
throw new EJBException("getRelatedId may only be called on a cmr-field with a multiplicity of one.");
}
load(myCtx);
List value = getFieldState(myCtx).getValue();
return value.isEmpty() ? null : value.get(0);
}
public Object getRelatedIdFromContext(EntityEnterpriseContext ctx)
{
Object relatedId = null;
Object fkFieldValue;
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
fkFieldValue = fkField.getInstanceValue(ctx);
if(fkFieldValue == null)
{
return null;
}
JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyFKFields.get(fkField);
relatedId = relatedPKField.setPrimaryKeyValue(relatedId, fkFieldValue);
}
return relatedId;
}
public void addRelation(EntityEnterpriseContext myCtx, Object fk)
{
addRelation(myCtx, fk, true);
relationManager.addRelation(this, myCtx.getId(), relatedCMRField, fk);
}
private void addRelation(EntityEnterpriseContext myCtx, Object fk, boolean updateForeignKey)
{
checkSetForeignKey(myCtx, fk);
if(isReadOnly())
{
throw new EJBException("Field is read-only: " + getFieldName());
}
if(!JDBCEntityBridge.isEjbCreateDone(myCtx))
{
throw new IllegalStateException("A CMR field cannot be set or added " +
"to a relationship in ejbCreate; this should be done in the " +
"ejbPostCreate method instead [EJB 2.0 Spec. 10.5.2].");
}
FieldState myState = getFieldState(myCtx);
myState.addRelation(fk);
if(hasForeignKey() && updateForeignKey)
{
setForeignKey(myCtx, fk);
}
}
public void removeRelation(EntityEnterpriseContext myCtx, Object fk)
{
removeRelation(myCtx, fk, true, true);
relationManager.removeRelation(this, myCtx.getId(), relatedCMRField, fk);
}
private void removeRelation(EntityEnterpriseContext myCtx,
Object fk,
boolean updateValueCollection,
boolean updateForeignKey)
{
if(isReadOnly())
{
throw new EJBException("Field is read-only: " + getFieldName());
}
if(updateValueCollection)
{
FieldState myState = getFieldState(myCtx);
myState.removeRelation(fk);
}
if(hasForeignKey() && updateForeignKey)
{
setForeignKey(myCtx, null);
}
}
private void load(EntityEnterpriseContext myCtx)
{
FieldState fieldState = getFieldState(myCtx);
if(fieldState.isLoaded())
{
return;
}
if(log.isTraceEnabled())
{
log.trace("Read ahead cahce load: cmrField=" + getFieldName() + " pk=" + myCtx.getId());
}
manager.getReadAheadCache().load(myCtx);
if(fieldState.isLoaded())
{
return;
}
Collection values;
if(hasForeignKey())
{
boolean loadWithManager = false;
Object fk = null;
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
if(!fkField.isLoaded(myCtx))
{
loadWithManager = true;
break;
}
Object fkFieldValue = fkField.getInstanceValue(myCtx);
if(fkFieldValue == null)
{
fk = null;
break;
}
JDBCCMP2xFieldBridge relatedPKField = (JDBCCMP2xFieldBridge) relatedPKFieldsByMyFKFields.get(fkField);
fk = relatedPKField.setPrimaryKeyValue(fk, fkFieldValue);
}
if(loadWithManager)
{
values = manager.loadRelation(this, myCtx.getId());
}
else
{
values = (fk == null ? Collections.EMPTY_LIST : Collections.singletonList(fk));
}
}
else
{
values = manager.loadRelation(this, myCtx.getId());
}
load(myCtx, values);
}
public void load(EntityEnterpriseContext myCtx, Collection values)
{
if(isSingleValued() && values.size() > 1)
{
throw new EJBException("Data contains multiple values, but this cmr field is single valued: " + values);
}
FieldState fieldState = getFieldState(myCtx);
fieldState.loadRelations(values);
if(hasForeignKey())
{
if(!values.isEmpty())
{
Object loadedValue = values.iterator().next();
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
Object fieldValue = fkField.getPrimaryKeyValue(loadedValue);
fkField.updateState(myCtx, fieldValue);
}
}
List realValue = fieldState.getValue();
Object fk = realValue.isEmpty() ? null : realValue.get(0);
setForeignKey(myCtx, fk);
}
JDBCEntityBridge.setCreated(myCtx);
}
public void setForeignKey(EntityEnterpriseContext myCtx, Object fk)
{
if(!hasForeignKey())
{
throw new EJBException(getFieldName() + " CMR field does not have a foreign key to set.");
}
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMP2xFieldBridge fkField = foreignKeyFields[i];
Object fieldValue = fkField.getPrimaryKeyValue(fk);
fkField.setInstanceValue(myCtx, fieldValue);
}
}
public void initInstance(EntityEnterpriseContext ctx)
{
getFieldState(ctx).loadRelations(Collections.EMPTY_SET);
if(foreignKeyFields == null)
{
return;
}
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMP2xFieldBridge foreignKeyField = foreignKeyFields[i];
if(!foreignKeyField.isFKFieldMappedToCMPField())
{
foreignKeyField.setInstanceValue(ctx, null);
}
}
}
public void resetPersistenceContext(EntityEnterpriseContext ctx)
{
if(!isReadTimedOut(ctx))
{
return;
}
JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
jdbcCtx.setFieldState(jdbcContextIndex, null);
if(foreignKeyFields == null)
{
return;
}
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMP2xFieldBridge foreignKeyField = foreignKeyFields[i];
if(!foreignKeyField.isFKFieldMappedToCMPField())
{
foreignKeyField.resetPersistenceContext(ctx);
}
}
}
public int setInstanceParameters(PreparedStatement ps,
int parameterIndex,
EntityEnterpriseContext ctx)
{
if(foreignKeyFields == null)
{
return parameterIndex;
}
List value = getFieldState(ctx).getValue();
Object fk = (value.isEmpty() ? null : value.get(0));
for(int i = 0; i < foreignKeyFields.length; ++i)
{
parameterIndex = foreignKeyFields[i].setPrimaryKeyParameters(ps, parameterIndex, fk);
}
return parameterIndex;
}
public int loadInstanceResults(ResultSet rs,
int parameterIndex,
EntityEnterpriseContext ctx)
{
if(!hasForeignKey())
{
return parameterIndex;
}
Object[] ref = new Object[1];
parameterIndex = loadArgumentResults(rs, parameterIndex, ref);
FieldState fieldState = getFieldState(ctx);
if(!fieldState.isLoaded())
{
if(ref[0] != null)
{
load(ctx, Collections.singleton(ref[0]));
}
else
{
load(ctx, Collections.EMPTY_SET);
}
}
return parameterIndex;
}
public int loadArgumentResults(ResultSet rs, int parameterIndex, Object[] fkRef)
{
if(foreignKeyFields == null)
{
return parameterIndex;
}
boolean fkIsNull = false;
Object[] argumentRef = new Object[1];
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMPFieldBridge field = foreignKeyFields[i];
parameterIndex = field.loadArgumentResults(rs, parameterIndex, argumentRef);
if(fkIsNull)
{
continue;
}
if(field.getPrimaryKeyField() != null)
{
if(argumentRef[0] == null)
{
fkRef[0] = null;
fkIsNull = true;
}
else
{
if(fkRef[0] == null)
{
fkRef[0] = relatedEntity.createPrimaryKeyInstance();
}
try
{
field.getPrimaryKeyField().set(fkRef[0], argumentRef[0]);
}
catch(Exception e)
{
throw new EJBException("Internal error setting foreign-key field " + getFieldName(), e);
}
}
}
else
{
fkRef[0] = argumentRef[0];
}
}
return parameterIndex;
}
public boolean isDirty(EntityEnterpriseContext ctx)
{
return foreignKeyFields == null ? relationManager.isDirty() : false;
}
public boolean invalidateCache(EntityEnterpriseContext ctx)
{
JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
FieldState fieldState = (FieldState) jdbcCtx.getFieldState(jdbcContextIndex);
return fieldState == null ? false : fieldState.isChanged();
}
public void setClean(EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public boolean isCMPField()
{
return false;
}
public JDBCEntityPersistenceStore getManager()
{
return manager;
}
public boolean hasFKFieldsMappedToCMPFields()
{
return hasFKFieldsMappedToCMPFields;
}
public void addRelatedPKWaitingForMyPK(Object myPK, Object relatedPK)
{
Map relatedPKsWaitingForMyPK = getRelatedPKsWaitingForMyPK();
synchronized(relatedPKsWaitingForMyPK)
{
List relatedPKs = (List) relatedPKsWaitingForMyPK.get(myPK);
if(relatedPKs == null)
{
relatedPKs = new ArrayList(1);
relatedPKsWaitingForMyPK.put(myPK, relatedPKs);
}
relatedPKs.add(relatedPK);
}
}
public void removeRelatedPKWaitingForMyPK(Object myPK, Object relatedPK)
{
final Map relatedPKMap = getRelatedPKsWaitingForMyPK();
synchronized(relatedPKMap)
{
List relatedPKs = (List) relatedPKMap.get(myPK);
if(relatedPKs != null)
{
relatedPKs.remove(relatedPK);
}
}
}
private FieldState getFieldState(EntityEnterpriseContext ctx)
{
JDBCContext jdbcCtx = (JDBCContext) ctx.getPersistenceContext();
FieldState fieldState = (FieldState) jdbcCtx.getFieldState(jdbcContextIndex);
if(fieldState == null)
{
fieldState = new FieldState(ctx);
jdbcCtx.setFieldState(jdbcContextIndex, fieldState);
}
return fieldState;
}
private void initializeForeignKeyFields()
throws DeploymentException
{
Collection foreignKeys = metadata.getRelatedRole().getKeyFields();
Map fkFieldsByRelatedPKFields = new HashMap();
for(Iterator i = foreignKeys.iterator(); i.hasNext();)
{
JDBCCMPFieldMetaData fkFieldMetaData = (JDBCCMPFieldMetaData) i.next();
JDBCCMP2xFieldBridge relatedPKField =
(JDBCCMP2xFieldBridge) relatedEntity.getFieldByName(fkFieldMetaData.getFieldName());
String fkColumnName = fkFieldMetaData.getColumnName();
JDBCCMP2xFieldBridge fkField = null;
JDBCFieldBridge[] tableFields = entity.getTableFields();
for(int tableInd = 0; tableInd < tableFields.length && fkField == null; ++tableInd)
{
JDBCCMP2xFieldBridge cmpField = (JDBCCMP2xFieldBridge) tableFields[tableInd];
if(fkColumnName.equals(cmpField.getColumnName()))
{
hasFKFieldsMappedToCMPFields = true;
fkField = new JDBCCMP2xFieldBridge((JDBCStoreManager) cmpField.getManager(), relatedPKField.getFieldName(),
relatedPKField.getFieldType(),
cmpField.getJDBCType(), relatedPKField.isReadOnly(),
relatedPKField.getReadTimeOut(),
relatedPKField.getPrimaryKeyClass(),
relatedPKField.getPrimaryKeyField(),
cmpField, this,
fkColumnName);
if(cmpField.isPrimaryKeyMember())
{
relatedPKFieldsByMyPKFields.put(cmpField, relatedPKField);
}
}
}
if(fkField == null)
{
fkField = new JDBCCMP2xFieldBridge(manager,
fkFieldMetaData,
manager.getJDBCTypeFactory().getJDBCType(fkFieldMetaData));
}
fkFieldsByRelatedPKFields.put(relatedPKField, fkField); relatedPKFieldsByMyFKFields.put(fkField, relatedPKField);
}
if(fkFieldsByRelatedPKFields.size() > 0)
{
JDBCFieldBridge[] relatedPKFields = relatedEntity.getPrimaryKeyFields();
List fkList = new ArrayList(relatedPKFields.length);
for(int i = 0; i < relatedPKFields.length; ++i)
{
JDBCCMPFieldBridge fkField = (JDBCCMPFieldBridge) fkFieldsByRelatedPKFields.remove(relatedPKFields[i]);
fkList.add(fkField);
}
foreignKeyFields = (JDBCCMP2xFieldBridge[]) fkList.toArray(new JDBCCMP2xFieldBridge[fkList.size()]);
}
else
{
foreignKeyFields = null;
}
allFKFieldsMappedToPKFields = relatedPKFieldsByMyPKFields.size() > 0
&& relatedPKFieldsByMyPKFields.size() == foreignKeyFields.length;
if(foreignKeyFields != null)
{
jdbcType = new CMRJDBCType(Arrays.asList(foreignKeyFields));
}
}
private Transaction getTransaction()
{
try
{
EntityContainer container = getJDBCStoreManager().getContainer();
TransactionManager tm = container.getTransactionManager();
return tm.getTransaction();
}
catch(SystemException e)
{
throw new EJBException("Error getting transaction from the transaction manager", e);
}
}
private Map getRelatedPKsWaitingForMyPK()
{
return (Map) relatedPKValuesWaitingForMyPK.get();
}
private RelationDataManager initRelationManager(JDBCCMRFieldBridge relatedField)
{
if(relationManager == null)
{
if(metadata.getRelationMetaData().isTableMappingStyle())
{
relationManager = new M2MRelationManager(this, relatedField);
}
else
{
relationManager = EMPTY_RELATION_MANAGER;
}
}
return relationManager;
}
private Object getRelatedPrimaryKey(Object localObject)
{
Object relatedId;
if(relatedEntity.getLocalInterface().isAssignableFrom(localObject.getClass()))
{
EJBLocalObject local = (EJBLocalObject) localObject;
try
{
relatedId = local.getPrimaryKey();
}
catch(NoSuchObjectLocalException e)
{
throw new IllegalArgumentException(e.getMessage());
}
}
else
{
throw new IllegalArgumentException("The values of this field must be of type " +
relatedEntity.getLocalInterface().getName());
}
return relatedId;
}
public String toString()
{
return entity.getEntityName() + '.' + getFieldName();
}
private final class FieldState
{
private final EntityEnterpriseContext ctx;
private List[] setHandle = new List[1];
private Set addedRelations;
private Set removedRelations;
private Set relationSet;
private boolean isLoaded = false;
private final long lastRead = -1;
private boolean changed;
public FieldState(EntityEnterpriseContext ctx)
{
this.ctx = ctx;
setHandle[0] = new ArrayList();
}
public List getValue()
{
if(!isLoaded)
{
throw new EJBException("CMR field value not loaded yet");
}
return Collections.unmodifiableList(setHandle[0]);
}
public boolean isLoaded()
{
return isLoaded;
}
public long getLastRead()
{
return lastRead;
}
public void addRelation(Object fk)
{
if(isLoaded)
{
setHandle[0].add(fk);
}
else
{
if(removedRelations == null)
{
removedRelations = new HashSet();
addedRelations = new HashSet();
}
removedRelations.remove(fk);
addedRelations.add(fk);
}
changed = true;
}
public void removeRelation(Object fk)
{
if(isLoaded)
{
setHandle[0].remove(fk);
}
else
{
if(removedRelations == null)
{
removedRelations = new HashSet();
addedRelations = new HashSet();
}
addedRelations.remove(fk);
removedRelations.add(fk);
}
changed = true;
}
public void loadRelations(Collection values)
{
if(isLoaded)
{
throw new EJBException("CMR field value is already loaded");
}
setHandle[0].clear();
setHandle[0].addAll(values);
if(removedRelations != null)
{
setHandle[0].removeAll(removedRelations);
removedRelations = null;
}
if(addedRelations != null)
{
setHandle[0].removeAll(addedRelations);
setHandle[0].addAll(addedRelations);
addedRelations = null;
}
isLoaded = true;
}
public Set getRelationSet()
{
if(!isLoaded)
{
throw new EJBException("CMR field value not loaded yet");
}
if(ctx.isReadOnly())
{
return new RelationSet(JDBCCMRFieldBridge.this,
ctx,
new List[]{new ArrayList(setHandle[0])},
true);
}
if(relationSet != null)
{
return relationSet;
}
try
{
EntityContainer container = getJDBCStoreManager().getContainer();
TransactionManager tm = container.getTransactionManager();
Transaction tx = tm.getTransaction();
if(tx != null && (tx.getStatus() == Status.STATUS_ACTIVE || tx.getStatus() == Status.STATUS_PREPARING))
{
relationSet = new RelationSet(JDBCCMRFieldBridge.this, ctx, setHandle, false);
TxSynchronization sync = new TxSynchronization(FieldState.this);
tx.registerSynchronization(sync);
}
else
{
relationSet = new RelationSet(JDBCCMRFieldBridge.this, ctx, new List[1], false);
}
return relationSet;
}
catch(SystemException e)
{
throw new EJBException("Error while creating RelationSet", e);
}
catch(RollbackException e)
{
throw new EJBException("Error while creating RelationSet", e);
}
}
public void invalidate()
{
List currentList = null;
if(setHandle != null && setHandle.length > 0)
{
currentList = setHandle[0];
setHandle[0] = null;
}
setHandle = new List[1];
setHandle[0] = currentList;
relationSet = null;
changed = false;
}
public boolean isChanged()
{
return changed;
}
}
private final static class CMRJDBCType implements JDBCType
{
private final String[] columnNames;
private final Class[] javaTypes;
private final int[] jdbcTypes;
private final String[] sqlTypes;
private final boolean[] notNull;
private CMRJDBCType(List fields)
{
List columnNamesList = new ArrayList();
List javaTypesList = new ArrayList();
List jdbcTypesList = new ArrayList();
List sqlTypesList = new ArrayList();
List notNullList = new ArrayList();
for(Iterator iter = fields.iterator(); iter.hasNext();)
{
JDBCCMPFieldBridge field = (JDBCCMPFieldBridge) iter.next();
JDBCType type = field.getJDBCType();
for(int i = 0; i < type.getColumnNames().length; i++)
{
columnNamesList.add(type.getColumnNames()[i]);
javaTypesList.add(type.getJavaTypes()[i]);
jdbcTypesList.add(new Integer(type.getJDBCTypes()[i]));
sqlTypesList.add(type.getSQLTypes()[i]);
notNullList.add(new Boolean(type.getNotNull()[i]));
}
}
columnNames = (String[]) columnNamesList.toArray(new String[columnNamesList.size()]);
javaTypes = (Class[]) javaTypesList.toArray(new Class[javaTypesList.size()]);
sqlTypes = (String[]) sqlTypesList.toArray(new String[sqlTypesList.size()]);
jdbcTypes = new int[jdbcTypesList.size()];
for(int i = 0; i < jdbcTypes.length; i++)
{
jdbcTypes[i] = ((Integer) jdbcTypesList.get(i)).intValue();
}
notNull = new boolean[notNullList.size()];
for(int i = 0; i < notNull.length; i++)
{
notNull[i] = ((Boolean) notNullList.get(i)).booleanValue();
}
}
public String[] getColumnNames()
{
return columnNames;
}
public Class[] getJavaTypes()
{
return javaTypes;
}
public int[] getJDBCTypes()
{
return jdbcTypes;
}
public String[] getSQLTypes()
{
return sqlTypes;
}
public boolean[] getNotNull()
{
return notNull;
}
public boolean[] getAutoIncrement()
{
return new boolean[]{false};
}
public Object getColumnValue(int index, Object value)
{
throw new UnsupportedOperationException();
}
public Object setColumnValue(int index, Object value, Object columnValue)
{
throw new UnsupportedOperationException();
}
public JDBCResultSetReader[] getResultSetReaders()
{
throw new UnsupportedOperationException();
}
public JDBCParameterSetter[] getParameterSetter()
{
throw new UnsupportedOperationException();
}
}
private final static class TxSynchronization implements Synchronization
{
private final WeakReference fieldStateRef;
private TxSynchronization(FieldState fieldState)
{
if(fieldState == null)
{
throw new IllegalArgumentException("fieldState is null");
}
this.fieldStateRef = new WeakReference(fieldState);
}
public void beforeCompletion()
{
FieldState fieldState = (FieldState) fieldStateRef.get();
if(fieldState != null)
{
fieldState.invalidate();
}
}
public void afterCompletion(int status)
{
}
}
public static interface RelationDataManager
{
void addRelation(JDBCCMRFieldBridge field, Object id, JDBCCMRFieldBridge relatedField, Object relatedId);
void removeRelation(JDBCCMRFieldBridge field, Object id, JDBCCMRFieldBridge relatedField, Object relatedId);
boolean isDirty();
RelationData getRelationData();
}
private static final RelationDataManager EMPTY_RELATION_MANAGER = new RelationDataManager()
{
public void addRelation(JDBCCMRFieldBridge field, Object id, JDBCCMRFieldBridge relatedField, Object relatedId)
{
}
public void removeRelation(JDBCCMRFieldBridge field,
Object id,
JDBCCMRFieldBridge relatedField,
Object relatedId)
{
}
public boolean isDirty()
{
return false;
}
public RelationData getRelationData()
{
throw new UnsupportedOperationException();
}
};
public static class M2MRelationManager
implements RelationDataManager
{
private final JDBCCMRFieldBridge leftField;
private final JDBCCMRFieldBridge rightField;
private final TransactionLocal relationData = new TransactionLocal()
{
protected Object initialValue()
{
return new RelationData(leftField, rightField);
}
};
public M2MRelationManager(JDBCCMRFieldBridge leftField, JDBCCMRFieldBridge rightField)
{
this.leftField = leftField;
this.rightField = rightField;
}
public void addRelation(JDBCCMRFieldBridge field,
Object id,
JDBCCMRFieldBridge relatedField,
Object relatedId)
{
final RelationData local = getRelationData();
local.addRelation(field, id, relatedField, relatedId);
}
public void removeRelation(JDBCCMRFieldBridge field,
Object id,
JDBCCMRFieldBridge relatedField,
Object relatedId)
{
RelationData local = getRelationData();
local.removeRelation(field, id, relatedField, relatedId);
}
public boolean isDirty()
{
RelationData local = getRelationData();
return local.isDirty();
}
public RelationData getRelationData()
{
final RelationData local = (RelationData) relationData.get();
return local;
}
}
interface SecurityActions
{
class UTIL
{
static SecurityActions getSecurityActions()
{
return System.getSecurityManager() == null ? NON_PRIVILEGED : PRIVILEGED;
}
}
SecurityActions NON_PRIVILEGED = new SecurityActions()
{
public Principal getPrincipal()
{
return SecurityAssociation.getPrincipal();
}
public Object getCredential()
{
return SecurityAssociation.getCredential();
}
};
SecurityActions PRIVILEGED = new SecurityActions()
{
private final PrivilegedAction getPrincipalAction = new PrivilegedAction()
{
public Object run()
{
return SecurityAssociation.getPrincipal();
}
};
private final PrivilegedAction getCredentialAction = new PrivilegedAction()
{
public Object run()
{
return SecurityAssociation.getCredential();
}
};
public Principal getPrincipal()
{
return (Principal) AccessController.doPrivileged(getPrincipalAction);
}
public Object getCredential()
{
return AccessController.doPrivileged(getCredentialAction);
}
};
Principal getPrincipal();
Object getCredential();
}
}