/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.cmp.jdbc;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.EntityContainer;
import org.jboss.logging.Logger;
import org.jboss.deployment.DeploymentException;
import org.jboss.security.SecurityAssociation;
import javax.ejb.RemoveException;
import javax.ejb.EJBObject;
import javax.ejb.EJBLocalObject;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.security.PrivilegedAction;
import java.security.Principal;
import java.security.AccessController;
import java.rmi.RemoteException;
/**
*
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version $Revision: 81030 $
*/
public abstract class CascadeDeleteStrategy
{
/**
* No cascade-delete strategy.
*/
public static final class NoneCascadeDeleteStrategy
extends CascadeDeleteStrategy
{
public NoneCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField) throws DeploymentException
{
super(cmrField);
}
public void removedIds(EntityEnterpriseContext ctx, Object[] oldRelationRefs, List ids)
{
cmrField.setInstanceValue(ctx, null);
}
public void cascadeDelete(EntityEnterpriseContext ctx, List oldValues) throws RemoveException, RemoteException
{
boolean trace = log.isTraceEnabled();
for(int i = 0; i < oldValues.size(); ++i)
{
Object oldValue = oldValues.get(i);
if(relatedManager.unscheduledCascadeDelete(oldValue))
{
if(trace)
{
log.trace("Removing " + oldValue);
}
invokeRemoveRelated(oldValue);
}
else if(trace)
{
log.trace(oldValue + " already removed");
}
}
}
}
/**
* Specification compliant cascade-delete strategy, i.e. one DELETE per child
*/
public static final class DefaultCascadeDeleteStrategy
extends CascadeDeleteStrategy
{
public DefaultCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField) throws DeploymentException
{
super(cmrField);
}
public void removedIds(EntityEnterpriseContext ctx, Object[] oldRelationRef, List ids)
{
cmrField.scheduleChildrenForCascadeDelete(ctx);
scheduleCascadeDelete(oldRelationRef, new ArrayList(ids));
cmrField.setInstanceValue(ctx, null);
}
public void cascadeDelete(EntityEnterpriseContext ctx, List oldValues) throws RemoveException, RemoteException
{
boolean trace = log.isTraceEnabled();
for(int i = 0; i < oldValues.size(); ++i)
{
Object oldValue = oldValues.get(i);
if(relatedManager.unscheduledCascadeDelete(oldValue))
{
if(trace)
{
log.trace("Removing " + oldValue);
}
invokeRemoveRelated(oldValue);
}
else if(trace)
{
log.trace(oldValue + " already removed");
}
}
}
}
/**
* Batch cascade-delete strategy. Deletes children with one statement of the form
* DELETE FROM RELATED_TABLE WHERE FOREIGN_KEY = ?
*/
public static final class BatchCascadeDeleteStrategy
extends CascadeDeleteStrategy
{
private final String batchCascadeDeleteSql;
public BatchCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField)
throws DeploymentException
{
super(cmrField);
if(cmrField.hasForeignKey())
{
throw new DeploymentException(
"Batch cascade-delete was setup for the role with a foreign key: relationship "
+ cmrField.getMetaData().getRelationMetaData().getRelationName()
+ ", role " + cmrField.getMetaData().getRelationshipRoleName()
+ ". Batch cascade-delete supported only for roles with no foreign keys."
);
}
StringBuffer buf = new StringBuffer(100);
buf.append("DELETE FROM ")
.append(cmrField.getRelatedJDBCEntity().getQualifiedTableName())
.append(" WHERE ");
SQLUtil.getWhereClause(cmrField.getRelatedCMRField().getForeignKeyFields(), buf);
batchCascadeDeleteSql = buf.toString();
log.debug(
cmrField.getMetaData().getRelationMetaData().getRelationName() + " batch cascade delete SQL: "
+ batchCascadeDeleteSql
);
}
public void removedIds(EntityEnterpriseContext ctx, Object[] oldRelationRefs, List ids)
{
cmrField.scheduleChildrenForBatchCascadeDelete(ctx);
scheduleCascadeDelete(oldRelationRefs, new ArrayList(ids));
}
public void cascadeDelete(EntityEnterpriseContext ctx, List oldValues) throws RemoveException, RemoteException
{
boolean didDelete = false;
boolean trace = log.isTraceEnabled();
for(int i = 0; i < oldValues.size(); ++i)
{
Object oldValue = oldValues.get(i);
if(relatedManager.unscheduledCascadeDelete(oldValue))
{
if(trace)
{
log.trace("Removing " + oldValue);
}
invokeRemoveRelated(oldValue);
didDelete = true;
}
else if(trace)
{
log.trace(oldValue + " already removed");
}
}
if(didDelete)
{
executeDeleteSQL(batchCascadeDeleteSql, ctx.getId());
}
}
}
public static CascadeDeleteStrategy getCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField)
throws DeploymentException
{
CascadeDeleteStrategy result;
JDBCRelationshipRoleMetaData relatedRole = cmrField.getMetaData().getRelatedRole();
if(relatedRole.isBatchCascadeDelete())
{
result = new BatchCascadeDeleteStrategy(cmrField);
}
else if(relatedRole.isCascadeDelete())
{
result = new DefaultCascadeDeleteStrategy(cmrField);
}
else
{
result = new NoneCascadeDeleteStrategy(cmrField);
}
return result;
}
protected final JDBCCMRFieldBridge cmrField;
protected final JDBCEntityBridge entity;
protected final JDBCStoreManager relatedManager;
protected final Logger log;
public CascadeDeleteStrategy(JDBCCMRFieldBridge cmrField) throws DeploymentException
{
this.cmrField = cmrField;
entity = (JDBCEntityBridge)cmrField.getEntity();
relatedManager = cmrField.getRelatedManager();
log = Logger.getLogger(getClass().getName() + "." + cmrField.getEntity().getEntityName());
}
public abstract void removedIds(EntityEnterpriseContext ctx, Object[] oldRelationRefs, List ids);
public abstract void cascadeDelete(EntityEnterpriseContext ctx, List oldValues) throws RemoveException,
RemoteException;
protected void scheduleCascadeDelete(Object[] oldRelationsRef, List values)
{
Map oldRelations = (Map)oldRelationsRef[0];
if(oldRelations == null)
{
oldRelations = new HashMap();
oldRelationsRef[0] = oldRelations;
}
oldRelations.put(cmrField, values);
relatedManager.scheduleCascadeDelete(values);
}
protected void executeDeleteSQL(String sql, Object key) throws RemoveException
{
Connection con = null;
PreparedStatement ps = null;
int rowsAffected = 0;
try
{
if(log.isDebugEnabled())
log.debug("Executing SQL: " + sql);
// get the connection
con = entity.getDataSource().getConnection();
ps = con.prepareStatement(sql);
// set the parameters
entity.setPrimaryKeyParameters(ps, 1, key);
// execute statement
rowsAffected = ps.executeUpdate();
}
catch(Exception e)
{
log.error("Could not remove " + key, e);
throw new RemoveException("Could not remove " + key);
}
finally
{
JDBCUtil.safeClose(ps);
JDBCUtil.safeClose(con);
}
// check results
if(rowsAffected == 0)
{
log.error("Could not remove entity " + key);
throw new RemoveException("Could not remove entity");
}
if(log.isDebugEnabled())
log.debug("Remove: Rows affected = " + rowsAffected);
}
public void invokeRemoveRelated(Object relatedId) throws RemoveException, RemoteException
{
EntityContainer container = relatedManager.getContainer();
/*
try
{
EntityCache instanceCache = (EntityCache) container.getInstanceCache();
SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
org.jboss.invocation.Invocation invocation = new org.jboss.invocation.Invocation();
invocation.setId(instanceCache.createCacheKey(relatedId));
invocation.setArguments(new Object[]{});
invocation.setTransaction(container.getTransactionManager().getTransaction());
invocation.setPrincipal(actions.getPrincipal());
invocation.setCredential(actions.getCredential());
invocation.setType(invocationType);
invocation.setMethod(removeMethod);
container.invoke(invocation);
}
catch(EJBException e)
{
throw e;
}
catch(Exception e)
{
throw new EJBException("Error in remove instance", e);
}
*/
/**
* Have to remove through EJB[Local}Object interface since the proxy contains the 'removed' flag
* to be set on removal.
*/
if(container.getLocalProxyFactory() != null)
{
final EJBLocalObject ejbObject = container.getLocalProxyFactory().getEntityEJBLocalObject(relatedId);
ejbObject.remove();
}
else
{
final EJBObject ejbObject = (EJBObject)container.getProxyFactory().getEntityEJBObject(relatedId);
ejbObject.remove();
}
}
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();
}
}