001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.cmp.jdbc;
023:
024: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
025: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
026: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationshipRoleMetaData;
027: import org.jboss.ejb.EntityEnterpriseContext;
028: import org.jboss.ejb.EntityContainer;
029: import org.jboss.logging.Logger;
030: import org.jboss.deployment.DeploymentException;
031: import org.jboss.security.SecurityAssociation;
032:
033: import javax.ejb.RemoveException;
034: import javax.ejb.EJBObject;
035: import javax.ejb.EJBLocalObject;
036: import java.util.List;
037: import java.util.ArrayList;
038: import java.util.Map;
039: import java.util.HashMap;
040: import java.sql.Connection;
041: import java.sql.PreparedStatement;
042: import java.security.PrivilegedAction;
043: import java.security.Principal;
044: import java.security.AccessController;
045: import java.rmi.RemoteException;
046:
047: /**
048: *
049: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
050: * @version $Revision: 57209 $
051: */
052: public abstract class CascadeDeleteStrategy {
053: /**
054: * No cascade-delete strategy.
055: */
056: public static final class NoneCascadeDeleteStrategy extends
057: CascadeDeleteStrategy {
058: public NoneCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField)
059: throws DeploymentException {
060: super (cmrField);
061: }
062:
063: public void removedIds(EntityEnterpriseContext ctx,
064: Object[] oldRelationRefs, List ids) {
065: cmrField.setInstanceValue(ctx, null);
066: }
067:
068: public void cascadeDelete(EntityEnterpriseContext ctx,
069: List oldValues) throws RemoveException, RemoteException {
070: boolean trace = log.isTraceEnabled();
071: for (int i = 0; i < oldValues.size(); ++i) {
072: Object oldValue = oldValues.get(i);
073: if (relatedManager.unscheduledCascadeDelete(oldValue)) {
074: if (trace) {
075: log.trace("Removing " + oldValue);
076: }
077:
078: invokeRemoveRelated(oldValue);
079: } else if (trace) {
080: log.trace(oldValue + " already removed");
081: }
082: }
083: }
084: }
085:
086: /**
087: * Specification compliant cascade-delete strategy, i.e. one DELETE per child
088: */
089: public static final class DefaultCascadeDeleteStrategy extends
090: CascadeDeleteStrategy {
091: public DefaultCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField)
092: throws DeploymentException {
093: super (cmrField);
094: }
095:
096: public void removedIds(EntityEnterpriseContext ctx,
097: Object[] oldRelationRef, List ids) {
098: cmrField.scheduleChildrenForCascadeDelete(ctx);
099: scheduleCascadeDelete(oldRelationRef, new ArrayList(ids));
100: cmrField.setInstanceValue(ctx, null);
101: }
102:
103: public void cascadeDelete(EntityEnterpriseContext ctx,
104: List oldValues) throws RemoveException, RemoteException {
105: boolean trace = log.isTraceEnabled();
106: for (int i = 0; i < oldValues.size(); ++i) {
107: Object oldValue = oldValues.get(i);
108: if (relatedManager.unscheduledCascadeDelete(oldValue)) {
109: if (trace) {
110: log.trace("Removing " + oldValue);
111: }
112: invokeRemoveRelated(oldValue);
113: } else if (trace) {
114: log.trace(oldValue + " already removed");
115: }
116: }
117: }
118: }
119:
120: /**
121: * Batch cascade-delete strategy. Deletes children with one statement of the form
122: * DELETE FROM RELATED_TABLE WHERE FOREIGN_KEY = ?
123: */
124: public static final class BatchCascadeDeleteStrategy extends
125: CascadeDeleteStrategy {
126: private final String batchCascadeDeleteSql;
127:
128: public BatchCascadeDeleteStrategy(JDBCCMRFieldBridge cmrField)
129: throws DeploymentException {
130: super (cmrField);
131:
132: if (cmrField.hasForeignKey()) {
133: throw new DeploymentException(
134: "Batch cascade-delete was setup for the role with a foreign key: relationship "
135: + cmrField.getMetaData()
136: .getRelationMetaData()
137: .getRelationName()
138: + ", role "
139: + cmrField.getMetaData()
140: .getRelationshipRoleName()
141: + ". Batch cascade-delete supported only for roles with no foreign keys.");
142: }
143:
144: StringBuffer buf = new StringBuffer(100);
145: buf.append("DELETE FROM ").append(
146: cmrField.getRelatedJDBCEntity()
147: .getQualifiedTableName()).append(" WHERE ");
148: SQLUtil.getWhereClause(cmrField.getRelatedCMRField()
149: .getForeignKeyFields(), buf);
150: batchCascadeDeleteSql = buf.toString();
151:
152: log.debug(cmrField.getMetaData().getRelationMetaData()
153: .getRelationName()
154: + " batch cascade delete SQL: "
155: + batchCascadeDeleteSql);
156: }
157:
158: public void removedIds(EntityEnterpriseContext ctx,
159: Object[] oldRelationRefs, List ids) {
160: cmrField.scheduleChildrenForBatchCascadeDelete(ctx);
161: scheduleCascadeDelete(oldRelationRefs, new ArrayList(ids));
162: }
163:
164: public void cascadeDelete(EntityEnterpriseContext ctx,
165: List oldValues) throws RemoveException, RemoteException {
166: boolean didDelete = false;
167: boolean trace = log.isTraceEnabled();
168: for (int i = 0; i < oldValues.size(); ++i) {
169: Object oldValue = oldValues.get(i);
170: if (relatedManager.unscheduledCascadeDelete(oldValue)) {
171: if (trace) {
172: log.trace("Removing " + oldValue);
173: }
174: invokeRemoveRelated(oldValue);
175: didDelete = true;
176: } else if (trace) {
177: log.trace(oldValue + " already removed");
178: }
179: }
180:
181: if (didDelete) {
182: executeDeleteSQL(batchCascadeDeleteSql, ctx.getId());
183: }
184: }
185: }
186:
187: public static CascadeDeleteStrategy getCascadeDeleteStrategy(
188: JDBCCMRFieldBridge cmrField) throws DeploymentException {
189: CascadeDeleteStrategy result;
190: JDBCRelationshipRoleMetaData relatedRole = cmrField
191: .getMetaData().getRelatedRole();
192: if (relatedRole.isBatchCascadeDelete()) {
193: result = new BatchCascadeDeleteStrategy(cmrField);
194: } else if (relatedRole.isCascadeDelete()) {
195: result = new DefaultCascadeDeleteStrategy(cmrField);
196: } else {
197: result = new NoneCascadeDeleteStrategy(cmrField);
198: }
199: return result;
200: }
201:
202: protected final JDBCCMRFieldBridge cmrField;
203: protected final JDBCEntityBridge entity;
204: protected final JDBCStoreManager relatedManager;
205: protected final Logger log;
206:
207: public CascadeDeleteStrategy(JDBCCMRFieldBridge cmrField)
208: throws DeploymentException {
209: this .cmrField = cmrField;
210: entity = (JDBCEntityBridge) cmrField.getEntity();
211: relatedManager = cmrField.getRelatedManager();
212:
213: log = Logger.getLogger(getClass().getName() + "."
214: + cmrField.getEntity().getEntityName());
215: }
216:
217: public abstract void removedIds(EntityEnterpriseContext ctx,
218: Object[] oldRelationRefs, List ids);
219:
220: public abstract void cascadeDelete(EntityEnterpriseContext ctx,
221: List oldValues) throws RemoveException, RemoteException;
222:
223: protected void scheduleCascadeDelete(Object[] oldRelationsRef,
224: List values) {
225: Map oldRelations = (Map) oldRelationsRef[0];
226: if (oldRelations == null) {
227: oldRelations = new HashMap();
228: oldRelationsRef[0] = oldRelations;
229: }
230: oldRelations.put(cmrField, values);
231: relatedManager.scheduleCascadeDelete(values);
232: }
233:
234: protected void executeDeleteSQL(String sql, Object key)
235: throws RemoveException {
236: Connection con = null;
237: PreparedStatement ps = null;
238: int rowsAffected = 0;
239: try {
240: if (log.isDebugEnabled())
241: log.debug("Executing SQL: " + sql);
242:
243: // get the connection
244: con = entity.getDataSource().getConnection();
245: ps = con.prepareStatement(sql);
246:
247: // set the parameters
248: entity.setPrimaryKeyParameters(ps, 1, key);
249:
250: // execute statement
251: rowsAffected = ps.executeUpdate();
252: } catch (Exception e) {
253: log.error("Could not remove " + key, e);
254: throw new RemoveException("Could not remove " + key);
255: } finally {
256: JDBCUtil.safeClose(ps);
257: JDBCUtil.safeClose(con);
258: }
259:
260: // check results
261: if (rowsAffected == 0) {
262: log.error("Could not remove entity " + key);
263: throw new RemoveException("Could not remove entity");
264: }
265:
266: if (log.isDebugEnabled())
267: log.debug("Remove: Rows affected = " + rowsAffected);
268: }
269:
270: public void invokeRemoveRelated(Object relatedId)
271: throws RemoveException, RemoteException {
272: EntityContainer container = relatedManager.getContainer();
273:
274: /*
275: try
276: {
277: EntityCache instanceCache = (EntityCache) container.getInstanceCache();
278: SecurityActions actions = SecurityActions.UTIL.getSecurityActions();
279:
280: org.jboss.invocation.Invocation invocation = new org.jboss.invocation.Invocation();
281: invocation.setId(instanceCache.createCacheKey(relatedId));
282: invocation.setArguments(new Object[]{});
283: invocation.setTransaction(container.getTransactionManager().getTransaction());
284: invocation.setPrincipal(actions.getPrincipal());
285: invocation.setCredential(actions.getCredential());
286: invocation.setType(invocationType);
287: invocation.setMethod(removeMethod);
288:
289: container.invoke(invocation);
290: }
291: catch(EJBException e)
292: {
293: throw e;
294: }
295: catch(Exception e)
296: {
297: throw new EJBException("Error in remove instance", e);
298: }
299: */
300:
301: /**
302: * Have to remove through EJB[Local}Object interface since the proxy contains the 'removed' flag
303: * to be set on removal.
304: */
305: if (container.getLocalProxyFactory() != null) {
306: final EJBLocalObject ejbObject = container
307: .getLocalProxyFactory().getEntityEJBLocalObject(
308: relatedId);
309: ejbObject.remove();
310: } else {
311: final EJBObject ejbObject = (EJBObject) container
312: .getProxyFactory().getEntityEJBObject(relatedId);
313: ejbObject.remove();
314: }
315: }
316:
317: interface SecurityActions {
318: class UTIL {
319: static SecurityActions getSecurityActions() {
320: return System.getSecurityManager() == null ? NON_PRIVILEGED
321: : PRIVILEGED;
322: }
323: }
324:
325: SecurityActions NON_PRIVILEGED = new SecurityActions() {
326: public Principal getPrincipal() {
327: return SecurityAssociation.getPrincipal();
328: }
329:
330: public Object getCredential() {
331: return SecurityAssociation.getCredential();
332: }
333: };
334:
335: SecurityActions PRIVILEGED = new SecurityActions() {
336: private final PrivilegedAction getPrincipalAction = new PrivilegedAction() {
337: public Object run() {
338: return SecurityAssociation.getPrincipal();
339: }
340: };
341:
342: private final PrivilegedAction getCredentialAction = new PrivilegedAction() {
343: public Object run() {
344: return SecurityAssociation.getCredential();
345: }
346: };
347:
348: public Principal getPrincipal() {
349: return (Principal) AccessController
350: .doPrivileged(getPrincipalAction);
351: }
352:
353: public Object getCredential() {
354: return AccessController
355: .doPrivileged(getCredentialAction);
356: }
357: };
358:
359: Principal getPrincipal();
360:
361: Object getCredential();
362: }
363: }
|