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(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
803
804         if (logger.isFineEnabled()) logger.logFine(txId + " reading " + resourceId);
805
806         lockResource(resourceId, txId, true);
807
808         String JavaDoc resourcePath = getPathForRead(txId, resourceId);
809         if (resourcePath == null) {
810             throw new ResourceManagerException("No such resource at '" + resourceId + "'", ERR_NO_SUCH_RESOURCE, txId);
811         }
812
813         File JavaDoc file = new File JavaDoc(resourcePath);
814         try {
815             FileInputStream JavaDoc stream = new FileInputStream JavaDoc(file);
816             getContext(txId).registerResource(stream);
817             return new InputStreamWrapper(stream, txId, resourceId);
818         } catch (FileNotFoundException JavaDoc e) {
819             throw new ResourceManagerSystemException("File '" + resourcePath + "' does not exist", ERR_SYSTEM, txId);
820         }
821     }
822
823     public OutputStream JavaDoc writeResource(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
824
825         if (logger.isFineEnabled()) logger.logFine(txId + " writing " + resourceId);
826
827         lockResource(resourceId, txId, false);
828
829         String JavaDoc resourcePath = getPathForWrite(txId, resourceId);
830         if (resourcePath == null) {
831             throw new ResourceManagerException("No such resource at '" + resourceId + "'", ERR_NO_SUCH_RESOURCE, txId);
832         }
833
834         File JavaDoc file = new File JavaDoc(resourcePath);
835         try {
836             FileOutputStream JavaDoc stream = new FileOutputStream JavaDoc(file);
837             TransactionContext context = getContext(txId);
838             context.registerResource(stream);
839             context.readOnly = false;
840             return stream;
841         } catch (FileNotFoundException JavaDoc e) {
842             throw new ResourceManagerSystemException("File '" + resourcePath + "' does not exist", ERR_SYSTEM, txId);
843         }
844     }
845
846     /*
847      * --- additional public methods complementing implementation of interfaces ---
848      *
849      *
850      */

851
852     /**
853      * Resets the store by deleting work <em>and</em> store directory.
854      */

855     public synchronized void reset() {
856         FileHelper.removeRec(new File JavaDoc(storeDir));
857         FileHelper.removeRec(new File JavaDoc(workDir));
858         new File JavaDoc(storeDir).mkdirs();
859         new File JavaDoc(workDir).mkdirs();
860     }
861
862     /**
863      * Synchronizes persistent data with caches. Is implemented with an empty
864      * body, but called by other methods relying on synchronization. Subclasses
865      * that utilize caching must implement this method reasonably.
866      *
867      * @throws ResourceManagerSystemException if anything fatal hapened during synchonization
868      */

869     public synchronized void sync() throws ResourceManagerSystemException {
870     }
871
872     /**
873      * Generates a transaction identifier unique to this resource manager. To do so
874      * it requires this resource manager to be started.
875      *
876      * @return generated transaction identifier
877      * @throws ResourceManagerSystemException if this resource manager has not been started, yet
878      */

879     public String JavaDoc generatedUniqueTxId() throws ResourceManagerSystemException {
880         assureRMReady();
881         String JavaDoc txId;
882         synchronized (globalTransactions) {
883             do {
884                 txId = Long.toHexString(System.currentTimeMillis());
885                 // XXX busy loop
886
} while (getContext(txId) != null);
887         }
888         return txId;
889     }
890
891     /*
892      * --- sane checks ---
893      *
894      *
895      */

