Coverage Report - org.apache.commons.transaction.file.FileResourceManager
 
Classes in this File Line Coverage Branch Coverage Complexity
FileResourceManager
61%
347/560
51%
132/256
3.606
FileResourceManager$InputStreamWrapper
68%
24/35
70%
7/10
3.606
FileResourceManager$TransactionContext
80%
108/135
75%
15/20
3.606
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.transaction.file;
 18  
 
 19  
 import java.io.BufferedReader;
 20  
 import java.io.BufferedWriter;
 21  
 import java.io.File;
 22  
 import java.io.FileInputStream;
 23  
 import java.io.FileNotFoundException;
 24  
 import java.io.FileOutputStream;
 25  
 import java.io.IOException;
 26  
 import java.io.InputStream;
 27  
 import java.io.InputStreamReader;
 28  
 import java.io.OutputStream;
 29  
 import java.io.OutputStreamWriter;
 30  
 import java.util.ArrayList;
 31  
 import java.util.Collection;
 32  
 import java.util.HashMap;
 33  
 import java.util.List;
 34  
 import java.util.Map;
 35  
 import java.util.Iterator;
 36  
 import java.util.Collections;
 37  
 
 38  
 import org.apache.commons.transaction.locking.GenericLock;
 39  
 import org.apache.commons.transaction.locking.GenericLockManager;
 40  
 import org.apache.commons.transaction.locking.LockException;
 41  
 import org.apache.commons.transaction.locking.LockManager2;
 42  
 import org.apache.commons.transaction.util.FileHelper;
 43  
 import org.apache.commons.transaction.util.LoggerFacade;
 44  
 
 45  
 /**
 46  
  * A resource manager for streamable objects stored in a file system.
 47  
  * 
 48  
  * It is intended for developer and "out of the box" use. 
 49  
  * It is <em>not</em> intended to be a real alternative for
 50  
  * a full blown DMBS (of course it can not be compared to a RDBMS at all).
 51  
  * 
 52  
  * Major features:<br>
 53  
  * <ul>
 54  
  * <li>Transactions performed with this class more or less comform to the widely accepted ACID properties
 55  
  * <li>Reading should be as fast as from the ordinary file system (at the cost of a bit slower commits) 
 56  
  * </ul>
 57  
  * 
 58  
  * Compared to a "real" DBMS major limitations are (in order of assumed severity):<br>
 59  
  * <ul>
 60  
  * <li>Number of simultaneously open resources is limited to the number of available file descriptors
 61  
  * <li>It does not scale a bit
 62  
  * <li>Pessimistic transaction and locking scheme
 63  
  * <li>Isolation level currently is restricted to <em>read committed</em> and <em>repeated read</em> (which is not that bad)
 64  
  * </ul>
 65  
  * 
 66  
  * <em>Important</em>: If possible you should have the work and store directory located in the 
 67  
  * same file system. If not, you might get additional problems, as there are:
 68  
  * <ul>
 69  
  * <li>On commit it might be necessay to copy files instead of rename/relink them. This may lead to time consuming, 
 70  
  * overly blocking commit phases and higher risk of corrupted files
 71  
  * <li>Prepare phase might be too permissive, no check for sufficient memory on store file system is possible
 72  
  * </ul> 
 73  
  * 
 74  
  * General limitations include:<br>
 75  
  * <ul>
 76  
  * <li>Due to lack of synchronization on the transaction context level, every transaction may only be
 77  
  * accessed by a <em>single thread</em> throughout its full life. 
 78  
  * This means it is forbidden for a thread that has not started a transaction 
 79  
  * to perform any operations inside this transaction. However, threads associated
 80  
  * with different transactions can safely access these methods concurrently.
 81  
  * Reasons for the lack of synchronization are improved performance and simplicity (of the code of this class).
 82  
  * <li>There is no dedicated class for a transaction. Having such a class would be better practice and 
 83  
  * make certain actions more intuitive.
 84  
  * <li>Resource identifiers need a reasonsable string representation obtainable by <code>toString</code>.
 85  
  * More specifically, they will have to resolve to a <em>valid</em> file path that does note denote a directory. 
 86  
  * If it does, you might be able to create it, but not to read or write anything 
 87  
  * from resp. to it. Valid string representations of a resource idenfier are 
 88  
  * for example "file" "/root" or "hjfhdfhuhuhsdufhdsufhdsufhdfuhdfduhduhduhdu". 
 89  
  * Invalid are for example "/" or "/root/". Invalid on some file systems are for example "c:" or "file://huhu".
 90  
  * <li>As there are no active processes inside this RM and it shares its threads with the application,
 91  
  * control over transactions is limited to points where the application calls the RM. 
 92  
  * In particular, this disables <em>active</em> termination of transactions upon timeout.
 93  
  * <li>There is no notion of a connection to this file manager. This means you can not connect from hosts other than
 94  
  * local and you will get problems when plugging this store into a J2EE store using connectors. 
 95  
  * <li>Methods should throw more specific exceptions
 96  
  * </ul>
 97  
  * 
 98  
  * <p><em>Caution</em>:<br>
 99  
  * The <code>txId</code> passed to many methods as an identifier for the
 100  
  * transaction concerned will function as a key in a <code>HashMap</code>.
 101  
  * Thus assure that <code>equals</code> and <code>hashCode</code> are both
 102  
  * properly implemented and match each other.</p>
 103  
  *  
 104  
  * <p><em>Caution</em>:<br>
 105  
  * You will have to guarantee that no other process will access neither
 106  
  * the store or the working dir concurrently to this <code>FileResourceManager</code>.</p>
 107  
  * 
 108  
  * <p><em>Special Caution</em>:<br>
 109  
  * Be very careful not to have two instances of <code>FileResourceManager</code>
 110  
  * working in the same store and/or working dir.
 111  
  *   
 112  
  * @version $Id: FileResourceManager.java 573315 2007-09-06 16:28:42Z ozeigermann $
 113  
  */
 114  
 public class FileResourceManager implements ResourceManager, ResourceManagerErrorCodes {
 115  
 
 116  
     // reflects the natural isolation level of this store
 117  
     protected static final int NATIVE_ISOLATION_LEVEL = ISOLATION_LEVEL_REPEATABLE_READ;
 118  
     protected static final int DEFAULT_ISOLATION_LEVEL = NATIVE_ISOLATION_LEVEL;
 119  
 
 120  
     protected static final int NO_LOCK = 0;
 121  
     protected static final int LOCK_ACCESS = NO_LOCK + 1;
 122  
     protected static final int LOCK_SHARED = NO_LOCK + 2;
 123  
     protected static final int LOCK_EXCLUSIVE = NO_LOCK + 3;
 124  
     protected static final int LOCK_COMMIT = NO_LOCK + 4;
 125  
 
 126  
     protected static final int OPERATION_MODE_STOPPED = 0;
 127  
     protected static final int OPERATION_MODE_STOPPING = 1;
 128  
     protected static final int OPERATION_MODE_STARTED = 2;
 129  
     protected static final int OPERATION_MODE_STARTING = 3;
 130  
     protected static final int OPERATION_MODE_RECOVERING = 4;
 131  
 
 132  
     protected static final String DEFAULT_PARAMETER_ENCODING = "ISO-8859-15";
 133  
 
 134  
     protected static final int DEFAULT_TIMEOUT_MSECS = 5000;
 135  
     protected static final int DEFAULT_COMMIT_TIMEOUT_FACTOR = 2;
 136  
 
 137  
     protected static final String WORK_CHANGE_DIR = "change";
 138  
     protected static final String WORK_DELETE_DIR = "delete";
 139  
 
 140  
     protected static final String CONTEXT_FILE = "transaction.log";
 141  
 
 142  
     /*
 143  
      * --- Static helper methods ---
 144  
      *
 145  
      *  
 146  
      */
 147  
 
 148  
     protected static void applyDeletes(File removeDir, File targetDir, File rootDir)
 149  
             throws IOException {
 150  15
         if (removeDir.isDirectory() && targetDir.isDirectory()) {
 151  15
             File[] files = removeDir.listFiles();
 152  25
             for (int i = 0; i < files.length; i++) {
 153  10
                 File removeFile = files[i];
 154  10
                 File targetFile = new File(targetDir, removeFile.getName());
 155  10
                 if (removeFile.isFile()) {
 156  7
                     if (targetFile.exists()) {
 157  7
                         if (!targetFile.delete()) {
 158  0
                             throw new IOException("Could not delete file " + removeFile.getName()
 159  
                                     + " in directory targetDir");
 160  
                         }
 161  
                     }
 162  
                     // indicate, this has been done
 163  7
                     removeFile.delete();
 164  
                 } else {
 165  3
                     applyDeletes(removeFile, targetFile, rootDir);
 166  
                 }
 167  
             }
 168  
             // delete empty target directories, except root dir
 169  15
             if (!targetDir.equals(rootDir) && targetDir.list().length == 0) {
 170  0
                 targetDir.delete();
 171  
             }
 172  
         }
 173  15
     }
 174  
     
 175  
     /*
 176  
      * --- object members ---
 177  
      * 
 178  
      *  
 179  
      */
 180  
 
 181  
     protected String workDir;
 182  
     protected String storeDir;
 183  10
     protected boolean cleanUp = true;
 184  10
     protected boolean dirty = false;
 185  10
     protected int operationMode = OPERATION_MODE_STOPPED;
 186  10
     protected long defaultTimeout = DEFAULT_TIMEOUT_MSECS;
 187  
     protected boolean debug;
 188  
 
 189  
     protected LoggerFacade logger;
 190  
 
 191  
     protected Map globalTransactions;
 192  
     protected List globalOpenResources;
 193  
     protected LockManager2 lockManager;
 194  
 
 195  10
     protected ResourceIdToPathMapper idMapper = null;
 196  10
     protected TransactionIdToPathMapper txIdMapper = null;
 197  
 
 198  10
     protected int idCnt = 0;
 199  
 
 200  
     /*
 201  
      * --- ctor and general getter / setter methods ---
 202  
      *
 203  
      *  
 204  
      */
 205  
 
 206  
     /**
 207  
      * Creates a new resource manager operation on the specified directories.
 208  
      * 
 209  
      * @param storeDir directory where main data should go after commit
 210  
      * @param workDir directory where transactions store temporary data
 211  
      * @param urlEncodePath if set to <code>true</code> encodes all paths to allow for any kind of characters
 212  
      * @param logger the logger to be used by this store  
 213  
      */
 214  
     public FileResourceManager(String storeDir, String workDir, boolean urlEncodePath, LoggerFacade logger) {
 215  0
         this(storeDir, workDir, urlEncodePath, logger, false);
 216  0
     }
 217  
 
 218  
     /**
 219  
      * Creates a new resource manager operation on the specified directories.
 220  
      * 
 221  
      * @param storeDir directory where main data should go after commit
 222  
      * @param workDir directory where transactions store temporary data 
 223  
      * @param urlEncodePath if set to <code>true</code> encodes all paths to allow for any kind of characters
 224  
      * @param logger the logger to be used by this store
 225  
      * @param debug if set to <code>true</code> logs all locking information to "transaction.log" for debugging inspection 
 226  
      */
 227  
     public FileResourceManager(
 228  
         String storeDir,
 229  
         String workDir,
 230  
         boolean urlEncodePath,
 231  
         LoggerFacade logger,
 232  
         boolean debug) {
 233  9
         this(storeDir, workDir, urlEncodePath ? new URLEncodeIdMapper() : null, new NoOpTransactionIdToPathMapper(), logger, debug);
 234  9
     }
 235  
 
 236  
     /**
 237  
      * Creates a new resource manager operation on the specified directories.
 238  
      * This constructor is reintroduced for backwards API compatibility and is used by jakarta-slide.
 239  
      *
 240  
      * @param storeDir directory where main data should go after commit
 241  
      * @param workDir directory where transactions store temporary data
 242  
      * @param idMapper mapper for resourceId to path
 243  
      * @param logger the logger to be used by this store
 244  
      * @param debug if set to <code>true</code> logs all locking information to "transaction.log" for debugging inspection
 245  
      */
 246  
     public FileResourceManager(
 247  
         String storeDir,
 248  
         String workDir,
 249  
         ResourceIdToPathMapper idMapper,
 250  
         LoggerFacade logger,
 251  
         boolean debug) {
 252  0
         this(storeDir, workDir, idMapper, new NoOpTransactionIdToPathMapper(), logger, debug);
 253  0
     }
 254  
     /**
 255  
      * Creates a new resource manager operation on the specified directories.
 256  
      * 
 257  
      * @param storeDir directory where main data should go after commit
 258  
      * @param workDir directory where transactions store temporary data 
 259  
      * @param idMapper mapper for resourceId to path
 260  
      * @param txIdMapper mapper for transaction id to path
 261  
      * @param logger the logger to be used by this store
 262  
      * @param debug if set to <code>true</code> logs all locking information to "transaction.log" for debugging inspection 
 263  
      */
 264  
     public FileResourceManager(
 265  
         String storeDir,
 266  
         String workDir,
 267  
         ResourceIdToPathMapper idMapper,
 268  
         TransactionIdToPathMapper txIdMapper,
 269  
         LoggerFacade logger,
 270  10
         boolean debug) {
 271  10
         this.workDir = workDir;
 272  10
         this.storeDir = storeDir;
 273  10
         this.idMapper = idMapper;
 274  10
         this.txIdMapper = txIdMapper;
 275  10
         this.logger = logger;
 276  10
         this.debug = debug;
 277  10
     }
 278  
 
 279  
     /**
 280  
      * Gets the store directory.
 281  
      * 
 282  
      * @return the store directory
 283  
      * @see #FileResourceManager(String, String, boolean, LoggerFacade)
 284  
      * @see #FileResourceManager(String, String, boolean, LoggerFacade, boolean)
 285  
      */
 286  
     public String getStoreDir() {
 287  0
         return storeDir;
 288  
     }
 289  
 
 290  
     /**
 291  
      * Gets the working directory.
 292  
      * 
 293  
      * @return the work directory
 294  
      * @see #FileResourceManager(String, String, boolean, LoggerFacade)
 295  
      * @see #FileResourceManager(String, String, boolean, LoggerFacade, boolean)
 296  
      */
 297  
     public String getWorkDir() {
 298  0
         return workDir;
 299  
     }
 300  
 
 301  
     /**
 302  
      * Gets the logger used by this resource manager. 
 303  
      * 
 304  
      * @return used logger 
 305  
      */
 306  
     public LoggerFacade getLogger() {
 307  0
         return logger;
 308  
     }
 309  
 
 310  
     /*
 311  
      * --- public methods of interface ResourceManager ---
 312  
      *
 313  
      *  
 314  
      */
 315  
 
 316  
     public boolean lockResource(Object resourceId, Object txId) throws ResourceManagerException {
 317  0
         lockResource(resourceId, txId, false);
 318  
         // XXX will never return false as it will either throw or return true
 319  0
         return true;
 320  
     }
 321  
 
 322  
     public boolean lockResource(Object resourceId, Object txId, boolean shared) throws ResourceManagerException {
 323  122
         lockResource(resourceId, txId, shared, true, Long.MAX_VALUE, true);
 324  
         // XXX will never return false as it will either throw or return true
 325  76
         return true;
 326  
     }
 327  
 
 328  
     public boolean lockResource(
 329  
         Object resourceId,
 330  
         Object txId,
 331  
         boolean shared,
 332  
         boolean wait,
 333  
         long timeoutMSecs,
 334  
         boolean reentrant)
 335  
         throws ResourceManagerException {
 336  
 
 337  122
         TransactionContext context = (shared ? txInitialSaneCheck(txId) : txInitialSaneCheckForWriting(txId));
 338  121
         assureNotMarkedForRollback(context);
 339  121
         fileInitialSaneCheck(txId, resourceId);
 340  
 
 341  
         // XXX allows locking of non existent resources (e.g. to prepare a create)
 342  121
         int level = (shared ? getSharedLockLevel(context) : LOCK_EXCLUSIVE);
 343  
         try {
 344  121
             lockManager.lock(txId, resourceId, level, reentrant, Math.min(timeoutMSecs,
 345  
                     context.timeoutMSecs));
 346  
             // XXX will never return false as it will either throw or return true
 347  76
             return true;
 348  45
         } catch (LockException e) {
 349  45
             switch (e.getCode()) {
 350  
             case LockException.CODE_INTERRUPTED:
 351  0
                 throw new ResourceManagerException("Could not get lock for resource at '"
 352  
                         + resourceId + "'", ERR_NO_LOCK, txId);
 353  
             case LockException.CODE_TIMED_OUT:
 354  0
                 throw new ResourceManagerException("Lock timed out for resource at '" + resourceId
 355  
                         + "'", ERR_NO_LOCK, txId);
 356  
             case LockException.CODE_DEADLOCK_VICTIM:
 357  45
                 throw new ResourceManagerException("Deadlock victim resource at '" + resourceId
 358  
                         + "'", ERR_DEAD_LOCK, txId);
 359  
             default :
 360  0
                 throw new ResourceManagerException("Locking exception for resource at '" + resourceId
 361  
                         + "'", ERR_DEAD_LOCK, txId);
 362  
             }
 363  
         }
 364  
     }
 365  
 
 366  
     public int getDefaultIsolationLevel() {
 367  0
         return DEFAULT_ISOLATION_LEVEL;
 368  
     }
 369  
 
 370  
     public int[] getSupportedIsolationLevels() throws ResourceManagerException {
 371  0
         return new int[] { ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ };
 372  
     }
 373  
 
 374  
     public boolean isIsolationLevelSupported(int level) throws ResourceManagerException {
 375  0
         return (level == ISOLATION_LEVEL_READ_COMMITTED || level == ISOLATION_LEVEL_REPEATABLE_READ);
 376  
     }
 377  
 
 378  
     /**
 379  
      * Gets the default transaction timeout in <em>milliseconds</em>.
 380  
      */
 381  
     public long getDefaultTransactionTimeout() {
 382  90
         return defaultTimeout;
 383  
     }
 384  
 
 385  
     /**
 386  
      * Sets the default transaction timeout.
 387  
      * 
 388  
      * @param timeout timeout in <em>milliseconds</em>
 389  
      */
 390  
     public void setDefaultTransactionTimeout(long timeout) {
 391  0
         defaultTimeout = timeout;
 392  0
     }
 393  
 
 394  
     public long getTransactionTimeout(Object txId) throws ResourceManagerException {
 395  0
         assureRMReady();
 396  0
         long msecs = 0;
 397  0
         TransactionContext context = getContext(txId);
 398  0
         if (context == null) {
 399  0
             msecs = getDefaultTransactionTimeout();
 400  
         } else {
 401  0
             msecs = context.timeoutMSecs;
 402  
         }
 403  0
         return msecs;
 404  
     }
 405  
 
 406  
     public void setTransactionTimeout(Object txId, long mSecs) throws ResourceManagerException {
 407  0
         assureRMReady();
 408  0
         TransactionContext context = getContext(txId);
 409  0
         if (context != null) {
 410  0
             context.timeoutMSecs = mSecs;
 411  
         } else {
 412  0
             throw new ResourceManagerException(ERR_NO_TX, txId);
 413  
         }
 414  0
     }
 415  
 
 416  
     public int getIsolationLevel(Object txId) throws ResourceManagerException {
 417  0
         assureRMReady();
 418  0
         TransactionContext context = getContext(txId);
 419  0
         if (context == null) {
 420  0
             return DEFAULT_ISOLATION_LEVEL;
 421  
         } else {
 422  0
             return context.isolationLevel;
 423  
         }
 424  
     }
 425  
 
 426  
     public void setIsolationLevel(Object txId, int level) throws ResourceManagerException {
 427  1
         assureRMReady();
 428  1
         TransactionContext context = getContext(txId);
 429  1
         if (context != null) {
 430  1
             if (level != ISOLATION_LEVEL_READ_COMMITTED || level != ISOLATION_LEVEL_REPEATABLE_READ) {
 431  1
                 context.isolationLevel = level;
 432  
             } else {
 433  0
                 throw new ResourceManagerException(ERR_ISOLATION_LEVEL_UNSUPPORTED, txId);
 434  
             }
 435  
         } else {
 436  0
             throw new ResourceManagerException(ERR_NO_TX, txId);
 437  
         }
 438  1
     }
 439  
 
 440  
     public synchronized void start() throws ResourceManagerSystemException {
 441  
 
 442  10
         logger.logInfo("Starting RM at '" + storeDir + "' / '" + workDir + "'");
 443  
 
 444  10
         operationMode = OPERATION_MODE_STARTING;
 445  
 
 446  10
         globalTransactions = Collections.synchronizedMap(new HashMap());
 447  10
         lockManager = new GenericLockManager(LOCK_COMMIT, logger);
 448  10
         globalOpenResources = Collections.synchronizedList(new ArrayList());
 449  
 
 450  10
         recover();
 451  10
         sync();
 452  
 
 453  10
         operationMode = OPERATION_MODE_STARTED;
 454  
 
 455  10
         if (dirty) {
 456  0
             logger.logWarning("Started RM, but in dirty mode only (Recovery of pending transactions failed)");
 457  
         } else {
 458  10
             logger.logInfo("Started RM");
 459  
         }
 460  
 
 461  10
     }
 462  
 
 463  
     public synchronized boolean stop(int mode) throws ResourceManagerSystemException {
 464  0
         return stop(mode, getDefaultTransactionTimeout() * DEFAULT_COMMIT_TIMEOUT_FACTOR);
 465  
     }
 466  
 
 467  
     public synchronized boolean stop(int mode, long timeOut) throws ResourceManagerSystemException {
 468  
 
 469  8
         logger.logInfo("Stopping RM at '" + storeDir + "' / '" + workDir + "'");
 470  
 
 471  8
         operationMode = OPERATION_MODE_STOPPING;
 472  
 
 473  8
         sync();
 474  8
         boolean success = shutdown(mode, timeOut);
 475  
 
 476  8
         releaseGlobalOpenResources();
 477  
 
 478  8
         if (success) {
 479  8
             operationMode = OPERATION_MODE_STOPPED;
 480  8
             logger.logInfo("Stopped RM");
 481  
         } else {
 482  0
             logger.logWarning("Failed to stop RM");
 483  
         }
 484  
 
 485  8
         return success;
 486  
     }
 487  
 
 488  
     public synchronized boolean recover() throws ResourceManagerSystemException {
 489  13
         if (operationMode != OPERATION_MODE_STARTED && operationMode != OPERATION_MODE_STARTING) {
 490  1
             throw new ResourceManagerSystemException(
 491  
                 ERR_SYSTEM,
 492  
                 "Recovery is possible in started or starting resource manager only");
 493  
         }
 494  12
         int oldMode = operationMode;
 495  12
         operationMode = OPERATION_MODE_RECOVERING;
 496  
 
 497  12
         recoverContexts();
 498  12
         if (globalTransactions.size() > 0) {
 499  8
             logger.logInfo("Recovering pending transactions");
 500  
         }
 501  
 
 502  12
         dirty = !rollBackOrForward();
 503  
 
 504  12
         operationMode = oldMode;
 505  12
         return dirty;
 506  
     }
 507  
 
 508  
     public int getTransactionState(Object txId) throws ResourceManagerException {
 509  0
         TransactionContext context = getContext(txId);
 510  
 
 511  0
         if (context == null) {
 512  0
             return STATUS_NO_TRANSACTION;
 513  
         } else {
 514  0
             return context.status;
 515  
         }
 516  
 
 517  
     }
 518  
 
 519  
     public void startTransaction(Object txId) throws ResourceManagerException {
 520  
 
 521  56
         if (logger.isFineEnabled()) logger.logFine("Starting Tx " + txId);
 522  
 
 523  56
         assureStarted(); // can only start a new transaction when not already stopping
 524  56
         if (txId == null || txIdMapper.getPathForId(txId).length() == 0) {
 525  0
             throw new ResourceManagerException(ERR_TXID_INVALID, txId);
 526  
         }
 527  
 
 528  
         // be sure we are the only ones who create this tx 
 529  56
         synchronized (globalTransactions) {
 530  56
             TransactionContext context = getContext(txId);
 531  
 
 532  56
             if (context != null) {
 533  0
                 throw new ResourceManagerException(ERR_DUP_TX, txId);
 534  
             }
 535  
 
 536  56
             context = new TransactionContext(txId);
 537  56
             context.init();
 538  56
             globalTransactions.put(txId, context);
 539  
 
 540  56
         }
 541  56
     }
 542  
 
 543  
     public void markTransactionForRollback(Object txId) throws ResourceManagerException {
 544  0
         assureRMReady();
 545  0
         TransactionContext context = txInitialSaneCheckForWriting(txId);
 546  
         try {
 547  0
             context.status = STATUS_MARKED_ROLLBACK;
 548  0
             context.saveState();
 549  0
         } finally {
 550  
             // be very sure to free locks and resources, as application might crash or otherwise forget to roll this tx back
 551  0
             context.finalCleanUp();
 552  0
         }
 553  0
     }
 554  
 
 555  
     public int prepareTransaction(Object txId) throws ResourceManagerException {
 556  0
         assureRMReady();
 557  
         // do not allow any further writing or commit or rollback when db is corrupt
 558  0
         if (dirty) {
 559  0
             throw new ResourceManagerSystemException(
 560  
                 "Database is set to dirty, this *may* mean it is corrupt. No modifications are allowed until a recovery run has been performed!",
 561  
                 ERR_SYSTEM,
 562  
                 txId);
 563  
         }
 564  
 
 565  0
         if (txId == null) {
 566  0
             throw new ResourceManagerException(ERR_TXID_INVALID, txId);
 567  
         }
 568  
 
 569  0
         TransactionContext context = getContext(txId);
 570  
 
 571  0
         if (context == null) {
 572  0
             return PREPARE_FAILURE;
 573  
         }
 574  
 
 575  0
         synchronized (context) {
 576  
 
 577  0
             sync();
 578  
 
 579  0
             if (context.status != STATUS_ACTIVE) {
 580  0
                 context.status = STATUS_MARKED_ROLLBACK;
 581  0
                 context.saveState();
 582  0
                 return PREPARE_FAILURE;
 583  
             }
 584  
 
 585  0
             if (logger.isFineEnabled()) logger.logFine("Preparing Tx " + txId);
 586  
 
 587  0
             int prepareStatus = PREPARE_FAILURE;
 588  
 
 589  0
             context.status = STATUS_PREPARING;
 590  0
             context.saveState();
 591  
             // do all checks as early as possible
 592  0
             context.closeResources();
 593  0
             if (context.readOnly) {
 594  0
                 prepareStatus = PREPARE_SUCCESS_READONLY;
 595  
             } else {
 596  
                 // do all checks as early as possible
 597  
                 try {
 598  0
                     context.upgradeLockToCommit();
 599  0
                 } catch (ResourceManagerException rme) {
 600  
                     // if this did not work, mark it for roll back as early as possible
 601  0
                     markTransactionForRollback(txId);
 602  0
                     throw rme;
 603  0
                 }
 604  0
                 prepareStatus = PREPARE_SUCCESS;
 605  
             }
 606  0
             context.status = STATUS_PREPARED;
 607  0
             context.saveState();
 608  0
             if (logger.isFineEnabled()) logger.logFine("Prepared Tx " + txId);
 609  
 
 610  0
             return prepareStatus;
 611  0
         }
 612  
     }
 613  
 
 614  
     public void rollbackTransaction(Object txId) throws ResourceManagerException {
 615  45
         assureRMReady();
 616  45
         TransactionContext context = txInitialSaneCheckForWriting(txId);
 617  
         // needing synchronization in order not to interfer with shutdown thread
 618  45
         synchronized (context) {
 619  
             try {
 620  
 
 621  45
                 if (logger.isFineEnabled()) logger.logFine("Rolling back Tx " + txId);
 622  
 
 623  45
                 context.status = STATUS_ROLLING_BACK;
 624  45
                 context.saveState();
 625  45
                 context.rollback();
 626  45
                 if (logger.isFineEnabled()) logger.logFine("All resources successfully removed for tx" + txId);
 627  45
                 context.status = STATUS_ROLLEDBACK;
 628  45
                 context.saveState();
 629  45
                 globalTransactions.remove(txId);
 630  45
                 context.cleanUp();
 631  
 
 632  45
                 if (logger.isFineEnabled()) logger.logFine("Rolled back Tx " + txId);
 633  
 
 634  
                 // any system or runtime exceptions or errors thrown in rollback means we are in deep trouble, set the dirty flag
 635  45
             } catch (Error e) {
 636  0
                 setDirty(txId, e);
 637  0
                 throw e;
 638  0
             } catch (RuntimeException e) {
 639  0
                 setDirty(txId, e);
 640  0
                 throw e;
 641  0
             } catch (ResourceManagerSystemException e) {
 642  0
                 setDirty(txId, e);
 643  0
                 throw e;
 644  
             } finally {
 645  0
                 context.finalCleanUp();
 646  
                 // tell shutdown thread this tx is finished
 647  45
                 context.notifyFinish();
 648  45
             }
 649  45
         }
 650  45
     }
 651  
 
 652  
     public void commitTransaction(Object txId) throws ResourceManagerException {
 653  10
         assureRMReady();
 654  10
         TransactionContext context = txInitialSaneCheckForWriting(txId);
 655  9
         assureNotMarkedForRollback(context);
 656  
 
 657  
         // needing synchronization in order not to interfer with shutdown thread
 658  9
         synchronized (context) {
 659  
             try {
 660  
 
 661  9
                 if (logger.isFineEnabled()) logger.logFine("Committing Tx " + txId);
 662  
 
 663  9
                 context.status = STATUS_COMMITTING;
 664  9
                 context.saveState();
 665  9
                 context.commit();
 666  9
                 if (logger.isFineEnabled()) logger.logFine("All resources successfully moved for tx" + txId);
 667  9
                 context.status = STATUS_COMMITTED;
 668  9
                 context.saveState();
 669  9
                 globalTransactions.remove(txId);
 670  9
                 context.cleanUp();
 671  
 
 672  9
                 if (logger.isFineEnabled()) logger.logFine("Committed Tx " + txId);
 673  
 
 674  
                 // any system or runtime exceptions or errors thrown in rollback means we are in deep trouble, set the dirty flag
 675  9
             } catch (Error e) {
 676  0
                 setDirty(txId, e);
 677  0
                 throw e;
 678  0
             } catch (RuntimeException e) {
 679  0
                 setDirty(txId, e);
 680  0
                 throw e;
 681  0
             } catch (ResourceManagerSystemException e) {
 682  0
                 setDirty(txId, e);
 683  0
                 throw e;
 684  
                 // like "could not upgrade lock"
 685  0
             } catch (ResourceManagerException e) {
 686  0
                 logger.logWarning("Could not commit tx " + txId + ", rolling back instead", e);
 687  0
                 rollbackTransaction(txId);
 688  0
             } finally {
 689  0
                 context.finalCleanUp();
 690  
                 // tell shutdown thread this tx is finished
 691  9
                 context.notifyFinish();
 692  9
             }
 693  9
         }
 694  9
     }
 695  
 
 696  
     public boolean resourceExists(Object resourceId) throws ResourceManagerException {
 697  
         // create temporary light weight tx
 698  
         Object txId;
 699  
         TransactionContext context;
 700  0
         synchronized (globalTransactions) {
 701  0
             txId = generatedUniqueTxId();
 702  0
             if (logger.isFinerEnabled())
 703  0
                 logger.logFiner("Creating temporary light weight tx " + txId + " to check for exists");
 704  0
             context = new TransactionContext(txId);
 705  0
             context.isLightWeight = true;
 706  
             // XXX higher isolation might be needed to make sure upgrade to commit lock always works
 707  0
             context.isolationLevel = ISOLATION_LEVEL_READ_COMMITTED;
 708  
             // context.isolationLevel = ISOLATION_LEVEL_REPEATABLE_READ;
 709  0
             globalTransactions.put(txId, context);
 710  0
         }
 711  
 
 712  0
         boolean exists = resourceExists(txId, resourceId);
 713  
 
 714  0
         context.freeLocks();
 715  0
         globalTransactions.remove(txId);
 716  0
         if (logger.isFinerEnabled())
 717  0
             logger.logFiner("Removing temporary light weight tx " + txId);
 718  
 
 719  0
         return exists;
 720  
     }
 721  
 
 722  
     public boolean resourceExists(Object txId, Object resourceId) throws ResourceManagerException {
 723  0
         lockResource(resourceId, txId, true);
 724  0
         return (getPathForRead(txId, resourceId) != null);
 725  
     }
 726  
 
 727  
     public void deleteResource(Object txId, Object resourceId) throws ResourceManagerException {
 728  55
         deleteResource(txId, resourceId, true);
 729  33
     }
 730  
 
 731  
     public void deleteResource(Object txId, Object resourceId, boolean assureOnly) throws ResourceManagerException {
 732  
 
 733  55
         if (logger.isFineEnabled()) logger.logFine(txId + " deleting " + resourceId);
 734  
 
 735  55
         lockResource(resourceId, txId, false);
 736  
 
 737  33
         if (getPathForRead(txId, resourceId) == null) {
 738  19
             if (assureOnly) {
 739  19
                 return;
 740  
             }
 741  0
             throw new ResourceManagerException("No such resource at '" + resourceId + "'", ERR_NO_SUCH_RESOURCE, txId);
 742  
         }
 743  14
         String txDeletePath = getDeletePath(txId, resourceId);
 744  14
         String mainPath = getMainPath(resourceId);
 745  
         try {
 746  14
             getContext(txId).readOnly = false;
 747  
 
 748  
             // first undo change / create when there was one
 749  14
             undoScheduledChangeOrCreate(txId, resourceId);
 750  
 
 751  
             // if there still is a file in main store, we need to schedule
 752  
             // a delete additionally
 753  14
             if (FileHelper.fileExists(mainPath)) {
 754  12
                 FileHelper.createFile(txDeletePath);
 755  
             }
 756  0
         } catch (IOException e) {
 757  0
             throw new ResourceManagerSystemException(
 758  
                 "Can not delete resource at '" + resourceId + "'",
 759  
                 ERR_SYSTEM,
 760  
                 txId,
 761  
                 e);
 762  14
         }
 763  14
     }
 764  
 
 765  
     public void createResource(Object txId, Object resourceId) throws ResourceManagerException {
 766  61
         createResource(txId, resourceId, true);
 767  37
     }
 768  
 
 769  
     public void createResource(Object txId, Object resourceId, boolean assureOnly) throws ResourceManagerException {
 770  
 
 771  61
         if (logger.isFineEnabled()) logger.logFine(txId + " creating " + resourceId);
 772  
 
 773  61
         lockResource(resourceId, txId, false);
 774  
 
 775  37
         if (getPathForRead(txId, resourceId) != null) {
 776  12
             if (assureOnly) {
 777  12
                 return;
 778  
             }
 779  0
             throw new ResourceManagerException(
 780  
                 "Resource at '" + resourceId + "', already exists",
 781  
                 ERR_RESOURCE_EXISTS,
 782  
                 txId);
 783  
         }
 784  
 
 785  25
         String txChangePath = getChangePath(txId, resourceId);
 786  
         try {
 787  25
             getContext(txId).readOnly = false;
 788  
             
 789  
             // creation means either undoing a delete or actually scheduling a create
 790  25
             if (!undoScheduledDelete(txId, resourceId)) {
 791  25
                 FileHelper.createFile(txChangePath);
 792  
             }
 793  
 
 794  0
         } catch (IOException e) {
 795  0
             throw new ResourceManagerSystemException(
 796  
                 "Can not create resource at '" + resourceId + "'",
 797  
                 ERR_SYSTEM,
 798  
                 txId,
 799  
                 e);
 800  25
         }
 801  25
     }
 802  
 
 803  
     public void copyResource(Object txId, Object fromResourceId, Object toResourceId, boolean overwrite) throws ResourceManagerException {
 804  0
         if (logger.isFineEnabled()) logger.logFine(txId + " copying " + fromResourceId + " to " + toResourceId);
 805  
 
 806  0
         lockResource(fromResourceId, txId, true);
 807  0
         lockResource(toResourceId, txId, false);
 808  
 
 809  0
         if (resourceExists(txId, toResourceId) && !overwrite) {
 810  0
             throw new ResourceManagerException(
 811  
                 "Resource at '" + toResourceId + "' already exists",
 812  
                 ERR_RESOURCE_EXISTS,
 813  
                 txId);
 814  
         }
 815  
 
 816  0
         InputStream fromResourceStream = null;
 817  0
         OutputStream toResourceStream = null;
 818  
         try {
 819  0
             fromResourceStream = readResource(txId, fromResourceId);
 820  0
             toResourceStream = writeResource(txId, toResourceId);
 821  0
             FileHelper.copy(fromResourceStream, toResourceStream);
 822  0
         } catch (IOException e) {
 823  0
             throw new ResourceManagerException(ERR_SYSTEM, txId, e);
 824  
         } finally {
 825  0
             closeOpenResource(fromResourceStream);
 826  0
             closeOpenResource(toResourceStream);
 827  0
         }
 828  0
     }
 829  
 
 830  
     public void moveResource(Object txId, Object fromResourceId, Object toResourceId, boolean overwrite) throws ResourceManagerException {
 831  0
         if (logger.isFineEnabled()) logger.logFine(txId + " moving " + fromResourceId + " to " + toResourceId);
 832  
 
 833  0
         lockResource(fromResourceId, txId, false);
 834  0
         lockResource(toResourceId, txId, false);
 835  
 
 836  0
         copyResource(txId, fromResourceId, toResourceId, overwrite);
 837  
 
 838  0
         deleteResource(txId, fromResourceId, false);
 839  0
     }
 840  
 
 841  
     public InputStream readResource(Object resourceId) throws ResourceManagerException {
 842  
         // create temporary light weight tx
 843  
         Object txId;
 844  1
         synchronized (globalTransactions) {
 845  1
             txId = generatedUniqueTxId();
 846  1
             if (logger.isFinerEnabled())
 847  0
                 logger.logFiner("Creating temporary light weight tx " + txId + " for reading");
 848  1
             TransactionContext context = new TransactionContext(txId);
 849  1
             context.isLightWeight = true;
 850  
             // XXX higher isolation might be needed to make sure upgrade to commit lock always works
 851  1
             context.isolationLevel = ISOLATION_LEVEL_READ_COMMITTED;
 852  
             // context.isolationLevel = ISOLATION_LEVEL_REPEATABLE_READ;
 853  1
             globalTransactions.put(txId, context);
 854  1
         }
 855  
 
 856  1
         InputStream is = readResource(txId, resourceId);
 857  1
         return is;
 858  
     }
 859  
 
 860  
     public InputStream readResource(Object txId, Object resourceId) throws ResourceManagerException {
 861  
 
 862  5
         if (logger.isFineEnabled()) logger.logFine(txId + " reading " + resourceId);
 863  
 
 864  5
         lockResource(resourceId, txId, true);
 865  
 
 866  5
         String resourcePath = getPathForRead(txId, resourceId);
 867  5
         if (resourcePath == null) {
 868  0
             throw new ResourceManagerException("No such resource at '" + resourceId + "'", ERR_NO_SUCH_RESOURCE, txId);
 869  
         }
 870  
 
 871  5
         File file = new File(resourcePath);
 872  
         try {
 873  5
             FileInputStream stream = new FileInputStream(file);
 874  5
             getContext(txId).registerResource(stream);
 875  5
             return new InputStreamWrapper(stream, txId, resourceId);
 876  0
         } catch (FileNotFoundException e) {
 877  0
             throw new ResourceManagerSystemException("File '" + resourcePath + "' does not exist", ERR_SYSTEM, txId);
 878  
         }
 879  
     }
 880  
 
 881  
     public OutputStream writeResource(Object txId, Object resourceId) throws ResourceManagerException {
 882  1
         return writeResource(txId, resourceId, false);
 883  
     }
 884  
 
 885  
     public OutputStream writeResource(Object txId, Object resourceId, boolean append) throws ResourceManagerException {
 886  
 
 887  1
         if (logger.isFineEnabled()) logger.logFine(txId + " writing " + resourceId);
 888  
 
 889  1
         lockResource(resourceId, txId, false);
 890  
 
 891  1
         if (append) {
 892  0
             String mainPath = getMainPath(resourceId);
 893  0
             String txChangePath = getChangePath(txId, resourceId);
 894  0
             String txDeletePath = getDeletePath(txId, resourceId);
 895  
 
 896  0
             boolean changeExists = FileHelper.fileExists(txChangePath);
 897  0
             boolean deleteExists = FileHelper.fileExists(txDeletePath);
 898  0
             boolean mainExists = FileHelper.fileExists(mainPath);
 899  
 
 900  0
             if (mainExists && !changeExists && !deleteExists) {
 901  
                 // the read and the write path for resourceId will be different!
 902  0
                 copyResource(txId, resourceId, resourceId, true);
 903  
             }
 904  
         }
 905  
 
 906  1
         String resourcePath = getPathForWrite(txId, resourceId);
 907  
 
 908  
         try {
 909  1
             FileOutputStream stream = new FileOutputStream(resourcePath, append);
 910  1
             TransactionContext context = getContext(txId);
 911  1
             context.registerResource(stream);
 912  1
             context.readOnly = false;
 913  1
             return stream;
 914  0
         } catch (FileNotFoundException e) {
 915  0
             throw new ResourceManagerSystemException("File '" + resourcePath + "' does not exist", ERR_SYSTEM, txId);
 916  
         }
 917  
     }
 918  
 
 919  
     /*
 920  
      * --- additional public methods complementing implementation of interfaces ---
 921  
      *
 922  
      *  
 923  
      */
 924  
 
 925  
     /**
 926  
      * Resets the store by deleting work <em>and</em> store directory.
 927  
      */
 928  
     public synchronized void reset() {
 929  0
         FileHelper.removeRec(new File(storeDir));
 930  0
         FileHelper.removeRec(new File(workDir));
 931  0
         new File(storeDir).mkdirs();
 932  0
         new File(workDir).mkdirs();
 933  0
     }
 934  
 
 935  
     /**
 936  
      * Synchronizes persistent data with caches. Is implemented with an empty
 937  
      * body, but called by other methods relying on synchronization. Subclasses
 938  
      * that utilize caching must implement this method reasonably.
 939  
      * 
 940  
      * @throws ResourceManagerSystemException if anything fatal hapened during synchonization
 941  
      */
 942  
     public synchronized void sync() throws ResourceManagerSystemException {
 943  18
     }
 944  
 
 945  
     /**
 946  
      * Generates a transaction identifier unique to this resource manager. To do so
 947  
      * it requires this resource manager to be started.
 948  
      * 
 949  
      * @return generated transaction identifier
 950  
      * @throws ResourceManagerSystemException if this resource manager has not been started, yet
 951  
      */
 952  
     public String generatedUniqueTxId() throws ResourceManagerSystemException {
 953  1
         assureRMReady();
 954  
         String txId;
 955  1
         synchronized (globalTransactions) {
 956  
             do {
 957  1
                 txId = Long.toHexString(System.currentTimeMillis()) + "-"
 958  
                         + Integer.toHexString(idCnt++);
 959  
                 // XXX busy loop
 960  1
             } while (getContext(txId) != null);
 961  1
         }
 962  1
         return txId;
 963  
     }
 964  
 
 965  
     /*
 966  
      * --- sane checks ---
 967  
      *
 968  
      *  
 969  
      */
 970  
 
 971  
     protected void fileInitialSaneCheck(Object txId, Object path) throws ResourceManagerException {
 972  121
         if (path == null || path.toString().length() == 0) {
 973  0
             throw new ResourceManagerException(ERR_RESOURCEID_INVALID, txId);
 974  
         }
 975  121
     }
 976  
 
 977  
     protected void assureStarted() throws ResourceManagerSystemException {
 978  56
         if (operationMode != OPERATION_MODE_STARTED) {
 979  0
             throw new ResourceManagerSystemException("Resource Manager Service not started", ERR_SYSTEM, null);
 980  
         }
 981  56
     }
 982  
 
 983  
     protected void assureRMReady() throws ResourceManagerSystemException {
 984  404
         if (operationMode != OPERATION_MODE_STARTED && operationMode != OPERATION_MODE_STOPPING) {
 985  0
             throw new ResourceManagerSystemException("Resource Manager Service not ready", ERR_SYSTEM, null);
 986  
         }
 987  404
     }
 988  
 
 989  
     protected void assureNotMarkedForRollback(TransactionContext context) throws ResourceManagerException {
 990  130
         if (context.status == STATUS_MARKED_ROLLBACK) {
 991  0
             throw new ResourceManagerException(ERR_MARKED_FOR_ROLLBACK, context.txId);
 992  
         }
 993  130
     }
 994  
 
 995  
     protected TransactionContext txInitialSaneCheckForWriting(Object txId) throws ResourceManagerException {
 996  172
         assureRMReady();
 997  
         // do not allow any further writing or commit or rollback when db is corrupt
 998  172
         if (dirty) {
 999  2
             throw new ResourceManagerSystemException(
 1000  
                 "Database is set to dirty, this *may* mean it is corrupt. No modifications are allowed until a recovery run has been performed!",
 1001  
                 ERR_SYSTEM,
 1002  
                 txId);
 1003  
         }
 1004  170
         return txInitialSaneCheck(txId);
 1005  
     }
 1006  
 
 1007  
     protected TransactionContext txInitialSaneCheck(Object txId) throws ResourceManagerException {
 1008  175
         assureRMReady();
 1009  175
         if (txId == null) {
 1010  0
             throw new ResourceManagerException(ERR_TXID_INVALID, txId);
 1011  
         }
 1012  
 
 1013  175
         TransactionContext context = getContext(txId);
 1014  
 
 1015  175
         if (context == null) {
 1016  0
             throw new ResourceManagerException(ERR_NO_TX, txId);
 1017  
         }
 1018  
 
 1019  175
         return context;
 1020  
     }
 1021  
 
 1022  
     /*
 1023  
      * --- General Helpers ---
 1024  
      *
 1025  
      *  
 1026  
      */
 1027  
 
 1028  
     protected TransactionContext getContext(Object txId) {
 1029  282
         return (TransactionContext) globalTransactions.get(txId);
 1030  
     }
 1031  
 
 1032  
     protected String assureLeadingSlash(Object pathObject) {
 1033  318
         String path = "";
 1034  318
         if (pathObject != null) {
 1035  318
             if (idMapper != null) {
 1036  0
                 path = idMapper.getPathForId(pathObject);
 1037  
             } else {
 1038  318
                 path = pathObject.toString();
 1039  
             }
 1040  318
             if (path.length() > 0 && path.charAt(0) != '/' && path.charAt(0) != '\\') {
 1041  228
                 path = "/" + path;
 1042  
             }
 1043  
         }
 1044  318
         return path;
 1045  
     }
 1046  
 
 1047  
     protected String getMainPath(Object path) {
 1048  89
         StringBuffer buf = new StringBuffer(storeDir.length() + path.toString().length() + 5);
 1049  89
         buf.append(storeDir).append(assureLeadingSlash(path));
 1050  89
         return buf.toString();
 1051  
     }
 1052  
 
 1053  
     protected String getTransactionBaseDir(Object txId) {
 1054  550
         return workDir + '/' + txIdMapper.getPathForId(txId);
 1055  
     }
 1056  
 
 1057  
     protected String getChangePath(Object txId, Object path) {
 1058  115
         String txBaseDir = getTransactionBaseDir(txId);
 1059  115
         StringBuffer buf = new StringBuffer(txBaseDir.length() + path.toString().length()
 1060  
                 + WORK_CHANGE_DIR.length() + 5);
 1061  115
         buf.append(txBaseDir).append('/').append(WORK_CHANGE_DIR).append(assureLeadingSlash(path));
 1062  115
         return buf.toString();
 1063  
     }
 1064  
 
 1065  
     protected String getDeletePath(Object txId, Object path) {
 1066  114
         String txBaseDir = getTransactionBaseDir(txId);
 1067  114
         StringBuffer buf = new StringBuffer(txBaseDir.length() + path.toString().length()
 1068  
                 + WORK_DELETE_DIR.length() + 5);
 1069  114
         buf.append(txBaseDir).append('/').append(WORK_DELETE_DIR).append(assureLeadingSlash(path));
 1070  114
         return buf.toString();
 1071  
     }
 1072  
 
 1073  
     protected boolean undoScheduledDelete(Object txId, Object resourceId) throws ResourceManagerException {
 1074  25
         String txDeletePath = getDeletePath(txId, resourceId);
 1075  25
         File deleteFile = new File(txDeletePath);
 1076  25
         if (deleteFile.exists()) {
 1077  0
             if (!deleteFile.delete()) {
 1078  0
                 throw new ResourceManagerSystemException(
 1079  
                     "Failed to undo delete of '" + resourceId + "'",
 1080  
                     ERR_SYSTEM,
 1081  
                     txId);
 1082  
             }
 1083  0
             return true;
 1084  
         }
 1085  25
         return false;
 1086  
     }
 1087  
 
 1088  
     protected boolean undoScheduledChangeOrCreate(Object txId, Object resourceId) throws ResourceManagerException {
 1089  14
         String txChangePath = getChangePath(txId, resourceId);
 1090  14
         File changeFile = new File(txChangePath);
 1091  14
         if (changeFile.exists()) {
 1092  2
             if (!changeFile.delete()) {
 1093  0
                 throw new ResourceManagerSystemException(
 1094  
                     "Failed to undo change / create of '" + resourceId + "'",
 1095  
                     ERR_SYSTEM,
 1096  
                     txId);
 1097  
             }
 1098  2
             return true;
 1099  
         }
 1100  12
         return false;
 1101  
     }
 1102  
 
 1103  
     protected String getPathForWrite(Object txId, Object resourceId) throws ResourceManagerException {
 1104  
         try {
 1105  
             // when we want to write, be sure to write to a local copy
 1106  1
             String txChangePath = getChangePath(txId, resourceId);
 1107  1
             if (!FileHelper.fileExists(txChangePath)) {
 1108  1
                 FileHelper.createFile(txChangePath);
 1109  
             }
 1110  1
             return txChangePath;
 1111  0
         } catch (IOException e) {
 1112  0
             throw new ResourceManagerSystemException(
 1113  
                 "Can not write to resource at '" + resourceId + "'",
 1114  
                 ERR_SYSTEM,
 1115  
                 txId,
 1116  
                 e);
 1117  
         }
 1118  
     }
 1119  
 
 1120  
     protected String getPathForRead(Object txId, Object resourceId) throws ResourceManagerException {
 1121  
 
 1122  75
         String mainPath = getMainPath(resourceId);
 1123  75
         String txChangePath = getChangePath(txId, resourceId);
 1124  75
         String txDeletePath = getDeletePath(txId, resourceId);
 1125  
 
 1126  
         // now, this gets a bit complicated:
 1127  
 
 1128  75
         boolean changeExists = FileHelper.fileExists(txChangePath);
 1129  75
         boolean deleteExists = FileHelper.fileExists(txDeletePath);
 1130  75
         boolean mainExists = FileHelper.fileExists(mainPath);
 1131  75
         boolean resourceIsDir =
 1132  
             ((mainExists && new File(mainPath).isDirectory())
 1133  
                 || (changeExists && new File(txChangePath).isDirectory()));
 1134  75
         if (resourceIsDir) {
 1135  0
             logger.logWarning("Resource at '" + resourceId + "' maps to directory");
 1136  
         }
 1137  
 
 1138  
         // first do some sane checks
 1139  
 
 1140  
         // this may never be, two cases are possible, both disallowing to have a delete together with a change
 1141  
         // 1. first there was a change, than a delete -> at least delete file exists (when there is a file in main store)
 1142  
         // 2. first there was a delete, than a change -> only change file exists
 1143  75
         if (!resourceIsDir && changeExists && deleteExists) {
 1144  0
             throw new ResourceManagerSystemException(
 1145  
                 "Inconsistent delete and change combination for resource at '" + resourceId + "'",
 1146  
                 ERR_TX_INCONSISTENT,
 1147  
                 txId);
 1148  
         }
 1149  
 
 1150  
         // you should not have been allowed to delete a file that does not exist at all
 1151  75
         if (deleteExists && !mainExists) {
 1152  0
             throw new ResourceManagerSystemException(
 1153  
                 "Inconsistent delete for resource at '" + resourceId + "'",
 1154  
                 ERR_TX_INCONSISTENT,
 1155  
                 txId);
 1156  
         }
 1157  
 
 1158  75
         if (changeExists) {
 1159  2
             return txChangePath;
 1160  73
         } else if (mainExists && !deleteExists) {
 1161  29
             return mainPath;
 1162  
         } else {
 1163  44
             return null;
 1164  
         }
 1165  
     }
 1166  
 
 1167  
     /*
 1168  
      * --- Locking Helpers ---
 1169  
      *
 1170  
      *  
 1171  
      */
 1172  
 
 1173  
     protected int getSharedLockLevel(TransactionContext context) throws ResourceManagerException {
 1174  5
         if (context.isolationLevel == ISOLATION_LEVEL_READ_COMMITTED
 1175  
             || context.isolationLevel == ISOLATION_LEVEL_READ_UNCOMMITTED) {
 1176  3
             return LOCK_ACCESS;
 1177  2
         } else if (
 1178  
             context.isolationLevel == ISOLATION_LEVEL_REPEATABLE_READ
 1179  
                 || context.isolationLevel == ISOLATION_LEVEL_SERIALIZABLE) {
 1180  2
             return LOCK_SHARED;
 1181  
         } else {
 1182  0
             return LOCK_ACCESS;
 1183  
         }
 1184  
     }
 1185  
 
 1186  
     /*
 1187  
      * --- Resource Management ---
 1188  
      *
 1189  
      *  
 1190  
      */
 1191  
 
 1192  
     protected void registerOpenResource(Object openResource) {
 1193  6
         if (logger.isFinerEnabled())
 1194  0
             logger.logFiner("Registering open resource " + openResource);
 1195  6
         globalOpenResources.add(openResource);
 1196  6
     }
 1197  
 
 1198  
     protected void releaseGlobalOpenResources() {
 1199  
         ArrayList copy;
 1200  8
         synchronized (globalOpenResources) {
 1201  
             // XXX need to copy in order to allow removal in releaseOpenResource  
 1202  8
             copy = new ArrayList(globalOpenResources);
 1203  8
             for (Iterator it = copy.iterator(); it.hasNext();) {
 1204  1
                 Object stream = it.next();
 1205  1
                 closeOpenResource(stream);
 1206  1
             }
 1207  8
         }
 1208  8
     }
 1209  
 
 1210  
     protected void closeOpenResource(Object openResource) {
 1211  10
         if (logger.isFinerEnabled()) logger.logFiner("Releasing resource " + openResource);
 1212  10
         globalOpenResources.remove(openResource);
 1213  10
         if (openResource instanceof InputStream) {
 1214  8
             InputStream is = (InputStream) openResource;
 1215  
             try {
 1216  8
                 is.close();
 1217  0
             } catch (IOException e) {
 1218  
                 // do not care, as it might have been closed somewhere else, before 
 1219  8
             }
 1220  8
         } else if (openResource instanceof OutputStream) {
 1221  2
             OutputStream os = (OutputStream) openResource;
 1222  
             try {
 1223  2
                 os.close();
 1224  0
             } catch (IOException e) {
 1225  
                 // do not care, as it might have been closed somewhere else, before 
 1226  2
             }
 1227  
         }
 1228  10
     }
 1229  
 
 1230  
     /*
 1231  
      * --- Recovery / Shutdown Support ---
 1232  
      *
 1233  
      *  
 1234  
      */
 1235  
 
 1236  
     protected boolean rollBackOrForward() {
 1237  12
         boolean allCool = true;
 1238  
 
 1239  12
         synchronized (globalTransactions) {
 1240  12
             ArrayList contexts = new ArrayList(globalTransactions.values());
 1241  12
             for (Iterator it = contexts.iterator(); it.hasNext();) {
 1242  12
                 TransactionContext context = (TransactionContext) it.next();
 1243  12
                 if (context.status == STATUS_COMMITTING) {
 1244  
                     // roll forward
 1245  3
                     logger.logInfo("Rolling forward " + context.txId);
 1246  
 
 1247  
                     try {
 1248  3
                         context.commit();
 1249  3
                         context.status = STATUS_COMMITTED;
 1250  3
                         context.saveState();
 1251  3
                         globalTransactions.remove(context.txId);
 1252  3
                         context.cleanUp();
 1253  0
                     } catch (ResourceManagerException e) {
 1254  
                         // this is not good, but what can we do now?
 1255  0
                         allCool = false;
 1256  0
                         logger.logSevere("Rolling forward of " + context.txId + " failed", e);
 1257  3
                     }
 1258  9
                 } else if (context.status == STATUS_COMMITTED) {
 1259  2
                     logger.logInfo("Cleaning already commited " + context.txId);
 1260  2
                     globalTransactions.remove(context.txId);
 1261  
                     try {
 1262  2
                         context.cleanUp();
 1263  0
                     } catch (ResourceManagerException e) {
 1264  
                         // this is not good, but what can we do now?
 1265  0
                         allCool = false;
 1266  0
                         logger.logWarning("Cleaning of " + context.txId + " failed", e);
 1267  2
                     }
 1268  
                 } else {
 1269  
                     // in all other cases roll back and warn when not rollback was explicitely selected for tx
 1270  7
                     if (context.status != STATUS_ROLLING_BACK
 1271  
                         && context.status != STATUS_ROLLEDBACK
 1272  
                         && context.status != STATUS_MARKED_ROLLBACK) {
 1273  3
                         logger.logWarning("Irregularly rolling back " + context.txId);
 1274  
                     } else {
 1275  4
                         logger.logInfo("Rolling back " + context.txId);
 1276  
                     }
 1277  
                     try {
 1278  7
                         context.rollback();
 1279  7
                         context.status = STATUS_ROLLEDBACK;
 1280  7
                         context.saveState();
 1281  7
                         globalTransactions.remove(context.txId);
 1282  7
                         context.cleanUp();
 1283  0
                     } catch (ResourceManagerException e) {
 1284  0
                         logger.logWarning("Rolling back of " + context.txId + " failed", e);
 1285  7
                     }
 1286  
                 }
 1287  12
             }
 1288  
 
 1289  12
         }
 1290  12
         return allCool;
 1291  
     }
 1292  
 
 1293  
     protected void recoverContexts() {
 1294  12
         File dir = new File(workDir);
 1295  12
         File[] files = dir.listFiles();
 1296  12
         if (files == null)
 1297  0
             return;
 1298  26
         for (int i = 0; i < files.length; i++) {
 1299  14
             File file = files[i];
 1300  14
             Object txId = txIdMapper.getIdForPath(file.getName());
 1301  
             // recover all transactions we do not already know
 1302  14
             if (!globalTransactions.containsKey(txId)) {
 1303  
 
 1304  12
                 logger.logInfo("Recovering " + txId);
 1305  
                 TransactionContext context;
 1306  
                 try {
 1307  12
                     context = new TransactionContext(txId);
 1308  12
                     context.recoverState();
 1309  10
                     globalTransactions.put(txId, context);
 1310  2
                 } catch (ResourceManagerException e) {
 1311  
                     // this is not good, but the best we get, just log as warning
 1312  2
                     logger.logWarning("Recovering of " + txId + " failed");
 1313  10
                 }
 1314  
             }
 1315  
         }
 1316  12
     }
 1317  
 
 1318  
     protected boolean waitForAllTxToStop(long timeoutMSecs) {
 1319  8
         long startTime = System.currentTimeMillis();
 1320  
 
 1321  
         // be sure not to lock globalTransactions for too long, as we need to give
 1322  
         // txs the chance to complete (otherwise deadlocks are very likely to occur)
 1323  
         // instead iterate over a copy as we can be sure no new txs will be registered
 1324  
         // after operation level has been set to stopping
 1325  
 
 1326  
         Collection transactionsToStop;
 1327  8
         synchronized (globalTransactions) {
 1328  8
             transactionsToStop = new ArrayList(globalTransactions.values());
 1329  8
         }
 1330  8
         for (Iterator it = transactionsToStop.iterator(); it.hasNext();) {
 1331  2
             long remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
 1332  
 
 1333  2
             if (remainingTimeout <= 0) {
 1334  0
                 return false;
 1335  
             }
 1336  
 
 1337  2
             TransactionContext context = (TransactionContext) it.next();
 1338  2
             synchronized (context) {
 1339  2
                 if (!context.finished) {
 1340  2
                     logger.logInfo(
 1341  
                         "Waiting for tx " + context.txId + " to finish for " + remainingTimeout + " milli seconds");
 1342  
                 }
 1343  4
                 while (!context.finished && remainingTimeout > 0) {
 1344  
                     try {
 1345  2
                         context.wait(remainingTimeout);
 1346  0
                     } catch (InterruptedException e) {
 1347  0
                         return false;
 1348  2
                     }
 1349  2
                     remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
 1350  
                 }
 1351  2
                 if (context.finished) {
 1352  2
                     logger.logInfo("Tx " + context.txId + " finished");
 1353  
                 } else {
 1354  0
                     logger.logWarning("Tx " + context.txId + " failed to finish in given time");
 1355  
                 }
 1356  2
             }
 1357  2
         }
 1358  
 
 1359  8
         return (globalTransactions.size() == 0);
 1360  
     }
 1361  
 
 1362  
     protected boolean shutdown(int mode, long timeoutMSecs) {
 1363  8
         switch (mode) {
 1364  
             case SHUTDOWN_MODE_NORMAL :
 1365  8
                 return waitForAllTxToStop(timeoutMSecs);
 1366  
             case SHUTDOWN_MODE_ROLLBACK :
 1367  0
                 return rollBackOrForward();
 1368  
             case SHUTDOWN_MODE_KILL :
 1369  0
                 return true;
 1370  
             default :
 1371  0
                 return false;
 1372  
         }
 1373  
     }
 1374  
 
 1375  
     protected void setDirty(Object txId, Throwable t) {
 1376  0
         logger.logSevere(
 1377  
             "Fatal error during critical commit/rollback of transaction " + txId + ", setting database to dirty.",
 1378  
             t);
 1379  0
         dirty = true;
 1380  0
     }
 1381  
 
 1382  
     /**
 1383  
      * Inner class to hold the complete context, i.e. all information needed, for a transaction.
 1384  
      * 
 1385  
      */
 1386  
     protected class TransactionContext {
 1387  
 
 1388  
         protected Object txId;
 1389  69
         protected int status = STATUS_ACTIVE;
 1390  69
         protected int isolationLevel = DEFAULT_ISOLATION_LEVEL;
 1391  69
         protected long timeoutMSecs = getDefaultTransactionTimeout();
 1392  
         protected long startTime;
 1393  69
         protected long commitTime = -1L;
 1394  69
         protected boolean isLightWeight = false;
 1395  69
         protected boolean readOnly = true;
 1396  69
         protected boolean finished = false;
 1397  
 
 1398  
         // list of streams participating in this tx
 1399  69
         private List openResources = new ArrayList();
 1400  
 
 1401  69
         public TransactionContext(Object txId) throws ResourceManagerException {
 1402  69
             this.txId = txId;
 1403  69
             startTime = System.currentTimeMillis();
 1404  69
         }
 1405  
 
 1406  
         public long getRemainingTimeout() {
 1407  0
             long now = System.currentTimeMillis();
 1408  0
             return (startTime - now + timeoutMSecs);
 1409  
         }
 1410  
 
 1411  
         public synchronized void init() throws ResourceManagerException {
 1412  56
             String baseDir = getTransactionBaseDir(txId);
 1413  56
             String changeDir = baseDir + "/" + WORK_CHANGE_DIR;
 1414  56
             String deleteDir = baseDir + "/" + WORK_DELETE_DIR;
 1415  
 
 1416  56
             new File(changeDir).mkdirs();
 1417  56
             new File(deleteDir).mkdirs();
 1418  
 
 1419  56
             saveState();
 1420  56
         }
 1421  
 
 1422  
         public synchronized void rollback() throws ResourceManagerException {
 1423  52
             closeResources();
 1424  52
             freeLocks();
 1425  52
         }
 1426  
 
 1427  
         public synchronized void commit() throws ResourceManagerException {
 1428  12
             String baseDir = getTransactionBaseDir(txId);
 1429  12
             String changeDir = baseDir + "/" + WORK_CHANGE_DIR;
 1430  12
             String deleteDir = baseDir + "/" + WORK_DELETE_DIR;
 1431  
 
 1432  12
             closeResources();
 1433  12
             upgradeLockToCommit();
 1434  
             try {
 1435  12
                 applyDeletes(new File(deleteDir), new File(storeDir), new File(storeDir));
 1436  12
                 FileHelper.moveRec(new File(changeDir), new File(storeDir));
 1437  0
             } catch (IOException e) {
 1438  0
                 throw new ResourceManagerSystemException("Commit failed", ERR_SYSTEM, txId, e);
 1439  12
             }
 1440  12
             freeLocks();
 1441  12
             commitTime = System.currentTimeMillis();
 1442  12
         }
 1443  
 
 1444  
         public synchronized void notifyFinish() {
 1445  55
             finished = true;
 1446  55
             notifyAll();
 1447  55
         }
 1448  
 
 1449  
         public synchronized void cleanUp() throws ResourceManagerException {
 1450  66
             if (!cleanUp)
 1451  0
                 return; // XXX for debugging only
 1452  66
             boolean clean = true;
 1453  66
             Exception cleanException = null;
 1454  66
             String baseDir = getTransactionBaseDir(txId);
 1455  66
             FileHelper.removeRec(new File(baseDir));
 1456  66
             if (!clean) {
 1457  0
                 throw new ResourceManagerSystemException(
 1458  
                     "Clean up failed due to unreleasable lock",
 1459  
                     ERR_SYSTEM,
 1460  
                     txId,
 1461  
                     cleanException);
 1462  
             }
 1463  66
         }
 1464  
 
 1465  
         public synchronized void finalCleanUp() throws ResourceManagerException {
 1466  55
             closeResources();
 1467  55
             freeLocks();
 1468  55
         }
 1469  
 
 1470  
         public synchronized void upgradeLockToCommit() throws ResourceManagerException {
 1471  12
             for (Iterator it =  lockManager.getAll(txId).iterator(); it.hasNext();) {
 1472  23
                 GenericLock lock = (GenericLock) it.next();
 1473  
                 // only upgrade if we had write access
 1474  23
                 if (lock.getLockLevel(txId) == LOCK_EXCLUSIVE) {
 1475  
                     try {
 1476  
                         // in case of deadlocks, make failure of non-committing tx more likely
 1477  21
                         if (!lock
 1478  
                             .acquire(
 1479  
                                 txId,
 1480  
                                 LOCK_COMMIT,
 1481  
                                 true,
 1482  
                                 true,
 1483  
                                 getDefaultTransactionTimeout() * DEFAULT_COMMIT_TIMEOUT_FACTOR)) {
 1484  0
                             throw new ResourceManagerException(
 1485  
                                 "Could not upgrade to commit lock for resource at '"
 1486  
                                     + lock.getResourceId().toString()
 1487  
                                     + "'",
 1488  
                                 ERR_NO_LOCK,
 1489  
                                 txId);
 1490  
                         }
 1491  0
                     } catch (InterruptedException e) {
 1492  0
                         throw new ResourceManagerSystemException(ERR_SYSTEM, txId, e);
 1493  21
                     }
 1494  
                 }
 1495  
 
 1496  23
             }
 1497  12
         }
 1498  
 
 1499  
         public synchronized void freeLocks() {
 1500  120
             lockManager.releaseAll(txId);
 1501  120
         }
 1502  
 
 1503  
         public synchronized void closeResources() {
 1504  119
             synchronized (globalOpenResources) {
 1505  119
                 for (Iterator it = openResources.iterator(); it.hasNext();) {
 1506  9
                     Object stream = it.next();
 1507  9
                     closeOpenResource(stream);
 1508  9
                 }
 1509  119
             }
 1510  119
         }
 1511  
 
 1512  
         public synchronized void registerResource(Object openResource) {
 1513  6
             synchronized (globalOpenResources) {
 1514  6
                 registerOpenResource(openResource);
 1515  6
                 openResources.add(openResource);
 1516  6
             }
 1517  6
         }
 1518  
 
 1519  
         public synchronized void saveState() throws ResourceManagerException {
 1520  175
             String statePath = getTransactionBaseDir(txId) + "/" + CONTEXT_FILE;
 1521  175
             File file = new File(statePath);
 1522  175
             BufferedWriter writer = null;
 1523  
             try {
 1524  175
                 OutputStream os = new FileOutputStream(file);
 1525  175
                 writer = new BufferedWriter(new OutputStreamWriter(os, DEFAULT_PARAMETER_ENCODING));
 1526  175
                 writer.write(toString());
 1527  0
             } catch (FileNotFoundException e) {
 1528  0
                 String msg = "Saving status information to '" + statePath + "' failed! Could not create file";
 1529  0
                 logger.logSevere(msg, e);
 1530  0
                 throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, e);
 1531  0
             } catch (IOException e) {
 1532  0
                 String msg = "Saving status information to '" + statePath + "' failed";
 1533  0
                 logger.logSevere(msg, e);
 1534  0
                 throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, e);
 1535  
             } finally {
 1536  175
                 if (writer != null) {
 1537  
                     try {
 1538  175
                         writer.close();
 1539  0
                     } catch (IOException e) {
 1540  175
                     }
 1541  
 
 1542  
                 }
 1543  
             }
 1544  175
         }
 1545  
 
 1546  
         public synchronized void recoverState() throws ResourceManagerException {
 1547  12
             String statePath = getTransactionBaseDir(txId) + "/" + CONTEXT_FILE;
 1548  12
             File file = new File(statePath);
 1549  12
             BufferedReader reader = null;
 1550  
             try {
 1551  12
                 InputStream is = new FileInputStream(file);
 1552  
 
 1553  10
                 reader = new BufferedReader(new InputStreamReader(is, DEFAULT_PARAMETER_ENCODING));
 1554  10
                 txId = reader.readLine();
 1555  10
                 status = Integer.parseInt(reader.readLine());
 1556  10
                 isolationLevel = Integer.parseInt(reader.readLine());
 1557  10
                 timeoutMSecs = Long.parseLong(reader.readLine());
 1558  10
                 startTime = Long.parseLong(reader.readLine());
 1559  2
             } catch (FileNotFoundException e) {
 1560  2
                 String msg = "Recovering status information from '" + statePath + "' failed! Could not find file";
 1561  2
                 logger.logSevere(msg, e);
 1562  2
                 throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId);
 1563  0
             } catch (IOException e) {
 1564  0
                 String msg = "Recovering status information from '" + statePath + "' failed";
 1565  0
                 logger.logSevere(msg, e);
 1566  0
                 throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, e);
 1567  0
             } catch (Throwable t) {
 1568  0
                 String msg = "Recovering status information from '" + statePath + "' failed";
 1569  0
                 logger.logSevere(msg, t);
 1570  0
                 throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, t);
 1571  
             } finally {
 1572  12
                 if (reader != null) {
 1573  
                     try {
 1574  10
                         reader.close();
 1575  0
                     } catch (IOException e) {
 1576  12
                     }
 1577  
 
 1578  
                 }
 1579  
             }
 1580  10
         }
 1581  
 
 1582  
         public synchronized String toString() {
 1583  175
             StringBuffer buf = new StringBuffer();
 1584  175
             buf.append(txId).append('\n');
 1585  175
             buf.append(Integer.toString(status)).append('\n');
 1586  175
             buf.append(Integer.toString(isolationLevel)).append('\n');
 1587  175
             buf.append(Long.toString(timeoutMSecs)).append('\n');
 1588  175
             buf.append(Long.toString(startTime)).append('\n');
 1589  175
             if (debug) {
 1590  175
                 buf.append("----- Lock Debug Info -----\n");
 1591  
 
 1592  175
                 for (Iterator it = lockManager.getAll(txId).iterator(); it.hasNext();) {
 1593  68
                     GenericLock lock = (GenericLock) it.next();
 1594  68
                     buf.append(lock.toString()+"\n");
 1595  68
                 }
 1596  
 
 1597  
             }
 1598  175
             return buf.toString();
 1599  
         }
 1600  
 
 1601  
     }
 1602  
 
 1603  
     private class InputStreamWrapper extends InputStream {
 1604  
         private InputStream is;
 1605  
         private Object txId;
 1606  
         private Object resourceId;
 1607  
 
 1608  5
         public InputStreamWrapper(InputStream is, Object txId, Object resourceId) {
 1609  5
             this.is = is;
 1610  5
             this.txId = txId;
 1611  5
             this.resourceId = resourceId;
 1612  5
         }
 1613  
 
 1614  
         public int read() throws IOException {
 1615  0
             return is.read();
 1616  
         }
 1617  
 
 1618  
         public int read(byte b[]) throws IOException {
 1619  0
             return is.read(b);
 1620  
         }
 1621  
 
 1622  
         public int read(byte b[], int off, int len) throws IOException {
 1623  3
             return is.read(b, off, len);
 1624  
         }
 1625  
 
 1626  
         public int available() throws IOException {
 1627  1
             return is.available();
 1628  
         }
 1629  
 
 1630  
         public void close() throws IOException {
 1631  
             try {
 1632  3
                 is.close();
 1633  3
             } finally {
 1634  0
                 TransactionContext context;
 1635  3
                 synchronized (globalTransactions) {
 1636  3
                     context = getContext(txId);
 1637  3
                     if (context == null) {
 1638  0
                         return;
 1639  
                     }
 1640  3
                 }
 1641  3
                 synchronized (context) {
 1642  3
                     if (context.isLightWeight) {
 1643  1
                         if (logger.isFinerEnabled())
 1644  0
                             logger.logFiner("Upon close of resource removing temporary light weight tx " + txId);
 1645  1
                         context.freeLocks();
 1646  1
                         globalTransactions.remove(txId);
 1647  
                     } else {
 1648  
                         // release access lock in order to allow other transactions to commit
 1649  2
                         if (lockManager.getLevel(txId, resourceId) == LOCK_ACCESS) {
 1650  1
                             if (logger.isFinerEnabled()) {
 1651  0
                                 logger.logFiner("Upon close of resource releasing access lock for tx " + txId + " on resource at " + resourceId);
 1652  
                             }
 1653  1
                             lockManager.release(txId, resourceId);
 1654  
                         }
 1655  
                     }
 1656  3
                 }
 1657  6
             }
 1658  3
         }
 1659  
 
 1660  
         public void mark(int readlimit) {
 1661  0
             is.mark(readlimit);
 1662  0
         }
 1663  
 
 1664  
         public void reset() throws IOException {
 1665  0
             is.reset();
 1666  0
         }
 1667  
 
 1668  
         public boolean markSupported() {
 1669  0
             return is.markSupported();
 1670  
 
 1671  
         }
 1672  
 
 1673  
     }
 1674  
 
 1675  
 }