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