896
897     protected void fileInitialSaneCheck(Object JavaDoc txId, Object JavaDoc path) throws ResourceManagerException {
898         if (path == null || path.toString().length() == 0) {
899             throw new ResourceManagerException(ERR_RESOURCEID_INVALID, txId);
900         }
901     }
902
903     protected void assureStarted() throws ResourceManagerSystemException {
904         if (operationMode != OPERATION_MODE_STARTED) {
905             throw new ResourceManagerSystemException("Resource Manager Service not started", ERR_SYSTEM, null);
906         }
907     }
908
909     protected void assureRMReady() throws ResourceManagerSystemException {
910         if (operationMode != OPERATION_MODE_STARTED && operationMode != OPERATION_MODE_STOPPING) {
911             throw new ResourceManagerSystemException("Resource Manager Service not ready", ERR_SYSTEM, null);
912         }
913     }
914
915     protected void assureNotMarkedForRollback(TransactionContext context) throws ResourceManagerException {
916         if (context.status == STATUS_MARKED_ROLLBACK) {
917             throw new ResourceManagerException(ERR_MARKED_FOR_ROLLBACK, context.txId);
918         }
919     }
920
921     protected TransactionContext txInitialSaneCheckForWriting(Object JavaDoc txId) throws ResourceManagerException {
922         assureRMReady();
923         // do not allow any further writing or commit or rollback when db is corrupt
924
if (dirty) {
925             throw new ResourceManagerSystemException(
926                 "Database is set to dirty, this *may* mean it is corrupt. No modifications are allowed until a recovery run has been performed!",
927                 ERR_SYSTEM,
928                 txId);
929         }
930         return txInitialSaneCheck(txId);
931     }
932
933     protected TransactionContext txInitialSaneCheck(Object JavaDoc txId) throws ResourceManagerException {
934         assureRMReady();
935         if (txId == null) {
936             throw new ResourceManagerException(ERR_TXID_INVALID, txId);
937         }
938
939         TransactionContext context = getContext(txId);
940
941         if (context == null) {
942             throw new ResourceManagerException(ERR_NO_TX, txId);
943         }
944
945         return context;
946     }
947
948     /*
949      * --- General Helpers ---
950      *
951      *
952      */

953
954     protected TransactionContext getContext(Object JavaDoc txId) {
955         return (TransactionContext) globalTransactions.get(txId);
956     }
957
958     protected String JavaDoc assureLeadingSlash(Object JavaDoc pathObject) {
959         String JavaDoc path = "";
960         if (pathObject != null) {
961             if (idMapper != null) {
962                 path = idMapper.getPathForId(pathObject);
963             } else {
964                 path = pathObject.toString();
965             }
966             if (path.length() > 0 && path.charAt(0) != '/' && path.charAt(0) != '\\') {
967                 path = "/" + path;
968             }
969         }
970         return path;
971     }
972
973     protected String JavaDoc getMainPath(Object JavaDoc path) {
974         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(storeDir.length() + path.toString().length() + 5);
975         buf.append(storeDir).append(assureLeadingSlash(path));
976         return buf.toString();
977     }
978
979     protected String JavaDoc getChangePath(Object JavaDoc txId, Object JavaDoc path) {
980         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(txId.toString().length() + path.toString().length()
981                 + WORK_CHANGE_DIR.length() + workDir.length() + 5);
982         buf.append(workDir).append('/').append(txId.toString()).append('/').append(WORK_CHANGE_DIR).append(
983                 assureLeadingSlash(path));
984         return buf.toString();
985     }
986
987     protected String JavaDoc getDeletePath(Object JavaDoc txId, Object JavaDoc path) {
988         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(txId.toString().length() + path.toString().length()
989                 + WORK_DELETE_DIR.length() + workDir.length() + 5);
990         buf.append(workDir).append('/').append(txId.toString()).append('/').append(WORK_DELETE_DIR).append(
991                 assureLeadingSlash(path));
992         return buf.toString();
993     }
994
995     protected boolean undoScheduledDelete(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
996         String JavaDoc txDeletePath = getDeletePath(txId, resourceId);
997         File JavaDoc deleteFile = new File JavaDoc(txDeletePath);
998         if (deleteFile.exists()) {
999             if (!deleteFile.delete()) {
1000                throw new ResourceManagerSystemException(
1001                    "Failed to undo delete of '" + resourceId + "'",
1002                    ERR_SYSTEM,
1003                    txId);
1004            }
1005            return true;
1006        }
1007        return false;
1008    }
1009
1010    protected boolean undoScheduledChangeOrCreate(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
1011        String JavaDoc txChangePath = getChangePath(txId, resourceId);
1012        File JavaDoc changeFile = new File JavaDoc(txChangePath);
1013        if (changeFile.exists()) {
1014            if (!changeFile.delete()) {
1015                throw new ResourceManagerSystemException(
1016                    "Failed to undo change / create of '" + resourceId + "'",
1017                    ERR_SYSTEM,
1018                    txId);
1019            }
1020            return true;
1021        }
1022        return false;
1023    }
1024
1025    protected String JavaDoc getPathForWrite(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
1026        try {
1027            // when we want to write, be sure to write to a local copy
1028
String JavaDoc txChangePath = getChangePath(txId, resourceId);
1029            if (!FileHelper.fileExists(txChangePath)) {
1030                FileHelper.createFile(txChangePath);
1031            }
1032            return txChangePath;
1033        } catch (IOException JavaDoc e) {
1034            throw new ResourceManagerSystemException(
1035                "Can not write to resource at '" + resourceId + "'",
1036                ERR_SYSTEM,
1037                txId,
1038                e);
1039        }
1040    }
1041
1042    protected String JavaDoc getPathForRead(Object JavaDoc txId, Object JavaDoc resourceId) throws ResourceManagerException {
1043
1044        String JavaDoc mainPath = getMainPath(resourceId);
1045        String JavaDoc txChangePath = getChangePath(txId, resourceId);
1046        String JavaDoc txDeletePath = getDeletePath(txId, resourceId);
1047
1048        // now, this is gets bit complicated:
1049

1050        boolean changeExists = FileHelper.fileExists(txChangePath);
1051        boolean deleteExists = FileHelper.fileExists(txDeletePath);
1052        boolean mainExists = FileHelper.fileExists(mainPath);
1053        boolean resourceIsDir =
1054            ((mainExists && new File JavaDoc(mainPath).isDirectory())
1055                || (changeExists && new File JavaDoc(txChangePath).isDirectory()));
1056        if (resourceIsDir) {
1057            logger.logWarning("Resource at '" + resourceId + "' maps to directory");
1058        }
1059
1060        // first do some sane checks
1061

1062        // this may never be, two cases are possible, both disallowing to have a delete together with a change
1063
// 1. first there was a change, than a delete -> at least delete file exists (when there is a file in main store)
1064
// 2. first there was a delete, than a change -> only change file exists
1065
if (!resourceIsDir && changeExists && deleteExists) {
1066            throw new ResourceManagerSystemException(
1067                "Inconsistent delete and change combination for resource at '" + resourceId + "'",
1068                ERR_TX_INCONSISTENT,
1069                txId);
1070        }
1071
1072        // you should not have been allowed to delete a file that does not exist at all
1073
if (deleteExists && !mainExists) {
1074            throw new ResourceManagerSystemException(
1075                "Inconsistent delete for resource at '" + resourceId + "'",
1076                ERR_TX_INCONSISTENT,
1077                txId);
1078        }
1079
1080        if (changeExists) {
1081            return txChangePath;
1082        } else if ((mainExists && !deleteExists)) {
1083            return mainPath;
1084        } else {
1085            return null;
1086        }
1087    }
1088
1089    /*
1090     * --- Locking Helpers ---
1091     *
1092     *
1093     */

