KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > transaction > file > FileResourceManager


1 /*
2  * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//transaction/src/java/org/apache/commons/transaction/file/FileResourceManager.java,v 1.6 2005/01/07 13:32:33 ozeigermann Exp $
3  * $Revision$
4  * $Date: 2005-02-26 14:16:14 +0100 (Sa, 26 Feb 2005) $
5  *
6  * ====================================================================
7  *
8  * Copyright 1999-2002 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  *
22  */

23
24 package org.apache.commons.transaction.file;
25
26 import java.io.BufferedReader JavaDoc;
27 import java.io.BufferedWriter JavaDoc;
28 import java.io.File JavaDoc;
29 import java.io.FileInputStream JavaDoc;
30 import java.io.FileNotFoundException JavaDoc;
31 import java.io.FileOutputStream JavaDoc;
32 import java.io.IOException JavaDoc;
33 import java.io.InputStream JavaDoc;
34 import java.io.InputStreamReader JavaDoc;
35 import java.io.OutputStream JavaDoc;
36 import java.io.OutputStreamWriter JavaDoc;
37 import java.util.ArrayList JavaDoc;
38 import java.util.Collection JavaDoc;
39 import java.util.HashMap JavaDoc;
40 import java.util.List JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.Iterator JavaDoc;
43 import java.util.Collections JavaDoc;
44
45 import org.apache.commons.transaction.locking.GenericLock;
46 import org.apache.commons.transaction.locking.GenericLockManager;
47 import org.apache.commons.transaction.locking.LockException;
48 import org.apache.commons.transaction.locking.LockManager2;
49 import org.apache.commons.transaction.util.FileHelper;
50 import org.apache.commons.transaction.util.LoggerFacade;
51
52 /**
53  * A resource manager for streamable objects stored in a file system.
54  *
55  * It is intended for developper and "out of the box" use.
56  * It is <em>not</em> intended to be a real alternative for
57  * a full blown DMBS (of course it can not be compared to an RDBMS at all).
58  *
59  * Major features:<br>
60  * <ul>
61  * <li>Transactions performed with this class more or less comform to the widely accepted ACID properties
62  * <li>Reading should be as fast as from the ordinary file system (at the cost of a bit slower commits)
63  * </ul>
64  *
65  * Compared to a "real" DBMS major limitations are (in order of assumed severity):<br>
66  * <ul>
67  * <li>Number of simultaneuously open resources is limited to the number of available file descriptors
68  * <li>It does not scale a bit
69  * <li>Pessimistic transaction and locking scheme
70  * <li>Isolation level currently is restricted to <em>read committed</em> and <em>repeated read</em> (which is not that bad)
71  * </ul>
72  *
73  * <em>Important</em>: If possible you should have the work and store directory located in the
74  * same file system. If not, you might get additional problems, as there are:
75  * <ul>
76  * <li>On commit it might be necessay to copy files instead of rename/relink them. This may lead to time consuming,
77  * overly blocking commit phases and higher risk of corrupted files
78  * <li>Prepare phase might be too permissive, no check for sufficient memory on store file system is possible
79  * </ul>
80  *
81  * General limitations include:<br>
82  * <ul>
83  * <li>Due to lack of synchronization on the transaction context level, every transaction may only be
84  * accessed by a <em>single thread</em> throughout its full life.
85  * This means it is forbidden for a thread that has not started a transaction
86  * to perform any operations inside this transaction. However, threads associated
87  * with different transactions can safely access these methods concurrently.
88  * Reasons for the lack of synchronization are improved performance and simplicity (of the code of this class).
89  * <li>There is no dedicated class for a transaction. Having such a class would be better practice and
90  * make certain actions more intuitive.
91  * <li>Resource identifiers need a reasonsable string representation obtainable by <code>toString</code>.
92  * More specifically, they will have to resolve to a <em>valid</em> file path that does note denote a directoy.
93  * If it does, you might be able to create it, but not to read or write anything
94  * from resp. to it. Valid string representations of a resource idenfier are
95  * for example "file" "/root" or "hjfhdfhuhuhsdufhdsufhdsufhdfuhdfduhduhduhdu".
96  * Invalid are for example "/" or "/root/". Invalid on some file systems are for example "c:" or "file://huhu".
97  * <li>As there are no active processes inside this RM and it shares its threads with the application,
98  * control over transactions is limited to points where the application calls the RM.
99  * In particular, this disables <em>active</em> terminmation of transactions upon timeout.
100  * <li>There is no notion of a connection to this file manager. This means you can not connect from hosts other than
101  * local and you will get problems when plugging this store into a J2EE store using connectors.
102  * <li>Methods should throw more specific exceptions
103  * </ul>
104  *
105  * <em>Caution</em>:<br>
106  * The <code>txId</code> passed to many methods as an identifier for the
107  * transaction concerned will function as a key in a <code>HashMap</code>.
108  * Thus assure that <code>equals</code> and <code>hashCode</code> are both
109  * properly implemented and match each other.
110  *
111  *
112  * <em>Caution</em>: You will have to guarantee that no other process will access neither
113  * the store or the working dir concurrently to this <code>FileResourceManager</code>.
114  *
115  * <em>Special Caution</em>: Be very careful not to have two instances of
116  * <code>FileResourceManager</code> working in the same store and/or working dir.
117  *
118  * @version $Revision$
119  */

