/*
* 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.jdbc2.bridge;
import org.jboss.ejb.plugins.cmp.bridge.EntityBridge;
import org.jboss.ejb.plugins.cmp.bridge.FieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc2.JDBCStoreManager2;
import org.jboss.ejb.plugins.cmp.jdbc2.schema.EntityTable;
import org.jboss.ejb.plugins.cmp.jdbc2.schema.RelationTable;
import org.jboss.ejb.plugins.cmp.jdbc2.schema.Cache;
import org.jboss.ejb.plugins.cmp.jdbc2.PersistentContext;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCType;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCEntityPersistenceStore;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractCMRFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractEntityBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.CMRInvocation;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.CMRMessage;
import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
import org.jboss.ejb.plugins.lock.Entrancy;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.EntityContainer;
import org.jboss.ejb.EntityCache;
import org.jboss.ejb.LocalProxyFactory;
import org.jboss.deployment.DeploymentException;
import org.jboss.logging.Logger;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.invocation.InvocationType;
import javax.ejb.EJBException;
import javax.ejb.EJBLocalObject;
import javax.ejb.RemoveException;
import javax.ejb.NoSuchObjectLocalException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.SystemException;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Collections;
import java.util.HashSet;
import java.util.ConcurrentModificationException;
import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.lang.reflect.Array;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Principal;
/**
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version <tt>$Revision: 81030 $</tt>
*/
public class JDBCCMRFieldBridge2
extends JDBCAbstractCMRFieldBridge
{
private final JDBCRelationshipRoleMetaData metadata;
private final JDBCStoreManager2 manager;
private final JDBCEntityBridge2 entity;
private final int cmrIndex;
private final Logger log;
private JDBCEntityBridge2 relatedEntity;
private JDBCCMRFieldBridge2 relatedCMRField;
private EntityContainer relatedContainer;
private JDBCCMPFieldBridge2[] tableKeyFields;
private JDBCCMPFieldBridge2[] foreignKeyFields;
private JDBCCMPFieldBridge2[] relatedPKFields;
private CMRFieldLoader loader;
private RelationTable relationTable;
private EntityTable.ForeignKeyConstraint fkConstraint;
private final TransactionManager tm;
public JDBCCMRFieldBridge2(JDBCEntityBridge2 entityBridge,
JDBCStoreManager2 manager,
JDBCRelationshipRoleMetaData metadata)
{
this.manager = manager;
this.entity = entityBridge;
this.metadata = metadata;
cmrIndex = entity.getNextCMRIndex();
tm = manager.getContainer().getTransactionManager();
log = Logger.getLogger(getClass().getName() + "." + entity.getEntityName() + "#" + getFieldName());
}
// Public
public void resolveRelationship() throws DeploymentException
{
//
// Set handles to the related entity's container, cache, manager, and invoker
//
// Related Entity Name
String relatedEntityName = metadata.getRelatedRole().getEntity().getName();
// Related Entity
Catalog catalog = (Catalog)manager.getApplicationData("CATALOG");
relatedEntity = (JDBCEntityBridge2)catalog.getEntityByEJBName(relatedEntityName);
if(relatedEntity == null)
{
throw new DeploymentException("Related entity not found: "
+
"entity="
+
entity.getEntityName()
+
", "
+
"cmrField="
+
getFieldName()
+
", " +
"relatedEntity=" + relatedEntityName
);
}
// Related CMR Field
JDBCCMRFieldBridge2[] cmrFields = (JDBCCMRFieldBridge2[])relatedEntity.getCMRFields();
for(int i = 0; i < cmrFields.length; ++i)
{
JDBCCMRFieldBridge2 cmrField = cmrFields[i];
if(metadata.getRelatedRole() == cmrField.getMetaData())
{
relatedCMRField = cmrField;
break;
}
}
// if we didn't find the related CMR field throw an exception with a detailed message
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);
}
// Related Container
relatedContainer = relatedEntity.getContainer();
//
// Initialize the key fields
//
if(metadata.getRelationMetaData().isTableMappingStyle())
{
// initialize relation table key fields
Collection tableKeys = metadata.getKeyFields();
List keyFieldsList = new ArrayList(tableKeys.size());
// first phase is to create fk fields
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 JDBCCMPFieldBridge2(manager, entity, cmpFieldMetaData, -1));
}
// second step is to order fk fields to match the order of pk fields
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 = (JDBCCMPFieldBridge2[])keyFieldsList.toArray(new JDBCCMPFieldBridge2[keyFieldsList.size()]);
}
else
{
initializeForeignKeyFields();
}
}
public void initLoader() throws DeploymentException
{
if(metadata.getRelationMetaData().isTableMappingStyle())
{
relationTable = relatedCMRField.getRelationTable();
loader = new RelationTableLoader();
}
else
{
if(foreignKeyFields != null)
{
loader = new ContextForeignKeyLoader();
}
else
{
loader = new ForeignKeyLoader();
}
}
}
public JDBCRelationshipRoleMetaData getMetaData()
{
return metadata;
}
public boolean removeRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
FieldState state = getFieldState(ctx);
return state.removeRelatedId(ctx, relatedId);
}
public boolean addRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
FieldState state = getFieldState(ctx);
return state.addRelatedId(ctx, relatedId);
}
public void remove(EntityEnterpriseContext ctx) throws RemoveException
{
if(metadata.getRelatedRole().isCascadeDelete())
{
FieldState state = getFieldState(ctx);
state.cascadeDelete(ctx);
}
else
{
destroyExistingRelationships(ctx);
}
}
public void destroyExistingRelationships(EntityEnterpriseContext ctx)
{
FieldState state = getFieldState(ctx);
state.destroyExistingRelationships(ctx);
}
public JDBCFieldBridge[] getTableKeyFields()
{
return tableKeyFields;
}
public JDBCEntityPersistenceStore getManager()
{
return manager;
}
public boolean hasForeignKey()
{
return foreignKeyFields != null;
}
public JDBCAbstractCMRFieldBridge getRelatedCMRField()
{
return this.relatedCMRField;
}
public JDBCFieldBridge[] getForeignKeyFields()
{
return foreignKeyFields;
}
public JDBCCMRFieldBridge2 getRelatedField()
{
return relatedCMRField;
}
public JDBCAbstractEntityBridge getEntity()
{
return entity;
}
public String getQualifiedTableName()
{
return relationTable.getTableName();
}
public String getTableName()
{
throw new UnsupportedOperationException();
}
// JDBCFieldBridge implementation
public JDBCType getJDBCType()
{
throw new UnsupportedOperationException();
}
public boolean isPrimaryKeyMember()
{
throw new UnsupportedOperationException();
}
public boolean isReadOnly()
{
throw new UnsupportedOperationException();
}
public boolean isReadTimedOut(EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public boolean isLoaded(EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public void initInstance(EntityEnterpriseContext ctx)
{
getFieldState(ctx).init();
}
public void resetPersistenceContext(EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public int setInstanceParameters(PreparedStatement ps, int parameterIndex, EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public Object getInstanceValue(EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public void setInstanceValue(EntityEnterpriseContext ctx, Object value)
{
throw new UnsupportedOperationException();
}
public int loadInstanceResults(ResultSet rs, int parameterIndex, EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public int loadArgumentResults(ResultSet rs, int parameterIndex, Object[] argumentRef)
{
throw new UnsupportedOperationException();
}
public boolean isDirty(EntityEnterpriseContext ctx)
{
return getFieldState(ctx).isModified();
}
public void setClean(EntityEnterpriseContext ctx)
{
throw new UnsupportedOperationException();
}
public boolean isCMPField()
{
return false;
}
// CMRFieldBridge implementation
public String getFieldName()
{
return metadata.getCMRFieldName();
}
public Object getValue(EntityEnterpriseContext ctx)
{
FieldState state = getFieldState(ctx);
return state.getValue(ctx);
}
public void setValue(EntityEnterpriseContext ctx, Object value)
{
FieldState state = getFieldState(ctx);
state.setValue(ctx, value);
state.cacheValue(ctx);
}
public boolean isSingleValued()
{
return metadata.getRelatedRole().isMultiplicityOne();
}
public EntityBridge getRelatedEntity()
{
return relatedEntity;
}
// Private
private void initializeForeignKeyFields() throws DeploymentException
{
Collection foreignKeys = metadata.getRelatedRole().getKeyFields();
// temporary map used later to write fk fields in special order
Map fkFieldsByRelatedPKFields = new HashMap();
for(Iterator i = foreignKeys.iterator(); i.hasNext();)
{
JDBCCMPFieldMetaData fkFieldMetaData = (JDBCCMPFieldMetaData)i.next();
JDBCCMPFieldBridge2 relatedPKField =
(JDBCCMPFieldBridge2)relatedEntity.getFieldByName(fkFieldMetaData.getFieldName());
// now determine whether the fk is mapped to a pk column
String fkColumnName = fkFieldMetaData.getColumnName();
JDBCCMPFieldBridge2 fkField = null;
// look among the CMP fields for the field with the same column name
JDBCCMPFieldBridge2[] tableFields = (JDBCCMPFieldBridge2[])entity.getTableFields();
for(int tableInd = 0; tableInd < tableFields.length && fkField == null; ++tableInd)
{
JDBCCMPFieldBridge2 cmpField = tableFields[tableInd];
if(fkColumnName.equals(cmpField.getColumnName()))
{
// construct the foreign key field
fkField = new JDBCCMPFieldBridge2(cmpField, relatedPKField);
/*
cmpField.getManager(), // this cmpField's manager
relatedPKField.getFieldName(),
relatedPKField.getFieldType(),
cmpField.getJDBCType(), // this cmpField's jdbc type
relatedPKField.isReadOnly(),
relatedPKField.getReadTimeOut(),
relatedPKField.getPrimaryKeyClass(),
relatedPKField.getPrimaryKeyField(),
cmpField, // CMP field I am mapped to
this,
fkColumnName
);
*/
}
}
// if the fk is not a part of pk then create a new field
if(fkField == null)
{
fkField = entity.addTableField(fkFieldMetaData);
}
fkFieldsByRelatedPKFields.put(relatedPKField, fkField); // temporary map
}
// Note: this important to order the foreign key fields so that their order matches
// the order of related entity's pk fields in case of complex primary keys.
// The order is important in fk-constraint generation and in SELECT when loading
if(fkFieldsByRelatedPKFields.size() > 0)
{
JDBCFieldBridge[] pkFields = relatedEntity.getPrimaryKeyFields();
List fkList = new ArrayList(pkFields.length);
List relatedPKList = new ArrayList(pkFields.length);
for(int i = 0; i < pkFields.length; ++i)
{
JDBCFieldBridge relatedPKField = pkFields[i];
JDBCFieldBridge fkField = (JDBCCMPFieldBridge2)fkFieldsByRelatedPKFields.remove(relatedPKField);
fkList.add(fkField);
relatedPKList.add(relatedPKField);
}
foreignKeyFields = (JDBCCMPFieldBridge2[])fkList.toArray(new JDBCCMPFieldBridge2[fkList.size()]);
relatedPKFields =
(JDBCCMPFieldBridge2[])relatedPKList.toArray(new JDBCCMPFieldBridge2[relatedPKList.size()]);
if(metadata.hasForeignKeyConstraint())
{
fkConstraint = entity.getTable().addFkConstraint(foreignKeyFields, relatedEntity.getTable());
}
}
else
{
foreignKeyFields = null;
relatedPKFields = null;
}
}
private FieldState getFieldState(EntityEnterpriseContext ctx)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
FieldState state = pctx.getCMRState(cmrIndex);
if(state == null)
{
if(isSingleValued())
{
state = new SingleValuedFieldState();
}
else
{
state = new CollectionValuedFieldState();
}
pctx.setCMRState(cmrIndex, state);
}
return state;
}
private void invokeRemoveRelatedId(Object myId, Object relatedId)
{
try
{
Transaction tx = getTransaction();
EntityCache instanceCache = (EntityCache)manager.getContainer().getInstanceCache();
/*
RelationInterceptor.RelationInvocation invocation =
new RelationInterceptor.RelationInvocation(RelationInterceptor.CMRMessage.REMOVE_RELATED_ID);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this, relatedId});
invocation.setTransaction(tx);
invocation.setPrincipal(SecurityAssociation.getPrincipal());
invocation.setCredential(SecurityAssociation.getCredential());
invocation.setType(InvocationType.LOCAL);
*/
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 invokeRemoveRelatedId()", e);
}
}
private void invokeAddRelatedId(Object myId, Object relatedId)
{
try
{
Transaction tx = getTransaction();
EntityCache instanceCache = (EntityCache)manager.getContainer().getInstanceCache();
/*
RelationInterceptor.RelationInvocation invocation =
new RelationInterceptor.RelationInvocation(RelationInterceptor.CMRMessage.ADD_RELATED_ID);
invocation.setId(instanceCache.createCacheKey(myId));
invocation.setArguments(new Object[]{this, relatedId});
invocation.setTransaction(tx);
invocation.setPrincipal(SecurityAssociation.getPrincipal());
invocation.setCredential(SecurityAssociation.getCredential());
invocation.setType(InvocationType.LOCAL);
*/
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 invokeAddRelatedId()", e);
}
}
private Transaction getTransaction() throws SystemException
{
return tm.getTransaction();
}
private RelationTable getRelationTable() throws DeploymentException
{
if(relationTable == null)
{
relationTable = manager.getSchema().createRelationTable(this, relatedCMRField);
}
return relationTable;
}
private Object getPrimaryKey(Object o)
{
if(o == null)
{
throw new IllegalArgumentException("This implementation does not support null members.");
}
if(!relatedEntity.getLocalInterface().isInstance(o))
{
throw new IllegalArgumentException("Argument must be of type " + entity.getLocalInterface().getName());
}
EJBLocalObject local = (EJBLocalObject)o;
try
{
return local.getPrimaryKey();
}
catch(NoSuchObjectLocalException e)
{
throw new IllegalArgumentException(e.getMessage());
}
}
// Inner
public class SingleValuedFieldState
implements FieldState
{
private boolean loaded;
private Object value;
private EJBLocalObject localObject;
private boolean modified;
public void init()
{
loaded = true;
}
public Object getValue(EntityEnterpriseContext ctx)
{
Object value = getLoadedValue(ctx);
if(value == null)
{
localObject = null;
}
else if(localObject == null)
{
localObject = relatedContainer.getLocalProxyFactory().getEntityEJBLocalObject(value);
}
return localObject;
}
public void setValue(EntityEnterpriseContext ctx, Object value)
{
if(value != null)
{
Object relatedId = getPrimaryKey(value);
addRelatedId(ctx, relatedId);
relatedCMRField.invokeAddRelatedId(relatedId, ctx.getId());
localObject = (EJBLocalObject)value;
}
else
{
destroyExistingRelationships(ctx);
}
}
public void cascadeDelete(EntityEnterpriseContext ctx) throws RemoveException
{
if(manager.registerCascadeDelete(ctx.getId(), ctx.getId()))
{
EJBLocalObject value = (EJBLocalObject)getValue(ctx);
if(value != null)
{
changeValue(null);
final Object relatedId = value.getPrimaryKey();
final JDBCStoreManager2 relatedManager = (JDBCStoreManager2)relatedEntity.getManager();
if(!relatedManager.isCascadeDeleted(relatedId))
{
value.remove();
}
}
manager.unregisterCascadeDelete(ctx.getId());
}
}
public void destroyExistingRelationships(EntityEnterpriseContext ctx)
{
Object value = getLoadedValue(ctx);
if(value != null)
{
removeRelatedId(ctx, value);
relatedCMRField.invokeRemoveRelatedId(value, ctx.getId());
}
}
public boolean removeRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
if(hasForeignKey())
{
getLoadedValue(ctx);
}
changeValue(null);
loader.removeRelatedId(ctx, relatedId);
cacheValue(ctx);
modified = true;
return true;
}
public boolean addRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
Object value = getLoadedValue(ctx);
if(value != null)
{
relatedCMRField.invokeRemoveRelatedId(value, ctx.getId());
}
changeValue(relatedId);
loader.addRelatedId(ctx, relatedId);
cacheValue(ctx);
modified = true;
return true;
}
public void addLoadedPk(Object pk)
{
if(loaded)
{
throw new IllegalStateException(entity.getEntityName()
+
"."
+
getFieldName()
+
" single-valued CMR field is already loaded. Check the database for consistancy. "
+ " current value=" + value + ", loaded value=" + pk
);
}
changeValue(pk);
}
public Object loadFromCache(Object value)
{
if(value != null)
{
changeValue(NULL_VALUE == value ? null : value);
}
return value;
}
public Object getCachedValue()
{
return value == null ? NULL_VALUE : value;
}
public void cacheValue(EntityEnterpriseContext ctx)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
pctx.cacheRelations(cmrIndex, this);
}
public boolean isModified()
{
return modified;
}
// Private
private void changeValue(Object newValue)
{
this.value = newValue;
this.localObject = null;
loaded = true;
}
private Object getLoadedValue(EntityEnterpriseContext ctx)
{
if(!loaded)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
pctx.loadCachedRelations(cmrIndex, this);
if(!loaded)
{
loader.load(ctx, this);
loaded = true;
cacheValue(ctx);
}
}
return value;
}
}
public class CollectionValuedFieldState
implements FieldState
{
private boolean loaded;
private Set value;
private CMRSet cmrSet;
private Set removedWhileNotLoaded;
private Set addedWhileNotLoaded;
private boolean modified;
public void init()
{
loaded = true;
value = new HashSet();
}
public Object getValue(EntityEnterpriseContext ctx)
{
if(cmrSet == null)
{
cmrSet = new CMRSet(ctx, this);
}
return cmrSet;
}
public void setValue(EntityEnterpriseContext ctx, Object value)
{
if(value == null)
{
throw new IllegalArgumentException("Can't set collection-valued CMR field to null: " +
entity.getEntityName() + "." + getFieldName()
);
}
destroyExistingRelationships(ctx);
Collection newValue = (Collection)value;
if(!newValue.isEmpty())
{
Set copy = new HashSet(newValue);
for(Iterator iter = copy.iterator(); iter.hasNext();)
{
Object relatedId = getPrimaryKey(iter.next());
addRelatedId(ctx, relatedId);
relatedCMRField.invokeAddRelatedId(relatedId, ctx.getId());
loader.addRelatedId(ctx, relatedId);
}
}
}
public void cascadeDelete(EntityEnterpriseContext ctx) throws RemoveException
{
Collection value = (Collection)getValue(ctx);
if(!value.isEmpty())
{
EJBLocalObject[] locals = (EJBLocalObject[])value.toArray();
for(int i = 0; i < locals.length; ++i)
{
locals[i].remove();
}
}
}
public void destroyExistingRelationships(EntityEnterpriseContext ctx)
{
Set value = getLoadedValue(ctx);
if(!value.isEmpty())
{
Object[] copy = value.toArray();
for(int i = 0; i < copy.length; ++i)
{
Object relatedId = copy[i];
removeRelatedId(ctx, relatedId);
relatedCMRField.invokeRemoveRelatedId(relatedId, ctx.getId());
loader.removeRelatedId(ctx, relatedId);
}
}
}
public boolean removeRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
boolean removed = false;
if(loaded)
{
Set value = getLoadedValue(ctx);
if(!value.isEmpty())
{
removed = value.remove(relatedId);
}
}
else
{
loadOnlyFromCache(ctx);
if(loaded)
{
Set value = getLoadedValue(ctx);
if(!value.isEmpty())
{
removed = value.remove(relatedId);
}
}
else
{
removed = removeWhileNotLoaded(relatedId);
}
}
modified = true;
if(removed)
{
((PersistentContext)ctx.getPersistenceContext()).setDirtyRelations();
}
return removed;
}
public boolean addRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
boolean added;
if(loaded)
{
Set value = getLoadedValue(ctx);
added = value.add(relatedId);
}
else
{
loadOnlyFromCache(ctx);
if(loaded)
{
Set value = getLoadedValue(ctx);
added = value.add(relatedId);
}
else
{
added = addWhileNotLoaded(relatedId);
}
}
modified = true;
if(added)
{
((PersistentContext)ctx.getPersistenceContext()).setDirtyRelations();
}
return added;
}
public void addLoadedPk(Object pk)
{
if(loaded)
{
throw new IllegalStateException(entity.getEntityName()
+
"."
+
getFieldName()
+
" collection-valued CMR field is already loaded. Check the database for consistancy. "
+ " current value=" + value + ", loaded value=" + pk
);
}
if(pk != null)
{
value.add(pk);
}
}
public Object loadFromCache(Object value)
{
if(value != null)
{
value = this.value = new HashSet((Set)value);
loaded = true;
}
return value;
}
public Object getCachedValue()
{
return value;
}
public void cacheValue(EntityEnterpriseContext ctx)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
pctx.cacheRelations(cmrIndex, this);
}
public boolean isModified()
{
return modified;
}
// Private
private Set getLoadedValue(EntityEnterpriseContext ctx)
{
if(!loaded)
{
loadOnlyFromCache(ctx);
if(!loaded)
{
if(value == null || value == Collections.EMPTY_SET)
{
value = new HashSet();
}
loader.load(ctx, this);
cacheValue(ctx);
loaded = true;
}
if(addedWhileNotLoaded != null)
{
value.addAll(addedWhileNotLoaded);
addedWhileNotLoaded = null;
}
if(removedWhileNotLoaded != null)
{
value.removeAll(removedWhileNotLoaded);
removedWhileNotLoaded = null;
}
}
return value;
}
private void loadOnlyFromCache(EntityEnterpriseContext ctx)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
if(pctx == null)
{
throw new EJBException("Persistence context is not available! Make sure the CMR collection is accessed in the transaction it was obtained.");
}
pctx.loadCachedRelations(cmrIndex, this);
}
private boolean removeWhileNotLoaded(Object relatedId)
{
boolean removed = false;
if(addedWhileNotLoaded != null)
{
removed = addedWhileNotLoaded.remove(relatedId);
}
if(!removed)
{
if(removedWhileNotLoaded == null)
{
removedWhileNotLoaded = new HashSet();
}
removed = removedWhileNotLoaded.add(relatedId);
}
if(log.isTraceEnabled() && removed)
{
log.trace("removed while not loaded: relatedId=" + relatedId);
}
return removed;
}
private boolean addWhileNotLoaded(Object relatedId)
{
boolean added = false;
if(removedWhileNotLoaded != null)
{
added = removedWhileNotLoaded.remove(relatedId);
}
if(!added)
{
if(addedWhileNotLoaded == null)
{
addedWhileNotLoaded = new HashSet();
}
added = addedWhileNotLoaded.add(relatedId);
}
if(log.isTraceEnabled() && added)
{
log.trace("added while not loaded: relatedId=" + relatedId);
}
return added;
}
}
public interface FieldState
extends Cache.CacheLoader
{
Object NULL_VALUE = new Object();
void init();
Object getValue(EntityEnterpriseContext ctx);
void cascadeDelete(EntityEnterpriseContext ctx) throws RemoveException;
void destroyExistingRelationships(EntityEnterpriseContext ctx);
void setValue(EntityEnterpriseContext ctx, Object value);
boolean removeRelatedId(EntityEnterpriseContext ctx, Object relatedId);
boolean addRelatedId(EntityEnterpriseContext ctx, Object value);
void addLoadedPk(Object pk);
void cacheValue(EntityEnterpriseContext ctx);
boolean isModified();
}
private class RelationTableLoader
implements CMRFieldLoader
{
private final String loadSql;
public RelationTableLoader()
{
StringBuffer sql = new StringBuffer();
sql.append("select ");
String relatedTable = relatedEntity.getQualifiedTableName();
String relationTable = metadata.getRelationMetaData().getDefaultTableName();
relatedEntity.getTable().appendColumnNames((JDBCCMPFieldBridge2[])relatedEntity.getTableFields(),
relatedTable,
sql
);
sql.append(" from ")
.append(relatedTable)
.append(" inner join ")
.append(relationTable)
.append(" on ");
JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[])relatedEntity.getPrimaryKeyFields();
for(int i = 0; i < pkFields.length; ++i)
{
if(i > 0)
{
sql.append(" and ");
}
sql.append(relatedTable).append('.').append(pkFields[i].getColumnName())
.append('=')
.append(relationTable).append('.').append(relatedCMRField.tableKeyFields[i].getColumnName());
}
/*
sql.append(" inner join ")
.append(myTable)
.append(" on ");
String myTable = entity.getQualifiedTableName();
pkFields = entity.getPrimaryKeyFields();
for(int i = 0; i < pkFields.length; ++i)
{
if(i > 0)
{
sql.append(", ");
}
sql.append(myTable).append('.').append(pkFields[i].getColumnName())
.append('=')
.append(relationTable).append('.').append(tableKeyFields[i].getColumnName());
}
*/
sql.append(" where ");
for(int i = 0; i < tableKeyFields.length; ++i)
{
if(i > 0)
{
sql.append(" and ");
}
sql.append(relationTable).append('.').append(tableKeyFields[i].getColumnName()).append("=?");
}
loadSql = sql.toString();
if(log.isTraceEnabled())
{
log.trace("load sql: " + loadSql);
}
}
public void load(EntityEnterpriseContext ctx, FieldState state)
{
Object value;
EntityTable relatedTable = relatedEntity.getTable();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
if(log.isDebugEnabled())
{
log.debug("executing: " + loadSql);
}
con = relatedTable.getDataSource().getConnection();
ps = con.prepareStatement(loadSql);
JDBCCMPFieldBridge2[] pkFields = (JDBCCMPFieldBridge2[])entity.getPrimaryKeyFields();
Object myPk = ctx.getId();
int paramInd = 1;
for(int i = 0; i < pkFields.length; ++i)
{
JDBCCMPFieldBridge2 pkField = pkFields[i];
Object fieldValue = pkField.getPrimaryKeyValue(myPk);
JDBCCMPFieldBridge2 relatedFkField = tableKeyFields[i];
relatedFkField.setArgumentParameters(ps, paramInd++, fieldValue);
}
rs = ps.executeQuery();
while(rs.next())
{
value = relatedTable.loadRow(rs, false);
state.addLoadedPk(value);
}
}
catch(SQLException e)
{
log.error("Failed to load related role: ejb-name="
+
entity.getEntityName() +
", cmr-field=" + getFieldName() + ": " + e.getMessage(), e
);
throw new EJBException("Failed to load related role: ejb-name="
+
entity.getEntityName() +
", cmr-field=" + getFieldName() + ": " + e.getMessage(), e
);
}
finally
{
JDBCUtil.safeClose(rs);
JDBCUtil.safeClose(ps);
JDBCUtil.safeClose(con);
}
}
public void removeRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
relationTable.removeRelation(JDBCCMRFieldBridge2.this, ctx.getId(), relatedCMRField, relatedId);
}
public void addRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
relationTable.addRelation(JDBCCMRFieldBridge2.this, ctx.getId(), relatedCMRField, relatedId);
}
}
private class ForeignKeyLoader
implements CMRFieldLoader
{
private final String loadSql;
public ForeignKeyLoader()
{
StringBuffer sql = new StringBuffer();
sql.append("select ");
relatedEntity.getTable().appendColumnNames((JDBCCMPFieldBridge2[])relatedEntity.getTableFields(), null, sql);
sql.append(" from ").append(relatedEntity.getQualifiedTableName()).append(" where ");
JDBCCMPFieldBridge2[] relatedFkFields = relatedCMRField.foreignKeyFields;
sql.append(relatedFkFields[0].getColumnName()).append("=?");
for(int i = 1; i < relatedFkFields.length; ++i)
{
JDBCCMPFieldBridge2 relatedFkField = relatedFkFields[i];
sql.append(" and ").append(relatedFkField.getColumnName()).append("=?");
}
loadSql = sql.toString();
if(log.isTraceEnabled())
{
log.trace("load sql: " + loadSql);
}
}
public void load(EntityEnterpriseContext ctx, FieldState state)
{
Object value;
EntityTable relatedTable = relatedEntity.getTable();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
if(log.isDebugEnabled())
{
log.debug("executing: " + loadSql);
}
con = relatedTable.getDataSource().getConnection();
ps = con.prepareStatement(loadSql);
JDBCCMPFieldBridge2[] relatedFkFields = relatedCMRField.foreignKeyFields;
JDBCCMPFieldBridge2[] myPkFields = relatedCMRField.relatedPKFields;
Object myPk = ctx.getId();
int paramInd = 1;
for(int i = 0; i < relatedFkFields.length; ++i)
{
JDBCCMPFieldBridge2 myPkField = myPkFields[i];
Object fieldValue = myPkField.getPrimaryKeyValue(myPk);
JDBCCMPFieldBridge2 relatedFkField = relatedFkFields[i];
relatedFkField.setArgumentParameters(ps, paramInd++, fieldValue);
}
rs = ps.executeQuery();
while(rs.next())
{
value = relatedTable.loadRow(rs, false);
state.addLoadedPk(value);
}
}
catch(SQLException e)
{
log.error("Failed to load related role: ejb-name="
+
entity.getEntityName() +
", cmr-field=" + getFieldName() + ": " + e.getMessage(), e
);
throw new EJBException("Failed to load related role: ejb-name="
+
entity.getEntityName() +
", cmr-field=" + getFieldName() + ": " + e.getMessage(), e
);
}
finally
{
JDBCUtil.safeClose(rs);
JDBCUtil.safeClose(ps);
JDBCUtil.safeClose(con);
}
}
public void removeRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
}
public void addRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
}
}
private class ContextForeignKeyLoader
implements CMRFieldLoader
{
public void load(EntityEnterpriseContext ctx, FieldState state)
{
Object relatedId = null;
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMPFieldBridge2 fkField = foreignKeyFields[i];
Object fkFieldValue = fkField.getValue(ctx);
if(fkFieldValue == null)
{
break;
}
JDBCCMPFieldBridge2 relatedPKField = relatedPKFields[i];
relatedId = relatedPKField.setPrimaryKeyValue(relatedId, fkFieldValue);
}
state.addLoadedPk(relatedId);
}
public void removeRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
for(int i = 0; i < foreignKeyFields.length; ++i)
{
foreignKeyFields[i].setValueInternal(ctx, null, fkConstraint == null);
}
if(fkConstraint != null)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
pctx.nullForeignKey(fkConstraint);
}
}
public void addRelatedId(EntityEnterpriseContext ctx, Object relatedId)
{
final boolean markDirty = relatedId != null || fkConstraint == null;
for(int i = 0; i < foreignKeyFields.length; ++i)
{
JDBCCMPFieldBridge2 relatedPKField = relatedPKFields[i];
Object fieldValue = relatedPKField.getPrimaryKeyValue(relatedId);
foreignKeyFields[i].setValueInternal(ctx, fieldValue, markDirty);
}
if(fkConstraint != null)
{
PersistentContext pctx = (PersistentContext)ctx.getPersistenceContext();
if(relatedId == null)
{
pctx.nullForeignKey(fkConstraint);
}
else
{
pctx.nonNullForeignKey(fkConstraint);
}
}
}
}
private interface CMRFieldLoader
{
void load(EntityEnterpriseContext ctx, FieldState state);
void removeRelatedId(EntityEnterpriseContext ctx, Object relatedId);
void addRelatedId(EntityEnterpriseContext ctx, Object relatedId);
}
private class CMRSet
implements Set
{
private final EntityEnterpriseContext ctx;
private final CollectionValuedFieldState state;
public CMRSet(EntityEnterpriseContext ctx, CollectionValuedFieldState state)
{
this.ctx = ctx;
this.state = state;
}
public int size()
{
return state.getLoadedValue(ctx).size();
}
public void clear()
{
destroyExistingRelationships(ctx);
}
public boolean isEmpty()
{
return size() == 0;
}
public boolean add(Object o)
{
Object relatedId = getPrimaryKey(o);
boolean modified = addRelatedId(ctx, relatedId);
if(modified)
{
relatedCMRField.invokeAddRelatedId(relatedId, ctx.getId());
loader.addRelatedId(ctx, relatedId);
}
return modified;
}
public boolean contains(Object o)
{
Object pk = getPrimaryKey(o);
return state.getLoadedValue(ctx).contains(pk);
}
public boolean remove(Object o)
{
Object relatedId = getPrimaryKey(o);
return removeById(relatedId);
}
public boolean addAll(Collection c)
{
if(c == null || c.isEmpty())
{
return false;
}
boolean modified = false;
Object[] copy = c.toArray();
for(int i = 0; i < copy.length; ++i)
{
// not modified || add()
modified = add(copy[i]) || modified;
}
return modified;
}
public boolean containsAll(Collection c)
{
if(c == null || c.isEmpty())
{
return true;
}
Set ids = argumentToIdSet(c);
return state.getLoadedValue(ctx).containsAll(ids);
}
public boolean removeAll(Collection c)
{
if(c == null || c.isEmpty())
{
return false;
}
boolean modified = false;
Object[] copy = c.toArray();
for(int i = 0; i < copy.length; ++i)
{
modified = remove(copy[i]) || modified;
}
return modified;
}
public boolean retainAll(Collection c)
{
Set value = state.getLoadedValue(ctx);
if(c == null || c.isEmpty())
{
if(value.isEmpty())
{
return false;
}
else
{
clear();
}
}
boolean modified = false;
Set idSet = argumentToIdSet(c);
Object[] valueCopy = value.toArray();
for(int i = 0; i < valueCopy.length; ++i)
{
Object id = valueCopy[i];
if(!idSet.contains(id))
{
removeById(id);
modified = true;
}
}
return modified;
}
public Iterator iterator()
{
return new Iterator()
{
// todo get rid of copying
private final Iterator idIter = new HashSet(state.getLoadedValue(ctx)).iterator();
private Object curId;
public void remove()
{
try
{
idIter.remove();
}
catch(ConcurrentModificationException e)
{
throw new IllegalStateException(e.getMessage());
}
removeById(curId);
}
public boolean hasNext()
{
try
{
return idIter.hasNext();
}
catch(ConcurrentModificationException e)
{
throw new IllegalStateException(e.getMessage());
}
}
public Object next()
{
try
{
curId = idIter.next();
}
catch(ConcurrentModificationException e)
{
throw new IllegalStateException(e.getMessage());
}
return relatedContainer.getLocalProxyFactory().getEntityEJBLocalObject(curId);
}
};
}
public Object[] toArray()
{
Set value = state.getLoadedValue(ctx);
Object[] result = (Object[])Array.newInstance(relatedEntity.getLocalInterface(), value.size());
LocalProxyFactory relatedPF = relatedContainer.getLocalProxyFactory();
int i = 0;
for(Iterator iter = value.iterator(); iter.hasNext();)
{
Object id = iter.next();
result[i++] = relatedPF.getEntityEJBLocalObject(id);
}
return result;
}
public Object[] toArray(Object a[])
{
Set value = state.getLoadedValue(ctx);
if(a == null || a.length < value.size())
{
a = (Object[])Array.newInstance(entity.getLocalInterface(), value.size());
}
LocalProxyFactory relatedPF = relatedContainer.getLocalProxyFactory();
int i = 0;
for(Iterator iter = value.iterator(); iter.hasNext();)
{
Object id = iter.next();
a[i++] = relatedPF.getEntityEJBLocalObject(id);
}
return a;
}
public String toString()
{
return state.getLoadedValue(ctx).toString();
}
// Private
private boolean removeById(Object relatedId)
{
boolean modified = removeRelatedId(ctx, relatedId);
if(modified)
{
relatedCMRField.invokeRemoveRelatedId(relatedId, ctx.getId());
loader.removeRelatedId(ctx, relatedId);
}
return modified;
}
private Set argumentToIdSet(Collection c)
{
Set ids = new HashSet();
for(Iterator iter = c.iterator(); iter.hasNext();)
{
Object pk = getPrimaryKey(iter.next());
ids.add(pk);
}
return ids;
}
}
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();
Principal p = null;
SecurityContext sc = getSecurityContext();
if(sc != null)
{
p = sc.getUtil().getUserPrincipal();
}
return p;
}
public Object getCredential()
{
//return SecurityAssociation.getCredential();
Object credential = null;
SecurityContext sc = getSecurityContext();
if(sc != null)
{
credential = sc.getUtil().getCredential();
}
return credential;
}
public SecurityContext getSecurityContext()
{
return SecurityContextAssociation.getSecurityContext();
}
};
SecurityActions PRIVILEGED = new SecurityActions()
{
private final PrivilegedAction getPrincipalAction = new PrivilegedAction()
{
public Object run()
{
//return SecurityAssociation.getPrincipal();
Principal p = null;
SecurityContext sc = getSecurityContext();
if(sc != null)
{
p = sc.getUtil().getUserPrincipal();
}
return p;
}
};
private final PrivilegedAction getCredentialAction = new PrivilegedAction()
{
public Object run()
{
//return SecurityAssociation.getCredential();
Object credential = null;
SecurityContext sc = getSecurityContext();
if(sc != null)
{
credential = sc.getUtil().getCredential();
}
return credential;
}
};
public Principal getPrincipal()
{
return (Principal)AccessController.doPrivileged(getPrincipalAction);
}
public Object getCredential()
{
return AccessController.doPrivileged(getCredentialAction);
}
public SecurityContext getSecurityContext()
{
return (SecurityContext) AccessController.doPrivileged(new PrivilegedAction(){
public Object run()
{
return SecurityContextAssociation.getSecurityContext();
}});
}
};
Principal getPrincipal();
Object getCredential();
SecurityContext getSecurityContext();
}
}