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