1094
1095    protected int getSharedLockLevel(TransactionContext context) throws ResourceManagerException {
1096        if (context.isolationLevel == ISOLATION_LEVEL_READ_COMMITTED
1097            || context.isolationLevel == ISOLATION_LEVEL_READ_UNCOMMITTED) {
1098            return LOCK_ACCESS;
1099        } else if (
1100            context.isolationLevel == ISOLATION_LEVEL_REPEATABLE_READ
1101                || context.isolationLevel == ISOLATION_LEVEL_SERIALIZABLE) {
1102            return LOCK_SHARED;
1103        } else {
1104            return LOCK_ACCESS;
1105        }
1106    }
1107
1108    /*
1109     * --- Resource Management ---
1110     *
1111     *
1112     */

1113
1114    protected void registerOpenResource(Object JavaDoc openResource) {
1115        if (logger.isFinerEnabled())
1116            logger.logFiner("Registering open resource " + openResource);
1117        globalOpenResources.add(openResource);
1118    }
1119
1120    protected void releaseGlobalOpenResources() {
1121        ArrayList JavaDoc copy;
1122        synchronized (globalOpenResources) {
1123            // XXX need to copy in order to allow removal in releaseOpenResource
1124
copy = new ArrayList JavaDoc(globalOpenResources);
1125            for (Iterator JavaDoc it = copy.iterator(); it.hasNext();) {
1126                Object JavaDoc stream = it.next();
1127                closeOpenResource(stream);
1128            }
1129        }
1130    }
1131
1132    protected void closeOpenResource(Object JavaDoc openResource) {
1133        if (logger.isFinerEnabled()) logger.logFiner("Releasing resource " + openResource);
1134        globalOpenResources.remove(openResource);
1135        if (openResource instanceof InputStream JavaDoc) {
1136            InputStream JavaDoc is = (InputStream JavaDoc) openResource;
1137            try {
1138                is.close();
1139            } catch (IOException JavaDoc e) {
1140                // do not care, as it might have been closed somewhere else, before
1141
}
1142        } else if (openResource instanceof OutputStream JavaDoc) {
1143            OutputStream JavaDoc os = (OutputStream JavaDoc) openResource;
1144            try {
1145                os.close();
1146            } catch (IOException JavaDoc e) {
1147                // do not care, as it might have been closed somewhere else, before
1148
}
1149        }
1150    }
1151
1152    /*
1153     * --- Recovery / Shutdown Support ---
1154     *
1155     *
1156     */