120 public class FileResourceManager implements ResourceManager, ResourceManagerErrorCodes {
121
122     // reflects the natural isolation level of this store
123
protected static final int NATIVE_ISOLATION_LEVEL = ISOLATION_LEVEL_REPEATABLE_READ;
124     protected static final int DEFAULT_ISOLATION_LEVEL = NATIVE_ISOLATION_LEVEL;
125
126     protected static final int NO_LOCK = 0;
127     protected static final int LOCK_ACCESS = NO_LOCK + 1;
128     protected static final int LOCK_SHARED = NO_LOCK + 2;
129     protected static final int LOCK_EXCLUSIVE = NO_LOCK + 3;
130     protected static final int LOCK_COMMIT = NO_LOCK + 4;
131
132     protected static final int OPERATION_MODE_STOPPED = 0;
133     protected static final int OPERATION_MODE_STOPPING = 1;
134     protected static final int OPERATION_MODE_STARTED = 2;
135     protected static final int OPERATION_MODE_STARTING = 3;
136     protected static final int OPERATION_MODE_RECOVERING = 4;
137
138     protected static final String JavaDoc DEFAULT_PARAMETER_ENCODING = "ISO-8859-15";
139
140     protected static final int DEFAULT_TIMEOUT_MSECS = 5000;
141     protected static final int DEFAULT_COMMIT_TIMEOUT_FACTOR = 2;
142
143     protected static final String JavaDoc WORK_CHANGE_DIR = "change";
144     protected static final String JavaDoc WORK_DELETE_DIR = "delete";
145
146     protected static final String JavaDoc CONTEXT_FILE = "transaction.log";
147
148     /*
149      * --- Static helper methods ---
150      *
151      *
152      */

153
154     protected static void applyDeletes(File JavaDoc removeDir, File JavaDoc targetDir, File JavaDoc rootDir) throws IOException JavaDoc {
155         if (removeDir.isDirectory() && targetDir.isDirectory()) {
156             File JavaDoc[] files = removeDir.listFiles();
157             for (int i = 0; i < files.length; i++) {
158                 File JavaDoc removeFile = files[i];
159                 File JavaDoc targetFile = new File JavaDoc(targetDir, removeFile.getName());
160                 if (removeFile.isFile()) {
161                     if (targetFile.exists()) {
162                         targetFile.delete();
163                     }
164                     // indicate, this has been done
165
removeFile.delete();
166                 } else {
167                     applyDeletes(removeFile, targetFile, rootDir);
168                 }
169                 // delete empty target directories, except root dir
170
if (!targetDir.equals(rootDir) && targetDir.list().length == 0) {
171                     targetDir.delete();
172                 }
173             }
174         }
175     }
176
177     /*
178      * --- object members ---
179      *
180      *
181      */

182
183     protected String JavaDoc workDir;
184     protected String JavaDoc storeDir;
185     protected boolean cleanUp = true;
186     protected boolean dirty = false;
187     protected int operationMode = OPERATION_MODE_STOPPED;
188     protected long defaultTimeout = DEFAULT_TIMEOUT_MSECS;
189     protected boolean debug;
190
191     protected LoggerFacade logger;
192
193     protected Map JavaDoc globalTransactions;
194     protected List JavaDoc globalOpenResources;
195     protected LockManager2 lockManager;
196     
197     protected ResourceIdToPathMapper idMapper = null;
198
199     /*
200      * --- ctor and general getter / setter methods ---
201      *
202      *
203      */

204
205     /**
206      * Creates a new resouce manager operation on the specified directories.
207      *
208      * @param storeDir directory where main data should go after commit
209      * @param workDir directory where transactions store temporary data
210      * @param urlEncodePath if set to <code>true</code> encodes all paths to allow for any kind of characters
211      * @param logger the logger to be used by this store
212      */

213     public FileResourceManager(String JavaDoc storeDir, String JavaDoc workDir, boolean urlEncodePath, LoggerFacade logger) {
214         this(storeDir, workDir, urlEncodePath, logger, false);
215     }
216
217     /**
218      * Creates a new resouce manager operation on the specified directories.
219      *
220      * @param storeDir directory where main data should go after commit
221      * @param workDir directory where transactions store temporary data
222      * @param urlEncodePath if set to <code>true</code> encodes all paths to allow for any kind of characters
223      * @param logger the logger to be used by this store
224      * @param debug if set to <code>true</code> logs all locking information to "transaction.log" for debugging inspection
225      */

226     public FileResourceManager(
227         String JavaDoc storeDir,
228         String JavaDoc workDir,
229         boolean urlEncodePath,
230         LoggerFacade logger,
231         boolean debug) {
232         this(storeDir, workDir, urlEncodePath ? new URLEncodeIdMapper() : null, logger, false);
233     }
234
235     /**
236      * Creates a new resouce manager operation on the specified directories.
237      *
238      * @param storeDir directory where main data should go after commit
239      * @param workDir directory where transactions store temporary data
240      * @param idMapper mapper for resourceId to path
241      * @param logger the logger to be used by this store
242      * @param debug if set to <code>true</code> logs all locking information to "transaction.log" for debugging inspection
243      */

244     public FileResourceManager(
245         String JavaDoc storeDir,
246         String JavaDoc workDir,
247         ResourceIdToPathMapper idMapper,
248         LoggerFacade logger,
249         boolean debug) {
250         this.workDir = workDir;
251         this.storeDir = storeDir;
252         this.logger = logger;
253         this.debug = debug;
254         this.idMapper = idMapper;
255     }
256
257     /**
258      * Gets the store directory.
259      *
260      * @return the store directory
261      * @see #FileResourceManager(String, String, boolean, LoggerFacade)
262      * @see #FileResourceManager(String, String, boolean, LoggerFacade, boolean)
263      */

264     public String JavaDoc getStoreDir() {
265         return storeDir;
266     }
267
268     /**
269      * Gets the working directory.
270      *
271      * @return the work directory
272      * @see #FileResourceManager(String, String, boolean, LoggerFacade)
273      * @see #FileResourceManager(String, String, boolean, LoggerFacade, boolean)
274      */

275     public String JavaDoc getWorkDir() {
276         return workDir;
277     }
278
279     /**
280      * Gets the logger used by this resource manager.
281      *
282      * @return used logger
283      */

284     public LoggerFacade getLogger() {
285         return logger;
286     }
287
288     /*
289      * --- public methods of interface ResourceManager ---
290      *
291      *
292      */

293
294     public boolean lockResource(Object JavaDoc resourceId, Object JavaDoc txId) throws ResourceManagerException {
295         lockResource(resourceId, txId, false);
296         // XXX will never return false as it will either throw or return true
297
return true;
298     }
299
300     public boolean lockResource(Object JavaDoc resourceId, Object JavaDoc txId, boolean shared) throws ResourceManagerException {
301         lockResource(resourceId, txId, shared, true, Long.MAX_VALUE, true);
302         // XXX will never return false as it will either throw or return true
303
return true;
304     }
305
306     public boolean lockResource(
307         Object JavaDoc resourceId,
308         Object JavaDoc txId,
309         boolean shared,
310         boolean wait,
311         long timeoutMSecs,
312         boolean reentrant)
313         throws ResourceManagerException {
314
315         TransactionContext context = (shared ? txInitialSaneCheck(txId) : txInitialSaneCheckForWriting(txId));
316         assureNotMarkedForRollback(context);
317         fileInitialSaneCheck(txId, resourceId);
318
319         // XXX allows locking of non existent resources (e.g. to prepare a create)
320
int level = (shared ? getSharedLockLevel(context) : LOCK_EXCLUSIVE);
321         try {
322             lockManager.lock(txId, resourceId, level, reentrant, Math.min(timeoutMSecs,
323                     context.timeoutMSecs));
324             // XXX will never return false as it will either throw or return true
325
return true;
326         } catch (LockException e) {
327             switch (e.getCode()) {
328             case LockException.CODE_INTERRUPTED:
329                 throw new ResourceManagerException("Could not get lock for resource at '"
330                         + resourceId + "'", ERR_NO_LOCK, txId);
331             case LockException.CODE_TIMED_OUT:
332                 throw new ResourceManagerException("Lock timed out for resource at '" + resourceId
333                         + "'", ERR_NO_LOCK, txId);
334             case LockException.CODE_DEADLOCK_VICTIM:
335                 throw new ResourceManagerException("Deadlock victim resource at '" + resourceId
336                         + "'", ERR_DEAD_LOCK, txId);
337             default :
338                 throw new ResourceManagerException("Locking exception for resource at '" + resourceId
339                         + "'", ERR_DEAD_LOCK, txId);
340             }
341         }
342     }
343
344     public int getDefaultIsolationLevel() {
345         return DEFAULT_ISOLATION_LEVEL;
346     }
347
348     public int[] getSupportedIsolationLevels() throws ResourceManagerException {
349         return new int[] { ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ };
350     }
351
352     public boolean isIsolationLevelSupported(int level) throws ResourceManagerException {
353         return (level == ISOLATION_LEVEL_READ_COMMITTED || level == ISOLATION_LEVEL_REPEATABLE_READ);
354     }
355
356     /**
357      * Gets the default transaction timeout in <em>milliseconds</em>.
358      */

359     public long getDefaultTransactionTimeout() {
360         return defaultTimeout;
361     }
362
363     /**
364      * Sets the default transaction timeout.
365      *
366      * @param timeout timeout in <em>milliseconds</em>
367      */

368     public void setDefaultTransactionTimeout(long timeout) {
369         defaultTimeout = timeout;
370     }
371
372     public long getTransactionTimeout(Object JavaDoc txId) throws ResourceManagerException {
373         assureRMReady();
374         long msecs = 0;
375         TransactionContext context = getContext(txId);
376         if (context == null) {
377             msecs = getDefaultTransactionTimeout();
378         } else {
379             msecs = context.timeoutMSecs;
380         }
381         return msecs;
382     }
383
384     public void setTransactionTimeout(Object JavaDoc txId, long mSecs) throws ResourceManagerException {
385         assureRMReady();
386         TransactionContext context = getContext(txId);
387         if (context != null) {
388             context.timeoutMSecs = mSecs;
389         } else {
390             throw new ResourceManagerException(ERR_NO_TX, txId);
391         }
392     }
393
394     public int getIsolationLevel(Object JavaDoc txId) throws ResourceManagerException {
395         assureRMReady();
396         TransactionContext context = getContext(txId);
397         if (context == null) {
398             return DEFAULT_ISOLATION_LEVEL;
399         } else {
400             return context.isolationLevel;
401         }
402     }
403
404     public void setIsolationLevel(Object JavaDoc txId, int level) throws ResourceManagerException {
405         assureRMReady();
406         TransactionContext context = getContext(txId);
407         if (context != null) {
408             if (level != ISOLATION_LEVEL_READ_COMMITTED || level != ISOLATION_LEVEL_REPEATABLE_READ) {
409                 context.isolationLevel = level;
410             } else {
411                 throw new ResourceManagerException(ERR_ISOLATION_LEVEL_UNSUPPORTED, txId);
412             }
413         } else {
414             throw new ResourceManagerException(ERR_NO_TX, txId);
415         }
416     }
417
418     public synchronized void start() throws ResourceManagerSystemException {
419
420         logger.logInfo("Starting RM at '" + storeDir + "' / '" + workDir + "'");
421
422         operationMode = OPERATION_MODE_STARTING;
423
424         globalTransactions = Collections.synchronizedMap(new HashMap JavaDoc());
425         lockManager = new GenericLockManager(LOCK_COMMIT, logger);
426         globalOpenResources = Collections.synchronizedList(new ArrayList JavaDoc());
427
428         recover();
429         sync();
430
431         operationMode = OPERATION_MODE_STARTED;
432
433         if (dirty) {
434             logger.logWarning("Started RM, but in dirty mode only (Recovery of pending transactions failed)");
435         } else {
436             logger.logInfo("Started RM");
437         }
438
439     }
440
441     public synchronized boolean stop(int mode) throws ResourceManagerSystemException {
442         return stop(mode, getDefaultTransactionTimeout() * DEFAULT_COMMIT_TIMEOUT_FACTOR);
443     }
444
445     public synchronized boolean stop(int mode, long timeOut) throws ResourceManagerSystemException {
446
447         logger.logInfo("Stopping RM at '" + storeDir + "' / '" + workDir + "'");
448
449         operationMode = OPERATION_MODE_STOPPING;
450
451         sync();
452         boolean success = shutdown(mode, timeOut);
453
454         releaseGlobalOpenResources();
455
456         if (success) {
457             operationMode = OPERATION_MODE_STOPPED;
458             logger.logInfo("Stopped RM");
459         } else {
460             logger.logWarning("Failed to stop RM");
461         }
462
463         return success;
464     }
465
466     public synchronized boolean recover() throws ResourceManagerSystemException {
467         if (operationMode != OPERATION_MODE_STARTED && operationMode != OPERATION_MODE_STARTING) {
468             throw new ResourceManagerSystemException(
469                 ERR_SYSTEM,
470                 "Recovery is possible in started or starting resource manager only");
471         }
472         int oldMode = operationMode;
473         operationMode = OPERATION_MODE_RECOVERING;
474
475         recoverContexts();
476         if (globalTransactions.size() > 0) {
477             logger.logInfo("Recovering pending transactions");
478         }
479
480         dirty = !rollBackOrForward();
481
482         operationMode = oldMode;
483         return dirty;
484     }
485
486     public int getTransactionState(Object JavaDoc txId) throws ResourceManagerException {
487         TransactionContext context = getContext(txId);
488
489         if (context == null) {
490             return STATUS_NO_TRANSACTION;
491         } else {
492             return context.status;
493         }
494
495     }
496
497     public void startTransaction(Object JavaDoc txId) throws ResourceManagerException {
498
499         if (logger.isFineEnabled()) logger.logFine("Starting Tx " + txId);
500
501         assureStarted(); // can only start a new transaction when not already stopping
502
if (txId == null || txId.toString().length() == 0) {
503             throw new ResourceManagerException(ERR_TXID_INVALID, txId);
504         }
505
506         // be sure we are the only ones who create this tx
507
synchronized (globalTransactions) {
508             TransactionContext context = getContext(txId);
509
510             if (context != null) {
511                 throw new ResourceManagerException(ERR_DUP_TX, txId);
512             }
513
514             context = new TransactionContext(txId);
515             context.init();
516             globalTransactions.put(txId, context);
517
518         }
519     }
520
521     public void markTransactionForRollback(Object JavaDoc txId) throws ResourceManagerException {
522         assureRMReady();
523         TransactionContext context = txInitialSaneCheckForWriting(txId);
524         try {
525             context.status = STATUS_MARKED_ROLLBACK;
526             context.saveState();
527         } finally {
528             // be very sure to free locks and resources, as application might crash or otherwise forget to roll this tx back
529
context.finalCleanUp();
530         }
531     }
532
533     public int prepareTransaction(Object JavaDoc txId) throws ResourceManagerException {
534         assureRMReady();
535         // do not allow any further writing or commit or rollback when db is corrupt
536
if (dirty) {
537             throw new ResourceManagerSystemException(
538                 "Database is set to dirty, this *may* mean it is corrupt. No modifications are allowed until a recovery run has been performed!",
539                 ERR_SYSTEM,
540                 txId);
541         }
542
543         if (txId == null) {
544             throw new ResourceManagerException(ERR_TXID_INVALID, txId);
545         }
546
547         TransactionContext context = getContext(txId);
548
549         if (context == null) {
550             return PREPARE_FAILURE;
551         }
552
553         synchronized (context) {
554
555             sync();
556
557             if (context.status != STATUS_ACTIVE) {
558                 context.status = STATUS_MARKED_ROLLBACK;
559                 context.saveState();
560                 return PREPARE_FAILURE;
561             }
562
563             if (logger.isFineEnabled()) logger.logFine("Preparing Tx " + txId);
564
565             int prepareStatus = PREPARE_FAILURE;
566
567             context.status = STATUS_PREPARING;
568             context.saveState();
569             // do all checks as early as possible
570
context.closeResources();
571             if (context.readOnly) {
572                 prepareStatus = PREPARE_SUCCESS_READONLY;
573             } else {
574                 // do all checks as early as possible
575
try {
576                     context.upgradeLockToCommit();
577                 } catch (ResourceManagerException rme) {
578                     // if this did not work, mark it for roll back as early as possible
579
markTransactionForRollback(txId);
580                     throw rme;
581                 }
582                 prepareStatus = PREPARE_SUCCESS;
583             }
584             context.status = STATUS_PREPARED;
585             context.saveState();
586             if (logger.isFineEnabled()) logger.logFine("Prepared Tx " + txId);
587
588             return prepareStatus;
589         }
590     }
591
592     public void rollbackTransaction(Object JavaDoc txId) throws ResourceManagerException {
593         assureRMReady();
594         TransactionContext context = txInitialSaneCheckForWriting(txId);
595         // needing synchronization in order not to interfer with shutdown thread
596
synchronized (context) {
597             try {
598
599                 if (logger.isFineEnabled()) logger.logFine("Rolling back Tx " + txId);
600
601                 context.status = STATUS_ROLLING_BACK;
602                 context.saveState();
603                 context.rollback();
604                 context.status = STATUS_ROLLEDBACK;
605                 context.saveState();
606                 globalTransactions.remove(txId);
607                 context.cleanUp();
608
609                 if (logger.isFineEnabled()) logger.logFine("Rolled back Tx " + txId);
610
611                 // any system or runtime exceptions or errors thrown in rollback means we are in deep trouble, set the dirty flag
612
} catch (Error JavaDoc e) {
613                 setDirty(txId, e);
614                 throw e;
615             } catch (RuntimeException JavaDoc e) {
616                 setDirty(txId, e);
617                 throw e;
618             } catch (ResourceManagerSystemException e) {
619                 setDirty(txId, e);
620                 throw e;
621             } finally {
622                 context.finalCleanUp();
623                 // tell shutdown thread this tx is finished
624
context.notifyFinish();
625             }
626         }
627     }
628
629     public void commitTransaction(Object JavaDoc txId) throws ResourceManagerException {
630         assureRMReady();
631         TransactionContext context = txInitialSaneCheckForWriting(txId);
632         assureNotMarkedForRollback(context);
633
634         // needing synchronization in order not to interfer with shutdown thread
635
synchronized (context) {
636             try {
637
638                 if (logger.isFineEnabled()) logger.logFine("Committing Tx " + txId);
639
640                 context.status = STATUS_COMMITTING;
641                 context.saveState();
642                 context.commit();
643                 context.status = STATUS_COMMITTED;
644                 context.saveState();
645                 globalTransactions.remove(txId);
646                 context.cleanUp();
647
648                 if (logger.isFineEnabled()) logger.logFine("Committed Tx " + txId);
649
650                 // any system or runtime exceptions or errors thrown in rollback means we are in deep trouble, set the dirty flag
651
} catch (Error JavaDoc e) {
652                 setDirty(txId, e);
653                 throw e;
654             } catch (RuntimeException JavaDoc e) {
655                 setDirty(txId, e);
656                 throw e;
657             } catch (ResourceManagerSystemException e) {
658                 setDirty(txId, e);
659                 throw e;
660                 // like "could not upgrade lock"
661
} catch (ResourceManagerException e) {
662                 logger.logWarning("Could not commit tx " + txId + ", rolling back instead", e);
663                 rollbackTransaction(txId);
664             } finally {
665                 context.finalCleanUp();
666                 // tell shutdown thread this tx is finished
667
context.notifyFinish();
668             }
669         }
670     }
671
672     public boolean resourceExists(Object JavaDoc resourceId) throws ResourceManagerException {
673         // create temporary light weight tx
674
Object JavaDoc txId;
675         TransactionContext context;
676         synchronized (globalTransactions) {
677             txId = generatedUniqueTxId();
678             if (logger.isFinerEnabled())
679                 logger.logFiner("Creating temporary light weight tx " + txId + " to check for exists");
680             context = new TransactionContext(txId);
681             context.isLightWeight = true;
682             // XXX higher isolation might be needed to make sure upgrade to commit lock always works
683
context.isolationLevel = ISOLATION_LEVEL_READ_COMMITTED;
684 // context.isolationLevel = ISOLATION_LEVEL_REPEATABLE_READ;
685
globalTransactions.put(txId, context);
686         }
687
688         boolean exists = resourceExists(txId, resourceId);
689
690         context.freeLocks();
691         globalTransactions.remove(txId);
692         if (logger.isFinerEnabled())
693             logger.logFiner("Removing temporary light weight tx " + txId);
694
695         return exists;
696     }
697
698     public boolean resourceExists(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
699         lockResource(resourceId, txId, true);
700         return (getPathForRead(txId, resourceId) != null);
701     }
702
703     public void deleteResource(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
704         deleteResource(txId, resourceId, true);
705     }
706
707     public void deleteResource(Object JavaDoc txId, Object JavaDoc resourceId, boolean assureOnly) throws ResourceManagerException {
708
709         if (logger.isFineEnabled()) logger.logFine(txId + " deleting " + resourceId);
710
711         lockResource(resourceId, txId, false);
712
713         if (getPathForRead(txId, resourceId) == null) {
714             if (assureOnly) {
715                 return;
716             }
717             throw new ResourceManagerException("No such resource at '" + resourceId + "'", ERR_NO_SUCH_RESOURCE, txId);
718         }
719         String JavaDoc txDeletePath = getDeletePath(txId, resourceId);
720         String JavaDoc mainPath = getMainPath(resourceId);
721         try {
722
723             // first undo change / create when there was one
724
undoScheduledChangeOrCreate(txId, resourceId);
725
726             // if there still is a file in main store, we need to schedule
727
// a delete additionally
728
if (FileHelper.fileExists(mainPath)) {
729                 FileHelper.createFile(txDeletePath);
730             }
731         } catch (IOException JavaDoc e) {
732             throw new ResourceManagerSystemException(
733                 "Can not delete resource at '" + resourceId + "'",
734                 ERR_SYSTEM,
735                 txId,
736                 e);
737         }
738     }
739
740     public void createResource(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
741         createResource(txId, resourceId, true);
742     }
743
744     public void createResource(Object JavaDoc txId, Object JavaDoc resourceId, boolean assureOnly) throws ResourceManagerException {
745
746         if (logger.isFineEnabled()) logger.logFine(txId + " creating " + resourceId);
747
748         lockResource(resourceId, txId, false);
749
750         if (getPathForRead(txId, resourceId) != null) {
751             if (assureOnly) {
752                 return;
753             }
754             throw new ResourceManagerException(
755                 "Resource at '" + resourceId + "', already exists",
756                 ERR_RESOURCE_EXISTS,
757                 txId);
758         }
759
760         String JavaDoc txChangePath = getChangePath(txId, resourceId);
761         try {
762
763             // creation means either undoing a delete or actually scheduling a create
764
if (!undoScheduledDelete(txId, resourceId)) {
765                 FileHelper.createFile(txChangePath);
766             }
767
768         } catch (IOException JavaDoc e) {
769             throw new ResourceManagerSystemException(
770                 "Can not create resource at '" + resourceId + "'",
771                 ERR_SYSTEM,
772                 txId,
773                 e);
774         }
775     }
776
777     /*
778      * --- public methods of interface StreamableResourceManager ---
779      *
780      *
781      */

782
783     public InputStream JavaDoc readResource(Object JavaDoc resourceId) throws ResourceManagerException {
784         // create temporary light weight tx
785
Object JavaDoc txId;
786         synchronized (globalTransactions) {
787             txId = generatedUniqueTxId();
788             if (logger.isFinerEnabled())
789                 logger.logFiner("Creating temporary light weight tx " + txId + " for reading");
790             TransactionContext context = new TransactionContext(txId);
791             context.isLightWeight = true;
792             // XXX higher isolation might be needed to make sure upgrade to commit lock always works
793
context.isolationLevel = ISOLATION_LEVEL_READ_COMMITTED;
794 // context.isolationLevel = ISOLATION_LEVEL_REPEATABLE_READ;
795
globalTransactions.put(txId, context);
796         }
797
798         InputStream JavaDoc is = readResource(txId, resourceId);
799         return is;
800     }
801
802     public InputStream JavaDoc readResource(