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