1157
1158    protected boolean rollBackOrForward() {
1159        boolean allCool = true;
1160
1161        synchronized (globalTransactions) {
1162            ArrayList JavaDoc contexts = new ArrayList JavaDoc(globalTransactions.values());
1163            for (Iterator JavaDoc it = contexts.iterator(); it.hasNext();) {
1164                TransactionContext context = (TransactionContext) it.next();
1165                if (context.status == STATUS_COMMITTING) {
1166                    // roll forward
1167
logger.logInfo("Rolling forward " + context.txId);
1168
1169                    try {
1170                        context.commit();
1171                        context.status = STATUS_COMMITTED;
1172                        context.saveState();
1173                        globalTransactions.remove(context.txId);
1174                        context.cleanUp();
1175                    } catch (ResourceManagerException e) {
1176                        // this is not good, but what can we do now?
1177
allCool = false;
1178                        logger.logSevere("Rolling forward of " + context.txId + " failed", e);
1179                    }
1180                } else if (context.status == STATUS_COMMITTED) {
1181                    logger.logInfo("Cleaning already commited " + context.txId);
1182                    globalTransactions.remove(context.txId);
1183                    try {
1184                        context.cleanUp();
1185                    } catch (ResourceManagerException e) {
1186                        // this is not good, but what can we do now?
1187
allCool = false;
1188                        logger.logWarning("Cleaning of " + context.txId + " failed", e);
1189                    }
1190                } else {
1191                    // in all other cases roll back and warn when not rollback was explicitely selected for tx
1192
if (context.status != STATUS_ROLLING_BACK
1193                        && context.status != STATUS_ROLLEDBACK
1194                        && context.status != STATUS_MARKED_ROLLBACK) {
1195                        logger.logWarning("Irregularly rolling back " + context.txId);
1196                    } else {
1197                        logger.logInfo("Rolling back " + context.txId);
1198                    }
1199                    try {
1200                        context.rollback();
1201                        context.status = STATUS_ROLLEDBACK;
1202                        context.saveState();
1203                        globalTransactions.remove(context.txId);
1204                        context.cleanUp();
1205                    } catch (ResourceManagerException e) {
1206                        logger.logWarning("Rolling back of " + context.txId + " failed", e);
1207                    }
1208                    //
1209
}
1210            }
1211
1212        }
1213        return allCool;
1214    }
1215
1216    protected void recoverContexts() {
1217        File JavaDoc dir = new File JavaDoc(workDir);
1218        File JavaDoc[] files = dir.listFiles();
1219        if (files == null)
1220            return;
1221        for (int i = 0; i < files.length; i++) {
1222            File JavaDoc file = files[i];
1223            String JavaDoc txId = file.getName();
1224            // recover all transactions we do not already know
1225
if (!globalTransactions.containsKey(txId)) {
1226
1227                logger.logInfo("Recovering " + txId);
1228                TransactionContext context;
1229                try {
1230                    context = new TransactionContext(txId);
1231                    context.recoverState();
1232                    globalTransactions.put(txId, context);
1233                } catch (ResourceManagerException e) {
1234                    // this is not good, but the best we get, just log as warning
1235
logger.logWarning("Recovering of " + txId + " failed");
1236                }
1237            }
1238        }
1239    }
1240
1241    protected boolean waitForAllTxToStop(long timeoutMSecs) {
1242        long startTime = System.currentTimeMillis();
1243
1244        // be sure not to lock globalTransactions for too long, as we need to give
1245
// txs the chance to complete (otherwise deadlocks are very likely to occur)
1246
// instead iterate over a copy as we can be sure no new txs will be registered
1247
// after operation level has been set to stopping
1248

1249        Collection JavaDoc transactionsToStop;
1250        synchronized (globalTransactions) {
1251            transactionsToStop = new ArrayList JavaDoc(globalTransactions.values());
1252        }
1253        for (Iterator JavaDoc it = transactionsToStop.iterator(); it.hasNext();) {
1254            long remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
1255
1256            if (remainingTimeout <= 0) {
1257                return false;
1258            }
1259
1260            TransactionContext context = (TransactionContext) it.next();
1261            synchronized (context) {
1262                if (!context.finished) {
1263                    logger.logInfo(
1264                        "Waiting for tx " + context.txId + " to finish for " + remainingTimeout + " milli seconds");
1265                }
1266                while (!context.finished && remainingTimeout > 0) {
1267                    try {
1268                        context.wait(remainingTimeout);
1269                    } catch (InterruptedException JavaDoc e) {
1270                        return false;
1271                    }
1272                    remainingTimeout = startTime - System.currentTimeMillis() + timeoutMSecs;
1273                }
1274                if (context.finished) {
1275                    logger.logInfo("Tx " + context.txId + " finished");
1276                } else {
1277                    logger.logWarning("Tx " + context.txId + " failed to finish in given time");
1278                }
1279            }
1280        }
1281
1282        return (globalTransactions.size() == 0);
1283    }
1284
1285    protected boolean shutdown(int mode, long timeoutMSecs) {
1286        switch (mode) {
1287            case SHUTDOWN_MODE_NORMAL :
1288                return waitForAllTxToStop(timeoutMSecs);
1289            case SHUTDOWN_MODE_ROLLBACK :
1290                return rollBackOrForward();
1291            case SHUTDOWN_MODE_KILL :
1292                return true;
1293            default :
1294                return false;
1295        }
1296    }
1297
1298    protected void setDirty(Object JavaDoc txId, Throwable JavaDoc t) {
1299        logger.logSevere(
1300            "Fatal error during critical commit/rollback of transaction " + txId + ", setting database to dirty.",
1301            t);
1302        dirty = true;
1303    }
1304
1305    /**
1306     * Inner class to hold the complete context, i.e. all information needed, for a transaction.
1307     *
1308     */

