View Javadoc

1   /*
2    * $Id: AbstractResourceManager.java 21383 2011-03-01 18:00:35Z aperepel $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.util.xa;
12  
13  import org.mule.config.i18n.CoreMessages;
14  
15  import java.util.ArrayList;
16  import java.util.Collection;
17  import java.util.Collections;
18  import java.util.Iterator;
19  
20  import javax.transaction.Status;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  /**
26   * This code is based on code coming from the <a
27   * href="http://jakarta.apache.org/commons/transaction/">commons-transaction</a>
28   * project.
29   */
30  public abstract class AbstractResourceManager
31  {
32  
33      /**
34       * Shutdown mode: Wait for all transactions to complete
35       */
36      public static final int SHUTDOWN_MODE_NORMAL = 0;
37  
38      /**
39       * Shutdown mode: Try to roll back all active transactions
40       */
41      public static final int SHUTDOWN_MODE_ROLLBACK = 1;
42  
43      /**
44       * Shutdown mode: Try to stop active transaction <em>NOW</em>, do no rollbacks
45       */
46      public static final int SHUTDOWN_MODE_KILL = 2;
47  
48      protected static final int OPERATION_MODE_STOPPED = 0;
49      protected static final int OPERATION_MODE_STOPPING = 1;
50      protected static final int OPERATION_MODE_STARTED = 2;
51      protected static final int OPERATION_MODE_STARTING = 3;
52      protected static final int OPERATION_MODE_RECOVERING = 4;
53  
54      protected static final int DEFAULT_TIMEOUT_MSECS = 5000;
55      protected static final int DEFAULT_COMMIT_TIMEOUT_FACTOR = 2;
56  
57      protected Collection globalTransactions = Collections.synchronizedCollection(new ArrayList());
58      protected int operationMode = OPERATION_MODE_STOPPED;
59      protected long defaultTimeout = DEFAULT_TIMEOUT_MSECS;
60  
61      protected Log logger = LogFactory.getLog(getClass());
62  
63      protected boolean dirty = false;
64  
65      public synchronized void start() throws ResourceManagerSystemException
66      {
67          logger.info("Starting ResourceManager");
68          operationMode = OPERATION_MODE_STARTING;
69          // TODO: recover and sync
70          doStart();
71          recover();
72          // sync();
73          operationMode = OPERATION_MODE_STARTED;
74          if (dirty)
75          {
76              logger
77                  .warn("Started ResourceManager, but in dirty mode only (Recovery of pending transactions failed)");
78          }
79          else
80          {
81              logger.info("Started ResourceManager");
82          }
83      }
84  
85      protected void doStart() throws ResourceManagerSystemException
86      {
87          // template method
88      }
89  
90      protected void recover() throws ResourceManagerSystemException
91      {
92          // nothing to do (yet?)
93      }
94  
95      public synchronized void stop() throws ResourceManagerSystemException
96      {
97          stop(SHUTDOWN_MODE_NORMAL);
98      }
99  
100     public synchronized boolean stop(int mode) throws ResourceManagerSystemException
101     {
102         return stop(mode, getDefaultTransactionTimeout() * DEFAULT_COMMIT_TIMEOUT_FACTOR);
103     }
104 
105     public synchronized boolean stop(int mode, long timeOut) throws ResourceManagerSystemException
106     {
107         logger.info("Stopping ResourceManager");
108         operationMode = OPERATION_MODE_STOPPING;
109         // TODO: sync
110         // sync();
111         boolean success = shutdown(mode, timeOut);
112         // TODO: release
113         // releaseGlobalOpenResources();
114         if (success)
115         {
116             operationMode = OPERATION_MODE_STOPPED;
117             logger.info("Stopped ResourceManager");
118         }
119         else
120         {
121             logger.warn("Failed to stop ResourceManager");
122         }
123 
124         return success;
125     }
126 
127     protected boolean shutdown(int mode, long timeoutMSecs)
128     {
129         switch (mode)
130         {
131             case SHUTDOWN_MODE_NORMAL :
132                 return waitForAllTxToStop(timeoutMSecs);
133             case SHUTDOWN_MODE_ROLLBACK :
134                 throw new UnsupportedOperationException();
135                 // return rollBackOrForward();
136             case SHUTDOWN_MODE_KILL :
137                 return true;
138             default :
139                 return false;
140         }
141     }
142 
143     /**
144      * Gets the default transaction timeout in <em>milliseconds</em>.
145      */
146     public long getDefaultTransactionTimeout()
147     {
148         return defaultTimeout;
149     }
150 
151     /**
152      * Sets the default transaction timeout.
153      * 
154      * @param timeout timeout in <em>milliseconds</em>
155      */
156     public void setDefaultTransactionTimeout(long timeout)
157     {
158         defaultTimeout = timeout;
159     }
160 
161     /**
162      * Starts a new transaction and associates it with the current thread. All
163      * subsequent changes in the same thread made to the map are invisible from other
164      * threads until {@link #commitTransaction(AbstractTransactionContext)} is called. Use
165      * {@link #rollbackTransaction(AbstractTransactionContext)} to discard your changes. After 
166      * calling either method there will be no transaction associated to the current thread any
167      * longer. <br>
168      * <br>
169      * <em>Caution:</em> Be careful to finally call one of those methods, as
170      * otherwise the transaction will lurk around for ever.
171      * 
172      * @see #prepareTransaction(AbstractTransactionContext)
173      * @see #commitTransaction(AbstractTransactionContext)
174      * @see #rollbackTransaction(AbstractTransactionContext)
175      */
176     public AbstractTransactionContext startTransaction(Object session) throws ResourceManagerException
177     {
178         return createTransactionContext(session);
179     }
180 
181     public void beginTransaction(AbstractTransactionContext context) throws ResourceManagerException
182     {
183         // can only start a new transaction when not already stopping
184         assureStarted();
185 
186         synchronized (context)
187         {
188             if (logger.isDebugEnabled())
189             {
190                 logger.debug("Beginning transaction " + context);
191             }
192             doBegin(context);
193             context.status = Status.STATUS_ACTIVE;
194             if (logger.isDebugEnabled())
195             {
196                 logger.debug("Began transaction " + context);
197             }
198         }
199         globalTransactions.add(context);
200     }
201 
202     public int prepareTransaction(AbstractTransactionContext context) throws ResourceManagerException
203     {
204         assureReady();
205         synchronized (context)
206         {
207             if (logger.isDebugEnabled())
208             {
209                 logger.debug("Preparing transaction " + context);
210             }
211             context.status = Status.STATUS_PREPARING;
212             int status = doPrepare(context);
213             context.status = Status.STATUS_PREPARED;
214             if (logger.isDebugEnabled())
215             {
216                 logger.debug("Prepared transaction " + context);
217             }
218             return status;
219         }
220     }
221 
222     public void rollbackTransaction(AbstractTransactionContext context) throws ResourceManagerException
223     {
224         assureReady();
225         synchronized (context)
226         {
227             if (logger.isDebugEnabled())
228             {
229                 logger.debug("Rolling back transaction " + context);
230             }
231             try
232             {
233                 context.status = Status.STATUS_ROLLING_BACK;
234                 doRollback(context);
235                 context.status = Status.STATUS_ROLLEDBACK;
236             }
237             catch (Error e)
238             {
239                 setDirty(context, e);
240                 throw e;
241             }
242             catch (RuntimeException e)
243             {
244                 setDirty(context, e);
245                 throw e;
246             }
247             catch (ResourceManagerSystemException e)
248             {
249                 setDirty(context, e);
250                 throw e;
251             }
252             finally
253             {
254                 globalTransactions.remove(context);
255                 context.finalCleanUp();
256                 // tell shutdown thread this tx is finished
257                 context.notifyFinish();
258             }
259             if (logger.isDebugEnabled())
260             {
261                 logger.debug("Rolled back transaction " + context);
262             }
263         }
264     }
265 
266     public void setTransactionRollbackOnly(AbstractTransactionContext context)
267         throws ResourceManagerException
268     {
269         context.status = Status.STATUS_MARKED_ROLLBACK;
270     }
271 
272     public void commitTransaction(AbstractTransactionContext context) throws ResourceManagerException
273     {
274         assureReady();
275         if (context.status == Status.STATUS_MARKED_ROLLBACK)
276         {
277             throw new ResourceManagerException(CoreMessages.transactionMarkedForRollback());
278         }
279         synchronized (context)
280         {
281             if (logger.isDebugEnabled())
282             {
283                 logger.debug("Committing transaction " + context);
284             }
285             try
286             {
287                 context.status = Status.STATUS_COMMITTING;
288                 doCommit(context);
289                 context.status = Status.STATUS_COMMITTED;
290             }
291             catch (Error e)
292             {
293                 setDirty(context, e);
294                 throw e;
295             }
296             catch (RuntimeException e)
297             {
298                 setDirty(context, e);
299                 throw e;
300             }
301             catch (ResourceManagerSystemException e)
302             {
303                 setDirty(context, e);
304                 throw e;
305             }
306             catch (ResourceManagerException e)
307             {
308                 logger.warn("Could not commit tx " + context + ", rolling back instead", e);
309                 doRollback(context);
310             }
311             finally
312             {
313                 globalTransactions.remove(context);
314                 context.finalCleanUp();
315                 // tell shutdown thread this tx is finished
316                 context.notifyFinish();
317             }
318             if (logger.isDebugEnabled())
319             {
320                 logger.debug("Committed transaction " + context);
321             }
322         }
323     }
324 
325     protected abstract AbstractTransactionContext createTransactionContext(Object session);
326 
327     protected abstract void doBegin(AbstractTransactionContext context);
328 
329     protected abstract int doPrepare(AbstractTransactionContext context);
330 
331     protected abstract void doCommit(AbstractTransactionContext context) throws ResourceManagerException;
332 
333     protected abstract void doRollback(AbstractTransactionContext context) throws ResourceManagerException;
334 
335     // TODO
336     // protected boolean rollBackOrForward() {
337     // }
338 
339     protected boolean waitForAllTxToStop(long timeoutMSecs)
340     {
341         long startTime = System.currentTimeMillis();
342 
343         // be sure not to lock globalTransactions for too long, as we need to
344         // give
345         // txs the chance to complete (otherwise deadlocks are very likely to
346         // occur)
347         // instead iterate over a copy as we can be sure no new txs will be
348         // registered
349         // after operation level has been set to stopping
350 
351         Collection transactionsToStop;
352         synchronized (globalTransactions)
353         {
354             transactionsToStop = new ArrayList(globalTransactions);
355         }
356         for (Iterator it = transactionsToStop.iterator(); it.hasNext();)
357         {
358             long remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
359 
360             if (remainingTimeout <= 0)
361             {
362                 return false;
363             }
364 
365             AbstractTransactionContext context = (AbstractTransactionContext) it.next();
366             synchronized (context)
367             {
368                 if (!context.finished)
369                 {
370                     logger.info("Waiting for tx " + context + " to finish for " + remainingTimeout
371                                     + " milli seconds");
372                 }
373                 while (!context.finished && remainingTimeout > 0)
374                 {
375                     try
376                     {
377                         context.wait(remainingTimeout);
378                     }
379                     catch (InterruptedException e)
380                     {
381                         return false;
382                     }
383                     remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
384                 }
385                 if (context.finished)
386                 {
387                     logger.info("Tx " + context + " finished");
388                 }
389                 else
390                 {
391                     logger.warn("Tx " + context + " failed to finish in given time");
392                 }
393             }
394         }
395 
396         return (globalTransactions.size() == 0);
397     }
398 
399     /**
400      * Flag this resource manager as dirty. No more operations will be allowed until
401      * a recovery has been successfully performed.
402      * 
403      * @param context
404      * @param t
405      */
406     protected void setDirty(AbstractTransactionContext context, Throwable t)
407     {
408         logger.error("Fatal error during critical commit/rollback of transaction " + context
409                         + ", setting resource manager to dirty.", t);
410         dirty = true;
411     }
412 
413     /**
414      * Check that the FileManager is started.
415      * 
416      * @throws FileManagerSystemException if the FileManager is not started.
417      */
418     protected void assureStarted() throws ResourceManagerSystemException
419     {
420         if (operationMode != OPERATION_MODE_STARTED)
421         {
422             throw new ResourceManagerSystemException(CoreMessages.resourceManagerNotStarted());
423         }
424         // do not allow any further writing or commit or rollback when db is
425         // corrupt
426         if (dirty)
427         {
428             throw new ResourceManagerSystemException(CoreMessages.resourceManagerDirty());
429         }
430     }
431 
432     /**
433      * Check that the FileManager is ready.
434      * 
435      * @throws FileManagerSystemException if the FileManager is neither started not
436      *             stopping.
437      */
438     protected void assureReady() throws ResourceManagerSystemException
439     {
440         if (operationMode != OPERATION_MODE_STARTED && operationMode != OPERATION_MODE_STOPPING)
441         {
442             throw new ResourceManagerSystemException(CoreMessages.resourceManagerNotReady());
443         }
444         // do not allow any further writing or commit or rollback when db is
445         // corrupt
446         if (dirty)
447         {
448             throw new ResourceManagerSystemException(CoreMessages.resourceManagerDirty());
449         }
450     }
451 
452 }