| 1 23 24 package org.apache.commons.transaction.file; 25 26 import java.io.BufferedReader ; 27 import java.io.BufferedWriter ; 28 import java.io.File ; 29 import java.io.FileInputStream ; 30 import java.io.FileNotFoundException ; 31 import java.io.FileOutputStream ; 32 import java.io.IOException ; 33 import java.io.InputStream ; 34 import java.io.InputStreamReader ; 35 import java.io.OutputStream ; 36 import java.io.OutputStreamWriter ; 37 import java.util.ArrayList ; 38 import java.util.Collection ; 39 import java.util.HashMap ; 40 import java.util.List ; 41 import java.util.Map ; 42 import java.util.Iterator ; 43 import java.util.Collections ; 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 120 public class FileResourceManager implements ResourceManager, ResourceManagerErrorCodes { 121 122 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 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 WORK_CHANGE_DIR = "change"; 144 protected static final String WORK_DELETE_DIR = "delete"; 145 146 protected static final String CONTEXT_FILE = "transaction.log"; 147 148 153 154 protected static void applyDeletes(File removeDir, File targetDir, File rootDir) throws IOException { 155 if (removeDir.isDirectory() && targetDir.isDirectory()) { 156 File [] files = removeDir.listFiles(); 157 for (int i = 0; i < files.length; i++) { 158 File removeFile = files[i]; 159 File targetFile = new File (targetDir, removeFile.getName()); 160 if (removeFile.isFile()) { 161 if (targetFile.exists()) { 162 targetFile.delete(); 163 } 164 removeFile.delete(); 166 } else { 167 applyDeletes(removeFile, targetFile, rootDir); 168 } 169 if (!targetDir.equals(rootDir) && targetDir.list().length == 0) { 171 targetDir.delete(); 172 } 173 } 174 } 175 } 176 177 182 183 protected String workDir; 184 protected String 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 globalTransactions; 194 protected List globalOpenResources; 195 protected LockManager2 lockManager; 196 197 protected ResourceIdToPathMapper idMapper = null; 198 199 204 205 213 public FileResourceManager(String storeDir, String workDir, boolean urlEncodePath, LoggerFacade logger) { 214 this(storeDir, workDir, urlEncodePath, logger, false); 215 } 216 217 226 public FileResourceManager( 227 String storeDir, 228 String workDir, 229 boolean urlEncodePath, 230 LoggerFacade logger, 231 boolean debug) { 232 this(storeDir, workDir, urlEncodePath ? new URLEncodeIdMapper() : null, logger, false); 233 } 234 235 244 public FileResourceManager( 245 String storeDir, 246 String 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 264 public String getStoreDir() { 265 return storeDir; 266 } 267 268 275 public String getWorkDir() { 276 return workDir; 277 } 278 279 284 public LoggerFacade getLogger() { 285 return logger; 286 } 287 288 293 294 public boolean lockResource(Object resourceId, Object txId) throws ResourceManagerException { 295 lockResource(resourceId, txId, false); 296 return true; 298 } 299 300 public boolean lockResource(Object resourceId, Object txId, boolean shared) throws ResourceManagerException { 301 lockResource(resourceId, txId, shared, true, Long.MAX_VALUE, true); 302 return true; 304 } 305 306 public boolean lockResource( 307 Object resourceId, 308 Object 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 int level = (shared ? getSharedLockLevel(context) : LOCK_EXCLUSIVE); 321 try { 322 lockManager.lock(txId, resourceId, level, reentrant, Math.min(timeoutMSecs, 323 context.timeoutMSecs)); 324 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 359 public long getDefaultTransactionTimeout() { 360 return defaultTimeout; 361 } 362 363 368 public void setDefaultTransactionTimeout(long timeout) { 369 defaultTimeout = timeout; 370 } 371 372 public long getTransactionTimeout(Object 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 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 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 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 ()); 425 lockManager = new GenericLockManager(LOCK_COMMIT, logger); 426 globalOpenResources = Collections.synchronizedList(new ArrayList ()); 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 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 txId) throws ResourceManagerException { 498 499 if (logger.isFineEnabled()) logger.logFine("Starting Tx " + txId); 500 501 assureStarted(); if (txId == null || txId.toString().length() == 0) { 503 throw new ResourceManagerException(ERR_TXID_INVALID, txId); 504 } 505 506 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 txId) throws ResourceManagerException { 522 assureRMReady(); 523 TransactionContext context = txInitialSaneCheckForWriting(txId); 524 try { 525 context.status = STATUS_MARKED_ROLLBACK; 526 context.saveState(); 527 } finally { 528 context.finalCleanUp(); 530 } 531 } 532 533 public int prepareTransaction(Object txId) throws ResourceManagerException { 534 assureRMReady(); 535 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 context.closeResources(); 571 if (context.readOnly) { 572 prepareStatus = PREPARE_SUCCESS_READONLY; 573 } else { 574 try { 576 context.upgradeLockToCommit(); 577 } catch (ResourceManagerException rme) { 578 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 txId) throws ResourceManagerException { 593 assureRMReady(); 594 TransactionContext context = txInitialSaneCheckForWriting(txId); 595 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 } catch (Error e) { 613 setDirty(txId, e); 614 throw e; 615 } catch (RuntimeException 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 context.notifyFinish(); 625 } 626 } 627 } 628 629 public void commitTransaction(Object txId) throws ResourceManagerException { 630 assureRMReady(); 631 TransactionContext context = txInitialSaneCheckForWriting(txId); 632 assureNotMarkedForRollback(context); 633 634 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 } catch (Error e) { 652 setDirty(txId, e); 653 throw e; 654 } catch (RuntimeException e) { 655 setDirty(txId, e); 656 throw e; 657 } catch (ResourceManagerSystemException e) { 658 setDirty(txId, e); 659 throw e; 660 } catch (ResourceManagerException e) { 662 logger.logWarning("Could not commit tx " + txId + ", rolling back instead", e); 663 rollbackTransaction(txId); 664 } finally { 665 context.finalCleanUp(); 666 context.notifyFinish(); 668 } 669 } 670 } 671 672 public boolean resourceExists(Object resourceId) throws ResourceManagerException { 673 Object 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 context.isolationLevel = ISOLATION_LEVEL_READ_COMMITTED; 684 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 txId, Object resourceId) throws ResourceManagerException { 699 lockResource(resourceId, txId, true); 700 return (getPathForRead(txId, resourceId) != null); 701 } 702 703 public void deleteResource(Object txId, Object resourceId) throws ResourceManagerException { 704 deleteResource(txId, resourceId, true); 705 } 706 707 public void deleteResource(Object txId, Object 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 txDeletePath = getDeletePath(txId, resourceId); 720 String mainPath = getMainPath(resourceId); 721 try { 722 723 undoScheduledChangeOrCreate(txId, resourceId); 725 726 if (FileHelper.fileExists(mainPath)) { 729 FileHelper.createFile(txDeletePath); 730 } 731 } catch (IOException 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 txId, Object resourceId) throws ResourceManagerException { 741 createResource(txId, resourceId, true); 742 } 743 744 public void createResource(Object txId, Object 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 txChangePath = getChangePath(txId, resourceId); 761 try { 762 763 if (!undoScheduledDelete(txId, resourceId)) { 765 FileHelper.createFile(txChangePath); 766 } 767 768 } catch (IOException e) { 769 throw new ResourceManagerSystemException( 770 "Can not create resource at '" + resourceId + "'", 771 ERR_SYSTEM, 772 txId, 773 e); 774 } 775 } 776 777 782 783 public InputStream readResource(Object resourceId) throws ResourceManagerException { 784 Object 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 context.isolationLevel = ISOLATION_LEVEL_READ_COMMITTED; 794 globalTransactions.put(txId, context); 796 } 797 798 InputStream is = readResource(txId, resourceId); 799 return is; 800 } 801 802 public InputStream readResource( |