1309    protected class TransactionContext {
1310
1311        protected Object JavaDoc txId;
1312        protected int status = STATUS_ACTIVE;
1313        protected int isolationLevel = DEFAULT_ISOLATION_LEVEL;
1314        protected long timeoutMSecs = getDefaultTransactionTimeout();
1315        protected long startTime;
1316        protected long commitTime = -1L;
1317        protected boolean isLightWeight = false;
1318        protected boolean readOnly = true;
1319        protected boolean finished = false;
1320
1321        // list of streams participating in this tx
1322
private List JavaDoc openResourcs = new ArrayList JavaDoc();
1323
1324        public TransactionContext(Object JavaDoc txId) throws ResourceManagerException {
1325            this.txId = txId;
1326            startTime = System.currentTimeMillis();
1327        }
1328
1329        public long getRemainingTimeout() {
1330            long now = System.currentTimeMillis();
1331            return (startTime - now + timeoutMSecs);
1332        }
1333
1334        public synchronized void init() throws ResourceManagerException {
1335            String JavaDoc baseDir = workDir + "/" + txId;
1336            String JavaDoc changeDir = baseDir + "/" + WORK_CHANGE_DIR;
1337            String JavaDoc deleteDir = baseDir + "/" + WORK_DELETE_DIR;
1338
1339            new File JavaDoc(changeDir).mkdirs();
1340            new File JavaDoc(deleteDir).mkdirs();
1341
1342            saveState();
1343        }
1344
1345        public synchronized void rollback() throws ResourceManagerException {
1346            closeResources();
1347            freeLocks();
1348        }
1349
1350        public synchronized void commit() throws ResourceManagerException {
1351            String JavaDoc baseDir = workDir + "/" + txId;
1352            String JavaDoc changeDir = baseDir + "/" + WORK_CHANGE_DIR;
1353            String JavaDoc deleteDir = baseDir + "/" + WORK_DELETE_DIR;
1354
1355            closeResources();
1356            upgradeLockToCommit();
1357            try {
1358                applyDeletes(new File JavaDoc(deleteDir), new File JavaDoc(storeDir), new File JavaDoc(storeDir));
1359                FileHelper.moveRec(new File JavaDoc(changeDir), new File JavaDoc(storeDir));
1360            } catch (IOException JavaDoc e) {
1361                throw new ResourceManagerSystemException("Commit failed", ERR_SYSTEM, txId, e);
1362            }
1363            freeLocks();
1364            commitTime = System.currentTimeMillis();
1365        }
1366
1367        public synchronized void notifyFinish() {
1368            finished = true;
1369            notifyAll();
1370        }
1371
1372        public synchronized void cleanUp() throws ResourceManagerException {
1373            if (!cleanUp)
1374                return; // XXX for debugging only
1375
boolean clean = true;
1376            Exception JavaDoc cleanException = null;
1377            String JavaDoc baseDir = workDir + "/" + txId;
1378            FileHelper.removeRec(new File JavaDoc(baseDir));
1379            if (!clean) {
1380                throw new ResourceManagerSystemException(
1381                    "Clean up failed due to unreleasable lock",
1382                    ERR_SYSTEM,
1383                    txId,
1384                    cleanException);
1385            }
1386        }
1387
1388        public synchronized void finalCleanUp() throws ResourceManagerException {
1389            closeResources();
1390            freeLocks();
1391        }
1392
1393        public synchronized void upgradeLockToCommit() throws ResourceManagerException {
1394            for (Iterator JavaDoc it = lockManager.getAll(txId).iterator(); it.hasNext();) {
1395                GenericLock lock = (GenericLock) it.next();
1396                // only upgrade if we had write access
1397
if (lock.getLockLevel(txId) == LOCK_EXCLUSIVE) {
1398                    try {
1399                        // in case of deadlocks, make failure of non-committing tx more likely
1400
if (!lock
1401                            .acquire(
1402                                txId,
1403                                LOCK_COMMIT,
1404                                true,
1405                                true,
1406                                getDefaultTransactionTimeout() * DEFAULT_COMMIT_TIMEOUT_FACTOR)) {
1407                            throw new ResourceManagerException(
1408                                "Could not upgrade to commit lock for resource at '"
1409                                    + lock.getResourceId().toString()
1410                                    + "'",
1411                                ERR_NO_LOCK,
1412                                txId);
1413                        }
1414                    } catch (InterruptedException JavaDoc e) {
1415                        throw new ResourceManagerSystemException(ERR_SYSTEM, txId, e);
1416                    }
1417                }
1418
1419            }
1420        }
1421
1422        public synchronized void freeLocks() {
1423            lockManager.releaseAll(txId);
1424        }
1425
1426        public synchronized void closeResources() {
1427            synchronized (globalOpenResources) {
1428                for (Iterator JavaDoc it = openResourcs.iterator(); it.hasNext();) {
1429                    Object JavaDoc stream = it.next();
1430                    closeOpenResource(stream);
1431                }
1432            }
1433        }
1434
1435        public synchronized void registerResource(Object JavaDoc openResource) {
1436            synchronized (globalOpenResources) {
1437                registerOpenResource(openResource);
1438                openResourcs.add(openResource);
1439            }
1440        }
1441
1442        public synchronized void saveState() throws ResourceManagerException {
1443            String JavaDoc statePath = workDir + "/" + txId + "/" + CONTEXT_FILE;
1444            File JavaDoc file = new File JavaDoc(statePath);
1445            BufferedWriter JavaDoc writer = null;
1446            try {
1447                OutputStream JavaDoc os = new FileOutputStream JavaDoc(file);
1448                writer = new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(os, DEFAULT_PARAMETER_ENCODING));
1449                writer.write(toString());
1450            } catch (FileNotFoundException JavaDoc e) {
1451                String JavaDoc msg = "Saving status information to '" + statePath + "' failed! Could not create file";
1452                logger.logSevere(msg, e);
1453                throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, e);
1454            } catch (IOException JavaDoc e) {
1455                String JavaDoc msg = "Saving status information to '" + statePath + "' failed";
1456                logger.logSevere(msg, e);
1457                throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, e);
1458            } finally {
1459                if (writer != null) {
1460                    try {
1461                        writer.close();
1462                    } catch (IOException JavaDoc e) {
1463                    }
1464
1465                }
1466            }
1467        }
1468
1469        public synchronized void recoverState() throws ResourceManagerException {
1470            String JavaDoc statePath = workDir + "/" + txId + "/" + CONTEXT_FILE;
1471            File JavaDoc file = new File JavaDoc(statePath);
1472            BufferedReader JavaDoc reader = null;
1473            try {
1474                InputStream JavaDoc is = new FileInputStream JavaDoc(file);
1475
1476                reader = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(is, DEFAULT_PARAMETER_ENCODING));
1477                txId = reader.readLine();
1478                status = Integer.parseInt(reader.readLine());
1479                isolationLevel = Integer.parseInt(reader.readLine());
1480                timeoutMSecs = Long.parseLong(reader.readLine());
1481                startTime = Long.parseLong(reader.readLine());
1482            } catch (FileNotFoundException JavaDoc e) {
1483                String JavaDoc msg = "Recovering status information from '" + statePath + "' failed! Could not find file";
1484                logger.logSevere(msg, e);
1485                throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId);
1486            } catch (IOException JavaDoc e) {
1487                String JavaDoc msg = "Recovering status information from '" + statePath + "' failed";
1488                logger.logSevere(msg, e);
1489                throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, e);
1490            } catch (Throwable JavaDoc t) {
1491                String JavaDoc msg = "Recovering status information from '" + statePath + "' failed";
1492                logger.logSevere(msg, t);
1493                throw new ResourceManagerSystemException(msg, ERR_SYSTEM, txId, t);
1494            } finally {
1495                if (reader != null) {
1496                    try {
1497                        reader.close();
1498                    } catch (IOException JavaDoc e) {
1499                    }
1500
1501                }
1502            }
1503        }
1504
1505        public synchronized String JavaDoc toString() {
1506            StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
1507            buf.append(txId).append('\n');
1508            buf.append(Integer.toString(status)).append('\n');
1509            buf.append(Integer.toString(isolationLevel)).append('\n');
1510            buf.append(Long.toString(timeoutMSecs)).append('\n');
1511            buf.append(Long.toString(startTime)).append('\n');
1512            if (debug) {
1513                buf.append("----- Lock Debug Info -----\n");
1514                
1515                for (Iterator JavaDoc it = lockManager.getAll(txId).iterator(); it.hasNext();) {
1516                    GenericLock lock = (GenericLock) it.next();
1517                    buf.append(lock.toString()+"\n");
1518                }
1519                
1520            }
1521            return buf.toString();
1522        }
1523
1524    }
1525
1526    private class InputStreamWrapper extends InputStream JavaDoc {
1527        private InputStream JavaDoc is;
1528        private Object JavaDoc txId;
1529        private Object JavaDoc resourceId;
1530
1531        public InputStreamWrapper(InputStream JavaDoc is, Object JavaDoc txId, Object JavaDoc resourceId) {
1532            this.is = is;
1533            this.txId = txId;
1534            this.resourceId = resourceId;
1535        }
1536
1537        public int read() throws IOException JavaDoc {
1538            return is.read();
1539        }
1540
1541        public int read(byte b[]) throws IOException JavaDoc {
1542            return is.read(b);
1543        }
1544
1545        public int read(byte b[], int off, int len) throws IOException JavaDoc {
1546            return is.read(b, off, len);
1547        }
1548
1549        public int available() throws IOException JavaDoc {
1550            return is.available();
1551        }
1552
1553        public void close() throws IOException JavaDoc {
1554            try {
1555                is.close();
1556            } finally {
1557                TransactionContext context;
1558                synchronized (globalTransactions) {
1559                    context = getContext(txId);
1560                    if (context == null) {
1561                        return;
1562                    }
1563                }
1564                synchronized (context) {
1565                    if (context.isLightWeight) {
1566                        if (logger.isFinerEnabled())
1567                            logger.logFiner("Upon close of resource removing temporary light weight tx " + txId);
1568                        context.freeLocks();
1569                        globalTransactions.remove(txId);
1570                    } else {
1571                        // release access lock in order to allow other transactions to commit
1572
if (lockManager.getLevel(txId, resourceId) == LOCK_ACCESS) {
1573                            if (logger.isFinerEnabled()) {
1574                                logger.logFiner(
1575                                    "Upon close of resource releasing access lock for tx "
1576                                        + txId
1577                                        + " on resource at "
1578                                        + resourceId);
1579                            }
1580                            lockManager.release(txId, resourceId);
1581                        }
1582                    }
1583                }
1584            }
1585        }
1586
1587        public void mark(int readlimit) {
1588            is.mark(readlimit);
1589        }
1590
1591        public void reset() throws IOException JavaDoc {
1592            is.reset();
1593        }
1594
1595        public boolean markSupported() {
1596            return is.markSupported();
1597
1598        }
1599    }
1600}
1601
Popular Tags