001// 002// Name 003// $RCSfile: Transaction.java,v $ 004// 005// COPYRIGHT 006// Copyright 2008-2017 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 007// Cloud Software Group, Inc. Confidential Information 008// 009// History 010// $Revision: 1.1.2.31.2.2 $ $Date: 2017/01/26 17:43:13 $ 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(ex.getDeadlockType(), 509 stringWriter.toString(), ++m_numDeadlocks); 510 } 511 catch (java.lang.Error ex) 512 { 513 abort(EXCEPTION_ABORT); 514 throw ex; 515 } 516 catch (java.lang.RuntimeException ex) 517 { 518 abort(EXCEPTION_ABORT); 519 throw ex; 520 } 521 } 522 return result; 523 } 524 525 /** 526 * Executes the user defined method within a transaction. Any 527 * deadlocks are transparently retried. 528 * <p> 529 * The default isolationLevel of SERIALIZABLE is used for the 530 * transaction. 531 * 532 * @return Result of transaction 533 * @throws InvalidTransactionState 534 * If a transaction is already active, or the transaction services 535 * are not available. 536 * @throws InvocationRunException 537 * The execution of the run method resulted in an exception 538 * that was propagated. 539 */ 540 public final Result execute() 541 throws InvalidTransactionState, InvocationRunException 542 { 543 return execute(m_defaultProperties); 544 } 545 546 /** 547 * Returns the transaction identifier for the current transaction. 548 * 549 * This only returns a valid transaction identifier when there 550 * is an active transaction. 551 * 552 * @throws java.lang.IllegalAccessError 553 * No active transaction 554 * 555 * @return The current transaction identifier. 556 */ 557 public static final Identifier getIdentifier() 558 throws IllegalAccessError 559 { 560 return new Identifier(); 561 } 562 563 /** 564 * Returns the transaction properties for the current transaction. 565 * 566 * This only returns the properties when there is an active transaction. 567 * 568 * @throws java.lang.IllegalAccessError 569 * No active transaction 570 * 571 * @return The properties for the given active transaction. 572 */ 573 public static final Properties getActiveTransactionProperties() 574 throws IllegalAccessError 575 { 576 String transactionName = getTransactionName(); 577 578 int ilevel = getIsolationLevel(); 579 assert( ilevel == ReadCommittedSnapshot || ilevel == Serializable ); 580 581 IsolationLevel isolationLevel = (ilevel == ReadCommittedSnapshot) 582 ? IsolationLevel.READ_COMMITTED_SNAPSHOT 583 : IsolationLevel.SERIALIZABLE; 584 585 return new Properties(transactionName, isolationLevel); 586 } 587 588 /** 589 * Determine if object was created in the current transaction. 590 * <p> 591 * This operation can be used to determine if the specified object 592 * was created in the current transaction. 593 * 594 * @param obj Object to check 595 * @throws java.lang.IllegalAccessError No active transaction 596 * @throws ManagedClassError If obj is not Managed. 597 * @return true if the object was created. 598 */ 599 public static boolean createdInTransaction(Object obj) 600 throws IllegalAccessError 601 { 602 return _createdInTransaction( 603 ManagedObject.getObjectReference(obj, "createdInTransaction")); 604 } 605 606 /** 607 * Determine if object was deleted in the current transaction. 608 * <p> 609 * This operation can be used to determine if the specified object 610 * was deleted in the current transaction. 611 * <p> 612 * <b>Warning</b>: 613 * If an instance has been deleted in a transaction, any attempt 614 * to cast the instance to a subtype will return an empty handle. 615 * 616 * @param obj Object to check 617 * @throws java.lang.IllegalAccessError No active transaction 618 * @throws ManagedClassError If obj is not Managed. 619 * @return true if the object was deleted. 620 */ 621 public static boolean deletedInTransaction(Object obj) 622 { 623 return _deletedInTransaction( 624 ManagedObject.getObjectReference(obj, "deletedInTransaction")); 625 } 626 627 /** 628 * Determine if object was modified in the current transaction. 629 * <p> 630 * This method can be used to determine if an object 631 * was modified in the current transaction. An object is 632 * considered to have been modified if it was created or updated 633 * in the current transaction. 634 * 635 * @param obj Object to check 636 * @throws java.lang.IllegalAccessError No active transaction 637 * @throws ManagedClassError If obj is not Managed. 638 * @return true if the object was modified. 639 */ 640 public static boolean modifiedInTransaction(Object obj) 641 { 642 return _modifiedInTransaction( 643 ManagedObject.getObjectReference(obj, "modifiedInTransaction")); 644 } 645 646 /** Run an object notifier 647 * <p> 648 * When called, the inTransaction method in the ObjectNotifier is 649 * called for each locked object in the current transaction. 650 * @param objectNotifier Application notifier. 651 * @throws java.lang.IllegalAccessError No active transaction 652 */ 653 public static void runObjectNotifier(ObjectNotifier objectNotifier) 654 { 655 _runObjectNotifier(objectNotifier); 656 } 657 658 /** 659 * Determines if a transaction is active. 660 * 661 * @return true if a transaction is active, false otherwise. 662 */ 663 public native static boolean isActive(); 664 665 /** 666 * Returns the current setting of the transaction description. 667 * 668 * @return Description, as set via setTransactionDescription(). 669 */ 670 public native static final String getTransactionDescription(); 671 672 /** 673 * Sets the current transaction description. 674 * 675 * The description is cleared when the transaction begins. 676 * 677 * @param description The value to set. 678 */ 679 public native static final void setTransactionDescription( 680 String description); 681 682 /** 683 * Returns the number of deadlocks detected and replayed for the last 684 * call to execute(). 685 * 686 * @return The number of detected deadlocks. 687 */ 688 public final int getNumberDeadlocks() 689 { 690 return m_numDeadlocks; 691 } 692 693 /** 694 * Establishes a write lock for the given object. 695 * 696 * @param obj Object to lock. 697 * 698 * 699 * @throws java.lang.IllegalAccessError 700 * No active transaction 701 * 702 * @throws NotTransactional 703 * If the object passed in is not Managed. 704 */ 705 public static final void writeLockObject(Object obj) 706 throws IllegalAccessError, NotTransactional 707 { 708 validateObject(obj, "writeLockObject"); 709 lockObject(WRITE_LOCK, ManagedObject.getObjectReference(obj)); 710 } 711 712 /** 713 * Establishes a read lock for the given object. 714 * <p> 715 * If the current transaction isolation level is {@link 716 * IsolationLevel#READ_COMMITTED_SNAPSHOT}, a snapshot of the data is 717 * taken and the read lock immediately released. 718 * 719 * @param obj Object to lock. 720 * 721 * @throws java.lang.IllegalAccessError 722 * No active transaction 723 * 724 * @throws NotTransactional 725 * If the object passed in is not Managed. 726 */ 727 public static final void readLockObject(Object obj) 728 throws IllegalAccessError, NotTransactional 729 { 730 validateObject(obj, "readLockObject"); 731 lockObject(READ_LOCK, ManagedObject.getObjectReference(obj)); 732 } 733 734 /** 735 * Determines if a write lock exists for an object in the current transaction. 736 * 737 * @param obj Object to examine. 738 * 739 * @throws java.lang.IllegalAccessError 740 * No active transaction 741 * 742 * @throws NotTransactional 743 * If the object passed in is not Managed. 744 * 745 * @return true if a write lock exists on 746 * object in current transaction, false otherwise. 747 */ 748 public static final boolean hasWriteLock(Object obj) 749 throws IllegalAccessError, NotTransactional 750 { 751 validateObject(obj, "hasWriteLock"); 752 return hasLock(WRITE_LOCK, ManagedObject.getObjectReference(obj)); 753 } 754 755 /** 756 * Determines if a read lock exists for an object in the current transaction. 757 * <p> 758 * If the current transaction isolation level is {@link 759 * IsolationLevel#READ_COMMITTED_SNAPSHOT}, readlocks are immediately 760 * released after a snapshot is taken. In this case hasReadLock() 761 * will always return false. 762 * 763 * @param obj Object to examine 764 * 765 * @throws java.lang.IllegalAccessError 766 * No active transaction 767 * 768 * @throws NotTransactional 769 * If the object passed in is not Managed. 770 * 771 * @return true if a read lock exists on 772 * object in current transaction, false otherwise. 773 */ 774 public static final boolean hasReadLock(Object obj) 775 throws IllegalAccessError, NotTransactional 776 { 777 validateObject(obj, "hasReadLock"); 778 return hasLock(READ_LOCK, ManagedObject.getObjectReference(obj)); 779 } 780 781 // 782 // Validate the object can be used in a transaction. 783 // 784 private static void validateObject(Object obj, String op) 785 { 786 if (! ManagedObject.isManaged(obj)) 787 { 788 throw new NotTransactional(op + " cannot be used " + 789 "on a non-Managed object"); 790 } 791 } 792 793 // 794 // These must match the LockType enum used in the C++ native methods 795 // 796 private static final int READ_LOCK = 1; 797 private static final int WRITE_LOCK = 2; 798 // 799 // These must match the ObjSrv::TxnIsolationLevel enum used in the 800 // C++ native methods. 801 // 802 private static final int Serializable = 1; 803 private static final int ReadCommittedSnapshot = 2; 804 // 805 // These must match the ObjectType enum used in the C++ native methods 806 // 807 private static final int JAVA_OBJECT = 1; 808 private static final int MANAGED_OBJECT = 2; 809 // 810 // These must match the ObjSrv::AbortReason enum used in the 811 // C++ native methods 812 // 813 private static final int DEADLOCK_ABORT = 1; 814 private static final int ROLLBACK_ABORT = 2; 815 private static final int EXCEPTION_ABORT = 3; 816 817 // 818 // Used to manage getNumDeadlocks() 819 // 820 private int m_numDeadlocks; 821 822 // 823 // Properties for transaction 824 // 825 private Properties m_defaultProperties; 826 827 // 828 // native methods 829 // 830 private native static void begin( 831 final String transactionName, int isolationLevel) 832 throws InvalidTransactionState; 833 private native void prepare(); 834 private native void commit(); 835 private native static void abort(int abortReason); 836 private native static void deadlockBackoff( 837 int deadlockType, String tranName, int numDeadlocks); 838 private native static void lockObject( 839 int lockType, final byte[] objRef); 840 private native static boolean hasLock( 841 int lockType, final byte[] objRef); 842 private native static final String getId(); 843 private native static final String getTransactionName(); 844 private native static final int getIsolationLevel(); 845 private native static final boolean _createdInTransaction( 846 final byte[] objectReference); 847 private native static final boolean _deletedInTransaction( 848 final byte[] objectReference); 849 private native static final boolean _modifiedInTransaction( 850 final byte[] objectReference); 851 private native static void _runObjectNotifier( 852 ObjectNotifier objectNotifier); 853}