View Javadoc

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