001// 002// Name 003// $RCSfile: Transaction.java,v $ 004// 005// COPYRIGHT 006// Copyright 2008-2015 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 007// Cloud Software Group, Inc. Confidential Information 008// 009// History 010// $Revision: 1.1.2.31 $ $Date: 2015/03/11 02:52:47 $ 011// 012 013package com.kabira.platform; 014 015import com.kabira.platform.annotation.Managed; 016 017import java.io.StringWriter; 018import java.io.PrintWriter; 019 020/** 021 * Executes code in the context of a transaction. 022 * 023 * <p> The class should be extended and the abstract {@link Transaction#run} 024 * method implemented. When executed via {@link Transaction#execute}, 025 * all code executed within the <code>run</code> method will be 026 * transactional, changes to Managed objects are logged. 027 * 028 * <p> Object instances that are modified are write locked into the 029 * transaction, blocking access from other transactions until the 030 * transaction commits or rolls back. 031 * 032 * <p> If the default {@link IsolationLevel#SERIALIZABLE} Isolation level 033 * is used, object instances that are accessed but not modified will be 034 * read locked into the transaction. This will allow other transactions 035 * to also access and read lock the object, but prevents other 036 * transactions from modifying the object. 037 * 038 * <p> If the {@link IsolationLevel#READ_COMMITTED_SNAPSHOT} Isolation 039 * level is used, object instances that are accessed but not modified 040 * will have a snapshot of the data taken when first accessed in the 041 * transaction. All subseqeunt accesses to the object data within the 042 * same transaction will use this snapshot in order to present a 043 * consistent view of the data. This will allow other transactions to 044 * both access and modify the object. If a transaction takes a snapshot, 045 * and subsequently attempts to modify the object, the runtime will 046 * determine if the object data has been modified by another transaction 047 * since the snapshot was taken. If it has been modified, a deadlock 048 * exception is thrown in order to retry the transaction. 049 * 050 * <p> Any deadlocks that occur will automatically cause a roll back of the 051 * current transaction, and the code within the <code>run</code> method will 052 * be re-executed. This means that any code called within the 053 * <code>run</code> method must be transaction safe. If the work done within 054 * the <code>run</code> method should not be executed more than once, the work 055 * should be deferred until commit time by using a transaction commit 056 * trigger. 057 * 058 * <h3>Exception handling</h3> 059 * 060 * If the code within the <code>run</code> method throws a {@link 061 * Transaction.Rollback} exception with no cause, the 062 * <code>execute</code> call will roll back the transaction, and return 063 * {@link Transaction.Result#ROLLBACK} to the caller. In order to support 064 * exception propagation through the <code>execute</code> method, the 065 * following mechanisms can be used: 066 * <ul> 067 * <li> If an unchecked exception is thrown in the <code>run</code> 068 * method, the exception will be rethrown to the caller of <code>execute</code>. 069 * <li> If the implementor of the <code>run</code> method wishes to 070 * propagate a checked exception, they can throw a Rollback exception 071 * with a cause. When caught by <code>execute</code>, the Rollback 072 * exception cause and message will be wrapped in a 073 * {@link Transaction.InvocationRunException} and thrown back to the caller. 074 * </ul> 075 * In both cases, the transaction is rolled back before the exception is 076 * propagated. 077 */ 078public abstract class Transaction 079{ 080 /** 081 * Execute method return values. 082 */ 083 public enum Result 084 { 085 /** Transaction committed. */ 086 COMMIT, 087 088 /** Transaction rolled back. */ 089 ROLLBACK 090 } 091 092 /** 093 * Transaction isolation levels. 094 */ 095 public enum IsolationLevel 096 { 097 /** 098 * Access data that has been committed. A snapshot of the data is 099 * taken when a field is read, but locks are immediately released 100 * to allow other transactions to update the data. Multiple 101 * accesses to the same field returns the same data, even if the 102 * underlying object has been updated in other transactions. If 103 * an attempt is made to subsequently update the data after 104 * another transaction has made changes, a deadlock exception is 105 * thrown to retry the transaction. 106 */ 107 READ_COMMITTED_SNAPSHOT, 108 109 /** 110 * All access is serialized using shared read and exclusive 111 * write locks. 112 */ 113 SERIALIZABLE 114 } 115 116 /** 117 * Invalid transaction state exception. 118 * <p> 119 * Exception thrown if execute() is called with a transaction 120 * already active, or if the transaction services are not available. 121 */ 122 public static class InvalidTransactionState extends java.lang.Error 123 { 124 125 /** 126 * Serialization version. 127 */ 128 public final static long serialVersionUID = 1L; 129 130 InvalidTransactionState(String message) 131 { 132 super(message); 133 } 134 } 135 136 /** 137 * Not transactional exception. 138 * <p> 139 * Exception thrown if one of the lock methods is called 140 * on a non-transactional object. 141 */ 142 public static class NotTransactional extends java.lang.Error 143 { 144 /** 145 * Serialization version. 146 */ 147 public final static long serialVersionUID = 1L; 148 149 NotTransactional(String message) 150 { 151 super(message); 152 } 153 } 154 155 /** 156 * Rollback exception. 157 * <p> 158 * Exception thrown in the run method to rollback the transaction. 159 * <p> 160 * If constructed with a cause, the cause is propagated through the 161 * execute() call via the InvocationRunException exception. 162 */ 163 public static class Rollback extends java.lang.Exception 164 { 165 /** 166 * Serialization version. 167 */ 168 public final static long serialVersionUID = 1L; 169 170 /** 171 * Creates a rollback exception. 172 */ 173 public Rollback() 174 { 175 super(); 176 } 177 178 /** 179 * Creates a rollback exception that is propagated to the caller. 180 * @param cause The Throwable causing the exception. 181 */ 182 public Rollback(Throwable cause) 183 { 184 super(cause); 185 } 186 187 /** 188 * Creates a rollback exception that is propagated to the caller. 189 * @param message Message to include in the exception. 190 * @param cause The throwable causing the exception. 191 */ 192 public Rollback(String message, Throwable cause) 193 { 194 super(message, cause); 195 } 196 } 197 198 /** 199 * Invocation run exception. 200 * <p> 201 * Exception thrown by the execute method when the enclosed 202 * <code>run</code> throws a Rollback exception with a cause. 203 */ 204 public static class InvocationRunException 205 extends java.lang.RuntimeException 206 { 207 /** 208 * Serialization version. 209 */ 210 public final static long serialVersionUID = 1L; 211 212 /** 213 * Creates an exception that is propagated to the caller. 214 * @param message Message to include in the exception. 215 * @param cause The throwable causing the exception. 216 */ 217 public InvocationRunException(String message, Throwable cause) 218 { 219 super(message, cause); 220 } 221 } 222 223 /** 224 * Transaction identifier. 225 * <p> 226 * An identifier for the current transaction. Transaction 227 * identifiers are unique per JVM instance. They are not 228 * unique across JVM restarts. 229 * <p> 230 * Multiple transaction identifiers created in the same 231 * transaction will return the same hash code, and comparisons 232 * using the equals() method will return true. 233 * <p> 234 * Transaction identifiers are provided for logging purposes 235 * only. 236 */ 237 public static class Identifier 238 { 239 private String m_id; 240 241 private Identifier() 242 { 243 m_id = getId(); 244 } 245 246 /** 247 * Returns a string representation of a transaction identifier. 248 * @return The string representation of a transaction identifier. 249 */ 250 @Override 251 public String toString() 252 { 253 return m_id; 254 } 255 256 /** 257 * Determines if two transaction identifiers are the same. 258 * @param id Transaction identifier to compare 259 * @return True if both objects represent the 260 * same transaction identifier. 261 */ 262 @Override 263 public boolean equals(Object id) 264 { 265 if (!(id instanceof Identifier)) 266 { 267 return false; 268 } 269 return m_id.equals(((Identifier)id).m_id); 270 } 271 272 /** 273 * Returns a hash value for this transaction identifier. 274 * @return A hash code value for this transaction identifier. 275 */ 276 @Override 277 public int hashCode() 278 { 279 return m_id.hashCode(); 280 } 281 } 282 283 /** 284 * The Transaction properties used when executing a transaction 285 */ 286 public final static class Properties 287 { 288 /** 289 * Default constructor, sets properties to default values: 290 * <p> 291 isolationLevel = {@link IsolationLevel#SERIALIZABLE}, 292 transactionName is null. 293 */ 294 public Properties() 295 { 296 // 297 // FIX THIS: We can't access the class name of the outer 298 // class, the getEnclosingClass() method will always return 299 // "Transaction". 300 // 301 m_isolationLevel = IsolationLevel.SERIALIZABLE; 302 m_transactionName = null; 303 } 304 /** 305 * Construct a properties instance with the given isolation level 306 * and a null transaction name. 307 * 308 * @param isolationLevel - Isolation level of transaction 309 * when it executes. 310 */ 311 public Properties(IsolationLevel isolationLevel) 312 { 313 m_isolationLevel = isolationLevel; 314 m_transactionName = null; 315 } 316 317 /** 318 * Construct a properties instance with the given name and a 319 * default isolationLevel of {@link IsolationLevel#SERIALIZABLE} 320 * 321 * @param transactionName - Name of transaction when it executes. 322 */ 323 public Properties(String transactionName) 324 { 325 m_transactionName = transactionName; 326 m_isolationLevel = IsolationLevel.SERIALIZABLE; 327 } 328 329 /** 330 * Construct a properties instance with the given name and 331 * isolation level. 332 * 333 * @param transactionName - Name of transaction when it executes. 334 * @param isolationLevel - Isolation level of transaction 335 * when it executes. 336 */ 337 public Properties(String transactionName, IsolationLevel isolationLevel) 338 { 339 m_transactionName = transactionName; 340 m_isolationLevel = isolationLevel; 341 } 342 /** 343 * Set the transaction name. 344 * 345 * @param transactionName - Name of transaction when executed. 346 */ 347 public void setTransactionName(String transactionName) 348 { 349 m_transactionName = transactionName; 350 } 351 /** 352 * Get the transaction name. 353 * <p> 354 * If never set, returns null. If passed into the {@link 355 * Transaction#execute(Properties)} method as null, the name 356 * passed into the {@link Transaction#Transaction(String)} 357 * constructor is used, or the calling class name is used if the 358 * default {@link Transaction#Transaction()} constructor was used. 359 * 360 * @return String containing transaction name. 361 */ 362 public String getTransactionName() 363 { 364 return m_transactionName; 365 } 366 /** 367 * Set the isolation level. 368 * 369 * @param isolationLevel - Isolation level of transaction 370 * when it executes. 371 */ 372 public void setIsolationLevel(IsolationLevel isolationLevel) 373 { 374 m_isolationLevel = isolationLevel; 375 } 376 /** 377 * Get the isolation level. 378 * 379 * @return Current isolation level. 380 */ 381 public IsolationLevel getIsolationLevel() 382 { 383 return m_isolationLevel; 384 } 385 386 private String m_transactionName; 387 private IsolationLevel m_isolationLevel; 388 } 389 390 /** Locked object notifier 391 * <p> 392 * This notifier is used to access all locked objects in a 393 * transaction. The abstract method is called once for 394 * each locked object in a transaction. 395 * <p> 396 * The ObjectNotifier is executed using the 397 * ObjectServices::runObjectNotifier static method. 398 * <p> 399 * Typically, the ObjectNotifier is used in conjunction with 400 * the TransactionNotifier to process objects at prepare or 401 * commit time. 402 * <p> 403 * Note: the notifier will not be invoked for objects created and 404 * deleted in the current transaction. 405 */ 406 public static abstract class ObjectNotifier 407 { 408 /** Called for each object locked in a transaction. 409 * <p> 410 * Will not be invoked for objects created and 411 * deleted in the current transaction. 412 * @param obj Object locked in transaction. 413 */ 414 protected abstract void inTransaction(final Object obj); 415 } 416 417 /** 418 * Creates a new transaction. 419 * @param transactionName - Name associated with the transaction. 420 */ 421 public Transaction(final String transactionName) 422 { 423 m_defaultProperties = new Properties(transactionName); 424 } 425 426 /** 427 * Creates a new transaction. 428 */ 429 public Transaction() 430 { 431 m_defaultProperties = new Properties(getClass().getName()); 432 } 433 434 /** 435 * User defined method that is run in the context of a transaction. 436 * 437 * @exception com.kabira.platform.Transaction.Rollback 438 * Thrown if the transaction should be rolled back 439 * and all changes discarded. 440 */ 441 protected abstract void run() throws Transaction.Rollback; 442 443 /** 444 * Executes the user defined method within a transaction. Any 445 * deadlocks are transparently retried. 446 * 447 * @param properties - Properties used when executing the transaction. 448 * 449 * @return Result of transaction 450 * 451 * @throws InvalidTransactionState 452 * If a transaction is already active, or the transaction services 453 * are not available. 454 * @throws InvocationRunException 455 * The execution of the run method resulted in an exception 456 * that was propagated. 457 */ 458 public final Result execute(Properties properties) 459 throws InvalidTransactionState, InvocationRunException 460 { 461 String transactionName = properties.getTransactionName(); 462 if (transactionName == null) 463 { 464 transactionName = getClass().getName(); 465 } 466 assert( properties.getIsolationLevel() == 467 IsolationLevel.READ_COMMITTED_SNAPSHOT || 468 properties.getIsolationLevel() == 469 IsolationLevel.SERIALIZABLE ); 470 int isolationLevel = (properties.getIsolationLevel() == 471 IsolationLevel.READ_COMMITTED_SNAPSHOT) 472 ? ReadCommittedSnapshot : Serializable; 473 474 boolean done = false; 475 Result result = Result.ROLLBACK; 476 477 m_numDeadlocks = 0; 478 479 while (!done) 480 { 481 begin(transactionName, isolationLevel); 482 try 483 { 484 run(); 485 prepare(); 486 commit(); 487 result = Result.COMMIT; 488 done = true; 489 } 490 catch (Rollback ex) 491 { 492 abort(ROLLBACK_ABORT); 493 if (ex.getCause() != null) 494 { 495 throw new InvocationRunException( 496 ex.getMessage(), ex.getCause()); 497 } 498 done = true; 499 } 500 catch (com.kabira.ktvm.transaction.DeadlockError ex) 501 { 502 abort(DEADLOCK_ABORT); 503 504 StringWriter stringWriter = new StringWriter(); 505 PrintWriter printWriter = new PrintWriter(stringWriter); 506 ex.printStackTrace(printWriter); 507 508 deadlockBackoff(stringWriter.toString(), ++m_numDeadlocks); 509 } 510 catch (java.lang.Error ex) 511 { 512 abort(EXCEPTION_ABORT); 513 throw ex; 514 } 515 catch (java.lang.RuntimeException ex) 516 { 517 abort(EXCEPTION_ABORT); 518 throw ex; 519 } 520 } 521 return result; 522 } 523 524 /** 525 * Executes the user defined method within a transaction. Any 526 * deadlocks are transparently retried. 527 * <p> 528 * The default isolationLevel of SERIALIZABLE is used for the 529 * transaction. 530 * 531 * @return Result of transaction 532 * @throws InvalidTransactionState 533 * If a transaction is already active, or the transaction services 534 * are not available. 535 * @throws InvocationRunException 536 * The execution of the run method resulted in an exception 537 * that was propagated. 538 */ 539 public final Result execute() 540 throws InvalidTransactionState, InvocationRunException 541 { 542 return execute(m_defaultProperties); 543 } 544 545 /** 546 * Returns the transaction identifier for the current transaction. 547 * 548 * This only returns a valid transaction identifier when there 549 * is an active transaction. 550 * 551 * @throws java.lang.IllegalAccessError 552 * No active transaction 553 * 554 * @return The current transaction identifier. 555 */ 556 public static final Identifier getIdentifier() 557 throws IllegalAccessError 558 { 559 return new Identifier(); 560 } 561 562 /** 563 * Returns the transaction properties for the current transaction. 564 * 565 * This only returns the properties when there is an active transaction. 566 * 567 * @throws java.lang.IllegalAccessError 568 * No active transaction 569 * 570 * @return The properties for the given active transaction. 571 */ 572 public static final Properties getActiveTransactionProperties() 573 throws IllegalAccessError 574 { 575 String transactionName = getTransactionName(); 576 577 int ilevel = getIsolationLevel(); 578 assert( ilevel == ReadCommittedSnapshot || ilevel == Serializable ); 579 580 IsolationLevel isolationLevel = (ilevel == ReadCommittedSnapshot) 581 ? IsolationLevel.READ_COMMITTED_SNAPSHOT 582 : IsolationLevel.SERIALIZABLE; 583 584 return new Properties(transactionName, isolationLevel); 585 } 586 587 /** 588 * Determine if object was created in the current transaction. 589 * <p> 590 * This operation can be used to determine if the specified object 591 * was created in the current transaction. 592 * 593 * @param obj Object to check 594 * @throws java.lang.IllegalAccessError No active transaction 595 * @throws ManagedClassError If obj is not Managed. 596 * @return true if the object was created. 597 */ 598 public static boolean createdInTransaction(Object obj) 599 throws IllegalAccessError 600 { 601 return _createdInTransaction( 602 ManagedObject.getObjectReference(obj, "createdInTransaction")); 603 } 604 605 /** 606 * Determine if object was deleted in the current transaction. 607 * <p> 608 * This operation can be used to determine if the specified object 609 * was deleted in the current transaction. 610 * <p> 611 * <b>Warning</b>: 612 * If an instance has been deleted in a transaction, any attempt 613 * to cast the instance to a subtype will return an empty handle. 614 * 615 * @param obj Object to check 616 * @throws java.lang.IllegalAccessError No active transaction 617 * @throws ManagedClassError If obj is not Managed. 618 * @return true if the object was deleted. 619 */ 620 public static boolean deletedInTransaction(Object obj) 621 { 622 return _deletedInTransaction( 623 ManagedObject.getObjectReference(obj, "deletedInTransaction")); 624 } 625 626 /** 627 * Determine if object was modified in the current transaction. 628 * <p> 629 * This operation can be used to determine if the specified object 630 * was modified in the current transaction. 631 * 632 * @param obj Object to check 633 * @throws java.lang.IllegalAccessError No active transaction 634 * @throws ManagedClassError If obj is not Managed. 635 * @return true if the object was modified. 636 */ 637 public static boolean modifiedInTransaction(Object obj) 638 { 639 return _modifiedInTransaction( 640 ManagedObject.getObjectReference(obj, "modifiedInTransaction")); 641 } 642 643 /** Run an object notifier 644 * <p> 645 * When called, the inTransaction method in the ObjectNotifier is 646 * called for each locked object in the current transaction. 647 * @param objectNotifier Application notifier. 648 * @throws java.lang.IllegalAccessError No active transaction 649 */ 650 public static void runObjectNotifier(ObjectNotifier objectNotifier) 651 { 652 _runObjectNotifier(objectNotifier); 653 } 654 655 /** 656 * Determines if a transaction is active. 657 * 658 * @return true if a transaction is active, false otherwise. 659 */ 660 public native static boolean isActive(); 661 662 /** 663 * Returns the current setting of the transaction description. 664 * 665 * @return Description, as set via setTransactionDescription(). 666 */ 667 public native static final String getTransactionDescription(); 668 669 /** 670 * Sets the current transaction description. 671 * 672 * The description is cleared when the transaction begins. 673 * 674 * @param description The value to set. 675 */ 676 public native static final void setTransactionDescription( 677 String description); 678 679 /** 680 * Returns the number of deadlocks detected and replayed for the last 681 * call to execute(). 682 * 683 * @return The number of detected deadlocks. 684 */ 685 public final int getNumberDeadlocks() 686 { 687 return m_numDeadlocks; 688 } 689 690 /** 691 * Establishes a write lock for the given object. 692 * 693 * @param obj Object to lock. 694 * 695 * 696 * @throws java.lang.IllegalAccessError 697 * No active transaction 698 * 699 * @throws NotTransactional 700 * If the object passed in is not Managed. 701 */ 702 public static final void writeLockObject(Object obj) 703 throws IllegalAccessError, NotTransactional 704 { 705 validateObject(obj, "writeLockObject"); 706 lockObject(WRITE_LOCK, ManagedObject.getObjectReference(obj)); 707 } 708 709 /** 710 * Establishes a read lock for the given object. 711 * <p> 712 * If the current transaction isolation level is {@link 713 * IsolationLevel#READ_COMMITTED_SNAPSHOT}, a snapshot of the data is 714 * taken and the read lock immediately released. 715 * 716 * @param obj Object to lock. 717 * 718 * @throws java.lang.IllegalAccessError 719 * No active transaction 720 * 721 * @throws NotTransactional 722 * If the object passed in is not Managed. 723 */ 724 public static final void readLockObject(Object obj) 725 throws IllegalAccessError, NotTransactional 726 { 727 validateObject(obj, "readLockObject"); 728 lockObject(READ_LOCK, ManagedObject.getObjectReference(obj)); 729 } 730 731 /** 732 * Determines if a write lock exists for an object in the current transaction. 733 * 734 * @param obj Object to examine. 735 * 736 * @throws java.lang.IllegalAccessError 737 * No active transaction 738 * 739 * @throws NotTransactional 740 * If the object passed in is not Managed. 741 * 742 * @return true if a write lock exists on 743 * object in current transaction, false otherwise. 744 */ 745 public static final boolean hasWriteLock(Object obj) 746 throws IllegalAccessError, NotTransactional 747 { 748 validateObject(obj, "hasWriteLock"); 749 return hasLock(WRITE_LOCK, ManagedObject.getObjectReference(obj)); 750 } 751 752 /** 753 * Determines if a read lock exists for an object in the current transaction. 754 * <p> 755 * If the current transaction isolation level is {@link 756 * IsolationLevel#READ_COMMITTED_SNAPSHOT}, readlocks are immediately 757 * released after a snapshot is taken. In this case hasReadLock() 758 * will always return false. 759 * 760 * @param obj Object to examine 761 * 762 * @throws java.lang.IllegalAccessError 763 * No active transaction 764 * 765 * @throws NotTransactional 766 * If the object passed in is not Managed. 767 * 768 * @return true if a read lock exists on 769 * object in current transaction, false otherwise. 770 */ 771 public static final boolean hasReadLock(Object obj) 772 throws IllegalAccessError, NotTransactional 773 { 774 validateObject(obj, "hasReadLock"); 775 return hasLock(READ_LOCK, ManagedObject.getObjectReference(obj)); 776 } 777 778 // 779 // Validate the object can be used in a transaction. 780 // 781 private static void validateObject(Object obj, String op) 782 { 783 if (! ManagedObject.isManaged(obj)) 784 { 785 throw new NotTransactional(op + " cannot be used " + 786 "on a non-Managed object"); 787 } 788 } 789 790 // 791 // These must match the LockType enum used in the C++ native methods 792 // 793 private static final int READ_LOCK = 1; 794 private static final int WRITE_LOCK = 2; 795 // 796 // These must match the ObjSrv::TxnIsolationLevel enum used in the 797 // C++ native methods. 798 // 799 private static final int Serializable = 1; 800 private static final int ReadCommittedSnapshot = 2; 801 // 802 // These must match the ObjectType enum used in the C++ native methods 803 // 804 private static final int JAVA_OBJECT = 1; 805 private static final int MANAGED_OBJECT = 2; 806 // 807 // These must match the ObjSrv::AbortReason enum used in the 808 // C++ native methods 809 // 810 private static final int DEADLOCK_ABORT = 1; 811 private static final int ROLLBACK_ABORT = 2; 812 private static final int EXCEPTION_ABORT = 3; 813 814 // 815 // Used to manage getNumDeadlocks() 816 // 817 private int m_numDeadlocks; 818 819 // 820 // Properties for transaction 821 // 822 private Properties m_defaultProperties; 823 824 // 825 // native methods 826 // 827 private native static void begin( 828 final String transactionName, int isolationLevel) 829 throws InvalidTransactionState; 830 private native void prepare(); 831 private native void commit(); 832 private native static void abort(int abortReason); 833 private native static void deadlockBackoff( 834 String tranName, int numDeadlocks); 835 private native static void lockObject( 836 int lockType, final byte[] objRef); 837 private native static boolean hasLock( 838 int lockType, final byte[] objRef); 839 private native static final String getId(); 840 private native static final String getTransactionName(); 841 private native static final int getIsolationLevel(); 842 private native static final boolean _createdInTransaction( 843 final byte[] objectReference); 844 private native static final boolean _deletedInTransaction( 845 final byte[] objectReference); 846 private native static final boolean _modifiedInTransaction( 847 final byte[] objectReference); 848 private native static void _runObjectNotifier( 849 ObjectNotifier objectNotifier); 850}