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;
023:
024: import org.jboss.logging.Logger;
025: import org.jboss.tm.TransactionLocal;
026:
027: import javax.ejb.EJBException;
028: import javax.transaction.RollbackException;
029: import javax.transaction.Status;
030: import javax.transaction.Synchronization;
031: import javax.transaction.SystemException;
032: import javax.transaction.Transaction;
033: import java.util.ArrayList;
034: import java.util.List;
035:
036: /**
037: * This class provides a way to find out what entities are contained in
038: * what transaction. It is used, to find which entities to call ejbStore()
039: * on when a ejbFind() method is called within a transaction. EJB 2.0- 9.6.4
040: * also, it is used to synchronize on a remove.
041: * Used in EntitySynchronizationInterceptor, EntityContainer
042: *
043: * Entities are stored in an ArrayList to ensure specific ordering.
044: *
045: * @author <a href="bill@burkecentral.com">Bill Burke</a>
046: * @author <a href="alex@jboss.org">Alexey Loubyansky</a>
047: * @version $Revision: 62278 $
048: */
049: public class GlobalTxEntityMap {
050: private static final Logger log = Logger
051: .getLogger(GlobalTxEntityMap.class);
052:
053: private final TransactionLocal txSynch = new TransactionLocal();
054:
055: /**
056: * An instance can be in one of the three states:
057: * <ul>
058: * <li>not associated with the tx and, hence, does not need to be synchronized</li>
059: * <li>associated with the tx and needs to be synchronized</li>
060: * <li>associated with the tx but does not need to be synchronized</li>
061: * </ul>
062: * Implementations of TxAssociation implement these states.
063: */
064: public static interface TxAssociation {
065: /**
066: * Schedules the instance for synchronization. The instance might or might not be associated with the tx.
067: *
068: * @param tx the transaction the instance should be associated with if not yet associated
069: * @param instance the instance to be scheduled for synchronization
070: * @throws SystemException
071: * @throws RollbackException
072: */
073: void scheduleSync(Transaction tx,
074: EntityEnterpriseContext instance)
075: throws SystemException, RollbackException;
076:
077: /**
078: * Synchronizes the instance if it is needed.
079: * @param thread current thread
080: * @param tx current transaction
081: * @param instance the instance to be synchronized
082: * @throws Exception thrown if synchronization failed
083: */
084: void synchronize(Thread thread, Transaction tx,
085: EntityEnterpriseContext instance) throws Exception;
086:
087: /**
088: * Invokes ejbStore if needed
089: * @param thread current thread
090: * @param instance the instance to be synchronized
091: * @throws Exception thrown if synchronization failed
092: */
093: void invokeEjbStore(Thread thread,
094: EntityEnterpriseContext instance) throws Exception;
095: }
096:
097: public static final TxAssociation NONE = new TxAssociation() {
098: public void scheduleSync(Transaction tx,
099: EntityEnterpriseContext instance)
100: throws SystemException, RollbackException {
101: EntityContainer.getGlobalTxEntityMap().associate(tx,
102: instance);
103: instance.setTxAssociation(SYNC_SCHEDULED);
104: }
105:
106: public void synchronize(Thread thread, Transaction tx,
107: EntityEnterpriseContext instance) {
108: throw new UnsupportedOperationException();
109: }
110:
111: public void invokeEjbStore(Thread thread,
112: EntityEnterpriseContext instance) {
113: throw new UnsupportedOperationException();
114: }
115: };
116:
117: public static final TxAssociation SYNC_SCHEDULED = new TxAssociation() {
118: public void scheduleSync(Transaction tx,
119: EntityEnterpriseContext instance) {
120: }
121:
122: public void invokeEjbStore(Thread thread,
123: EntityEnterpriseContext instance) throws Exception {
124: if (instance.getId() != null) {
125: EntityContainer container = (EntityContainer) instance
126: .getContainer();
127: // set the context class loader before calling the store method
128: SecurityActions.setContextClassLoader(thread, container
129: .getClassLoader());
130:
131: // store it
132: container.invokeEjbStore(instance);
133: }
134: }
135:
136: public void synchronize(Thread thread, Transaction tx,
137: EntityEnterpriseContext instance) throws Exception {
138: // only synchronize if the id is not null. A null id means
139: // that the entity has been removed.
140: if (instance.getId() != null) {
141: EntityContainer container = (EntityContainer) instance
142: .getContainer();
143:
144: // set the context class loader before calling the store method
145: SecurityActions.setContextClassLoader(thread, container
146: .getClassLoader());
147:
148: // store it
149: container.storeEntity(instance);
150:
151: instance.setTxAssociation(SYNCHRONIZED);
152: }
153: }
154: };
155:
156: public static final TxAssociation SYNCHRONIZED = new TxAssociation() {
157: public void scheduleSync(Transaction tx,
158: EntityEnterpriseContext instance) {
159: instance.setTxAssociation(SYNC_SCHEDULED);
160: }
161:
162: public void invokeEjbStore(Thread thread,
163: EntityEnterpriseContext instance) {
164: }
165:
166: public void synchronize(Thread thread, Transaction tx,
167: EntityEnterpriseContext instance) {
168: }
169: };
170:
171: public static final TxAssociation PREVENT_SYNC = new TxAssociation() {
172: public void scheduleSync(Transaction tx,
173: EntityEnterpriseContext instance) {
174: }
175:
176: public void synchronize(Thread thread, Transaction tx,
177: EntityEnterpriseContext instance) throws Exception {
178: EntityContainer container = (EntityContainer) instance
179: .getContainer();
180: if (container.getPersistenceManager().isStoreRequired(
181: instance)) {
182: throw new EJBException(
183: "The instance of "
184: + container.getBeanMetaData()
185: .getEjbName()
186: + " with pk="
187: + instance.getId()
188: + " was not stored to prevent potential inconsistency of data in the database:"
189: + " the instance was evicted from the cache during the transaction"
190: + " and the database was possibly updated by another process.");
191: }
192: }
193:
194: public void invokeEjbStore(Thread thread,
195: EntityEnterpriseContext instance) throws Exception {
196: GlobalTxEntityMap.SYNC_SCHEDULED.invokeEjbStore(thread,
197: instance);
198: }
199: };
200:
201: /**
202: * Used for instances in the create phase,
203: * i.e. before the ejbCreate and until after the ejbPostCreate returns
204: */
205: public static final TxAssociation NOT_READY = new TxAssociation() {
206: public void scheduleSync(Transaction tx,
207: EntityEnterpriseContext instance) {
208: }
209:
210: public void synchronize(Thread thread, Transaction tx,
211: EntityEnterpriseContext instance) throws Exception {
212: }
213:
214: public void invokeEjbStore(Thread thread,
215: EntityEnterpriseContext instance) throws Exception {
216: }
217: };
218:
219: /**
220: * sync all EntityEnterpriseContext that are involved (and changed)
221: * within a transaction.
222: */
223: public void synchronizeEntities(Transaction tx) {
224: GlobalTxSynchronization globalSync = (GlobalTxSynchronization) txSynch
225: .get(tx);
226: if (globalSync != null) {
227: globalSync.synchronize();
228: }
229: }
230:
231: /**
232: * associate instance with transaction
233: */
234: private void associate(Transaction tx,
235: EntityEnterpriseContext entity) throws RollbackException,
236: SystemException {
237: GlobalTxSynchronization globalSync = (GlobalTxSynchronization) txSynch
238: .get(tx);
239: if (globalSync == null) {
240: globalSync = new GlobalTxSynchronization(tx);
241: txSynch.set(tx, globalSync);
242: tx.registerSynchronization(globalSync);
243: }
244:
245: //There should be only one thread associated with this tx at a time.
246: //Therefore we should not need to synchronize on entityFifoList to ensure exclusive
247: //access. entityFifoList is correct since it was obtained in a synch block.
248:
249: globalSync.associate(entity);
250: }
251:
252: // Inner
253:
254: /**
255: * A list of instances associated with the transaction.
256: */
257: private class GlobalTxSynchronization implements Synchronization {
258: private Transaction tx;
259: private List instances = new ArrayList();
260: private boolean synchronizing;
261:
262: public GlobalTxSynchronization(Transaction tx) {
263: this .tx = tx;
264: }
265:
266: public void associate(EntityEnterpriseContext ctx) {
267: instances.add(ctx);
268: }
269:
270: public void synchronize() {
271: if (synchronizing || instances.isEmpty()) {
272: return;
273: }
274:
275: synchronizing = true;
276:
277: // This is an independent point of entry. We need to make sure the
278: // thread is associated with the right context class loader
279: Thread currentThread = Thread.currentThread();
280: ClassLoader oldCl = SecurityActions.getContextClassLoader();
281:
282: EntityEnterpriseContext instance = null;
283: try {
284: for (int i = 0; i < instances.size(); i++) {
285: // any one can mark the tx rollback at any time so check
286: // before continuing to the next store
287: if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
288: return;
289: }
290:
291: instance = (EntityEnterpriseContext) instances
292: .get(i);
293: instance.getTxAssociation().invokeEjbStore(
294: currentThread, instance);
295: }
296:
297: for (int i = 0; i < instances.size(); i++) {
298: // any one can mark the tx rollback at any time so check
299: // before continuing to the next store
300: if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
301: return;
302: }
303:
304: // read-only instances will never get into this list.
305: instance = (EntityEnterpriseContext) instances
306: .get(i);
307: instance.getTxAssociation().synchronize(
308: currentThread, tx, instance);
309: }
310: } catch (Exception causeByException) {
311: // EJB 1.1 section 12.3.2 and EJB 2 section 18.3.3
312: // exception during store must log exception, mark tx for
313: // rollback and throw a TransactionRolledback[Local]Exception
314: // if using caller's transaction. All of this is handled by
315: // the AbstractTxInterceptor and LogInterceptor.
316: //
317: // All we need to do here is mark the transaction for rollback
318: // and rethrow the causeByException. The caller will handle logging
319: // and wraping with TransactionRolledback[Local]Exception.
320: try {
321: tx.setRollbackOnly();
322: } catch (Exception e) {
323: log.warn("Exception while trying to rollback tx: "
324: + tx, e);
325: }
326:
327: // Rethrow cause by exception
328: if (causeByException instanceof EJBException) {
329: throw (EJBException) causeByException;
330: }
331: throw new EJBException(
332: "Exception in store of entity:"
333: + ((instance == null || instance
334: .getId() == null) ? "<null>"
335: : instance.getId().toString()),
336: causeByException);
337: } finally {
338: SecurityActions.setContextClassLoader(oldCl);
339: synchronizing = false;
340: }
341: }
342:
343: // Synchronization implementation -----------------------------
344:
345: public void beforeCompletion() {
346: if (log.isTraceEnabled()) {
347: log.trace("beforeCompletion called for tx " + tx);
348: }
349:
350: // let the runtime exceptions fall out, so the committer can determine
351: // the root cause of a rollback
352: synchronize();
353: }
354:
355: public void afterCompletion(int status) {
356: //no-op
357: }
358: }
359: }
|