Coverage Report - org.mule.util.xa.AbstractResourceManager
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractResourceManager
48%
73/151
32%
16/50
2.88
 
 1  
 /*
 2  
  * $Id: AbstractResourceManager.java 8077 2007-08-27 20:15:25Z aperepel $
 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  1194
 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  1194
     protected Collection globalTransactions = Collections.synchronizedCollection(new ArrayList());
 57  1194
     protected int operationMode = OPERATION_MODE_STOPPED;
 58  1194
     protected long defaultTimeout = DEFAULT_TIMEOUT_MSECS;
 59  1194
     protected Log logger = getLogger();
 60  1194
     protected boolean dirty = false;
 61  
 
 62  
     protected abstract Log getLogger();
 63  
 
 64  
     public synchronized void start() throws ResourceManagerSystemException
 65  
     {
 66  48
         logger.info("Starting ResourceManager");
 67  48
         operationMode = OPERATION_MODE_STARTING;
 68  
         // TODO: recover and sync
 69  48
         doStart();
 70  48
         recover();
 71  
         // sync();
 72  48
         operationMode = OPERATION_MODE_STARTED;
 73  48
         if (dirty)
 74  
         {
 75  0
             logger
 76  
                 .warn("Started ResourceManager, but in dirty mode only (Recovery of pending transactions failed)");
 77  
         }
 78  
         else
 79  
         {
 80  48
             logger.info("Started ResourceManager");
 81  
         }
 82  48
     }
 83  
 
 84  
     protected void doStart() throws ResourceManagerSystemException
 85  
     {
 86  
         // template method
 87  0
     }
 88  
 
 89  
     protected void recover() throws ResourceManagerSystemException
 90  
     {
 91  
         // nothing to do (yet?)
 92  0
     }
 93  
 
 94  
     public synchronized void stop() throws ResourceManagerSystemException
 95  
     {
 96  0
         stop(SHUTDOWN_MODE_NORMAL);
 97  0
     }
 98  
 
 99  
     public synchronized boolean stop(int mode) throws ResourceManagerSystemException
 100  
     {
 101  48
         return stop(mode, getDefaultTransactionTimeout() * DEFAULT_COMMIT_TIMEOUT_FACTOR);
 102  
     }
 103  
 
 104  
     public synchronized boolean stop(int mode, long timeOut) throws ResourceManagerSystemException
 105  
     {
 106  48
         logger.info("Stopping ResourceManager");
 107  48
         operationMode = OPERATION_MODE_STOPPING;
 108  
         // TODO: sync
 109  
         // sync();
 110  48
         boolean success = shutdown(mode, timeOut);
 111  
         // TODO: release
 112  
         // releaseGlobalOpenResources();
 113  48
         if (success)
 114  
         {
 115  48
             operationMode = OPERATION_MODE_STOPPED;
 116  48
             logger.info("Stopped ResourceManager");
 117  
         }
 118  
         else
 119  
         {
 120  0
             logger.warn("Failed to stop ResourceManager");
 121  
         }
 122  
 
 123  48
         return success;
 124  
     }
 125  
 
 126  
     protected boolean shutdown(int mode, long timeoutMSecs)
 127  
     {
 128  48
         switch (mode)
 129  
         {
 130  
             case SHUTDOWN_MODE_NORMAL :
 131  48
                 return waitForAllTxToStop(timeoutMSecs);
 132  
             case SHUTDOWN_MODE_ROLLBACK :
 133  0
                 throw new UnsupportedOperationException();
 134  
                 // return rollBackOrForward();
 135  
             case SHUTDOWN_MODE_KILL :
 136  0
                 return true;
 137  
             default :
 138  0
                 return false;
 139  
         }
 140  
     }
 141  
 
 142  
     /**
 143  
      * Gets the default transaction timeout in <em>milliseconds</em>.
 144  
      */
 145  
     public long getDefaultTransactionTimeout()
 146  
     {
 147  48
         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  0
         defaultTimeout = timeout;
 158  0
     }
 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  0
         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  20
         assureStarted();
 184  
 
 185  20
         synchronized (context)
 186  
         {
 187  20
             if (logger.isDebugEnabled())
 188  
             {
 189  0
                 logger.debug("Beginning transaction " + context);
 190  
             }
 191  20
             doBegin(context);
 192  20
             context.status = Status.STATUS_ACTIVE;
 193  20
             if (logger.isDebugEnabled())
 194  
             {
 195  0
                 logger.debug("Began transaction " + context);
 196  
             }
 197  20
         }
 198  20
         globalTransactions.add(context);
 199  20
     }
 200  
 
 201  
     public int prepareTransaction(AbstractTransactionContext context) throws ResourceManagerException
 202  
     {
 203  0
         assureReady();
 204  0
         synchronized (context)
 205  
         {
 206  0
             if (logger.isDebugEnabled())
 207  
             {
 208  0
                 logger.debug("Preparing transaction " + context);
 209  
             }
 210  0
             context.status = Status.STATUS_PREPARING;
 211  0
             int status = doPrepare(context);
 212  0
             context.status = Status.STATUS_PREPARED;
 213  0
             if (logger.isDebugEnabled())
 214  
             {
 215  0
                 logger.debug("Prepared transaction " + context);
 216  
             }
 217  0
             return status;
 218  0
         }
 219  
     }
 220  
 
 221  
     public void rollbackTransaction(AbstractTransactionContext context) throws ResourceManagerException
 222  
     {
 223  10
         assureReady();
 224  10
         synchronized (context)
 225  
         {
 226  10
             if (logger.isDebugEnabled())
 227  
             {
 228  0
                 logger.debug("Rolling back transaction " + context);
 229  
             }
 230  
             try
 231  
             {
 232  10
                 context.status = Status.STATUS_ROLLING_BACK;
 233  10
                 doRollback(context);
 234  10
                 context.status = Status.STATUS_ROLLEDBACK;
 235  
             }
 236  0
             catch (Error e)
 237  
             {
 238  0
                 setDirty(context, e);
 239  0
                 throw e;
 240  
             }
 241  0
             catch (RuntimeException e)
 242  
             {
 243  0
                 setDirty(context, e);
 244  0
                 throw e;
 245  
             }
 246  0
             catch (ResourceManagerSystemException e)
 247  
             {
 248  0
                 setDirty(context, e);
 249  0
                 throw e;
 250  
             }
 251  
             finally
 252  
             {
 253  10
                 globalTransactions.remove(context);
 254  10
                 context.finalCleanUp();
 255  
                 // tell shutdown thread this tx is finished
 256  10
                 context.notifyFinish();
 257  10
             }
 258  10
             if (logger.isDebugEnabled())
 259  
             {
 260  0
                 logger.debug("Rolled back transaction " + context);
 261  
             }
 262  10
         }
 263  10
     }
 264  
 
 265  
     public void setTransactionRollbackOnly(AbstractTransactionContext context)
 266  
         throws ResourceManagerException
 267  
     {
 268  0
         context.status = Status.STATUS_MARKED_ROLLBACK;
 269  0
     }
 270  
 
 271  
     public void commitTransaction(AbstractTransactionContext context) throws ResourceManagerException
 272  
     {
 273  10
         assureReady();
 274  10
         if (context.status == Status.STATUS_MARKED_ROLLBACK)
 275  
         {
 276  0
             throw new ResourceManagerException(CoreMessages.transactionMarkedForRollback());
 277  
         }
 278  10
         synchronized (context)
 279  
         {
 280  10
             if (logger.isDebugEnabled())
 281  
             {
 282  0
                 logger.debug("Committing transaction " + context);
 283  
             }
 284  
             try
 285  
             {
 286  10
                 context.status = Status.STATUS_COMMITTING;
 287  10
                 doCommit(context);
 288  10
                 context.status = Status.STATUS_COMMITTED;
 289  
             }
 290  0
             catch (Error e)
 291  
             {
 292  0
                 setDirty(context, e);
 293  0
                 throw e;
 294  
             }
 295  0
             catch (RuntimeException e)
 296  
             {
 297  0
                 setDirty(context, e);
 298  0
                 throw e;
 299  
             }
 300  0
             catch (ResourceManagerSystemException e)
 301  
             {
 302  0
                 setDirty(context, e);
 303  0
                 throw e;
 304  
             }
 305  0
             catch (ResourceManagerException e)
 306  
             {
 307  0
                 logger.warn("Could not commit tx " + context + ", rolling back instead", e);
 308  0
                 doRollback(context);
 309  
             }
 310  
             finally
 311  
             {
 312  10
                 globalTransactions.remove(context);
 313  10
                 context.finalCleanUp();
 314  
                 // tell shutdown thread this tx is finished
 315  10
                 context.notifyFinish();
 316  10
             }
 317  10
             if (logger.isDebugEnabled())
 318  
             {
 319  0
                 logger.debug("Committed transaction " + context);
 320  
             }
 321  10
         }
 322  10
     }
 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  48
         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  48
         synchronized (globalTransactions)
 352  
         {
 353  48
             transactionsToStop = new ArrayList(globalTransactions);
 354  48
         }
 355  48
         for (Iterator it = transactionsToStop.iterator(); it.hasNext();)
 356  
         {
 357  0
             long remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
 358  
 
 359  0
             if (remainingTimeout <= 0)
 360  
             {
 361  0
                 return false;
 362  
             }
 363  
 
 364  0
             AbstractTransactionContext context = (AbstractTransactionContext) it.next();
 365  0
             synchronized (context)
 366  
             {
 367  0
                 if (!context.finished)
 368  
                 {
 369  0
                     logger.info("Waiting for tx " + context + " to finish for " + remainingTimeout
 370  
                                     + " milli seconds");
 371  
                 }
 372  0
                 while (!context.finished && remainingTimeout > 0)
 373  
                 {
 374  
                     try
 375  
                     {
 376  0
                         context.wait(remainingTimeout);
 377  
                     }
 378  0
                     catch (InterruptedException e)
 379  
                     {
 380  0
                         return false;
 381  0
                     }
 382  0
                     remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
 383  
                 }
 384  0
                 if (context.finished)
 385  
                 {
 386  0
                     logger.info("Tx " + context + " finished");
 387  
                 }
 388  
                 else
 389  
                 {
 390  0
                     logger.warn("Tx " + context + " failed to finish in given time");
 391  
                 }
 392  0
             }
 393  0
         }
 394  
 
 395  48
         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  0
         logger.error("Fatal error during critical commit/rollback of transaction " + context
 408  
                         + ", setting resource manager to dirty.", t);
 409  0
         dirty = true;
 410  0
     }
 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  20
         if (operationMode != OPERATION_MODE_STARTED)
 420  
         {
 421  0
             throw new ResourceManagerSystemException(CoreMessages.resourceManagerNotStarted());
 422  
         }
 423  
         // do not allow any further writing or commit or rollback when db is
 424  
         // corrupt
 425  20
         if (dirty)
 426  
         {
 427  0
             throw new ResourceManagerSystemException(CoreMessages.resourceManagerDirty());
 428  
         }
 429  20
     }
 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  20
         if (operationMode != OPERATION_MODE_STARTED && operationMode != OPERATION_MODE_STOPPING)
 440  
         {
 441  0
             throw new ResourceManagerSystemException(CoreMessages.resourceManagerNotReady());
 442  
         }
 443  
         // do not allow any further writing or commit or rollback when db is
 444  
         // corrupt
 445  20
         if (dirty)
 446  
         {
 447  0
             throw new ResourceManagerSystemException(CoreMessages.resourceManagerDirty());
 448  
         }
 449  20
     }
 450  
 
 451  
 }