001// 002// Name 003// $RCSfile: Transaction.java,v $ 004// 005// COPYRIGHT 006// Copyright 2008-2012 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 007// Cloud Software Group, Inc. Confidential Information 008// 009// History 010// $Revision: 1.1.2.20 $ $Date: 2012/05/03 17:55:41 $ 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 /** 391 * Creates a new transaction. 392 * @param transactionName - Name associated with the transaction. 393 */ 394 public Transaction(final String transactionName) 395 { 396 m_defaultProperties = new Properties(transactionName); 397 } 398 399 /** 400 * Creates a new transaction. 401 */ 402 public Transaction() 403 { 404 m_defaultProperties = new Properties(getClass().getName()); 405 } 406 407 /** 408 * User defined method that is run in the context of a transaction. 409 * 410 * @exception com.kabira.platform.Transaction.Rollback 411 * Thrown if the transaction should be rolled back 412 * and all changes discarded. 413 */ 414 protected abstract void run() throws Transaction.Rollback; 415 416 /** 417 * Executes the user defined method within a transaction. Any 418 * deadlocks are transparently retried. 419 * 420 * @param properties - Properties used when executing the transaction. 421 * 422 * @return Result of transaction 423 * 424 * @throws InvalidTransactionState 425 * If a transaction is already active, or the transaction services 426 * are not available. 427 * @throws InvocationRunException 428 * The execution of the run method resulted in an exception 429 * that was propagated. 430 */ 431 public final Result execute(Properties properties) 432 throws InvalidTransactionState, InvocationRunException 433 { 434 String transactionName = properties.getTransactionName(); 435 if (transactionName == null) 436 { 437 transactionName = getClass().getName(); 438 } 439 assert( properties.getIsolationLevel() == 440 IsolationLevel.READ_COMMITTED_SNAPSHOT || 441 properties.getIsolationLevel() == 442 IsolationLevel.SERIALIZABLE ); 443 int isolationLevel = (properties.getIsolationLevel() == 444 IsolationLevel.READ_COMMITTED_SNAPSHOT) 445 ? ReadCommittedSnapshot : Serializable; 446 447 boolean done = false; 448 Result result = Result.ROLLBACK; 449 450 m_numDeadlocks = 0; 451 452 while (!done) 453 { 454 begin(transactionName, isolationLevel); 455 try 456 { 457 run(); 458 prepare(); 459 commit(); 460 result = Result.COMMIT; 461 done = true; 462 } 463 catch (Rollback ex) 464 { 465 abort(null); 466 if (ex.getCause() != null) 467 { 468 throw new InvocationRunException( 469 ex.getMessage(), ex.getCause()); 470 } 471 done = true; 472 } 473 catch (com.kabira.ktvm.transaction.DeadlockError ex) 474 { 475 StringWriter sw = new StringWriter(); 476 ex.printStackTrace(new PrintWriter(sw)); 477 abort(sw.toString()); 478 deadlockBackoff(transactionName, ++m_numDeadlocks); 479 } 480 catch (java.lang.Error ex) 481 { 482 abort(null); 483 throw ex; 484 } 485 catch (java.lang.RuntimeException ex) 486 { 487 abort(null); 488 throw ex; 489 } 490 } 491 return result; 492 } 493 494 /** 495 * Executes the user defined method within a transaction. Any 496 * deadlocks are transparently retried. 497 * <p> 498 * The default isolationLevel of SERIALIZABLE is used for the 499 * transaction. 500 * 501 * @return Result of transaction 502 * @throws InvalidTransactionState 503 * If a transaction is already active, or the transaction services 504 * are not available. 505 * @throws InvocationRunException 506 * The execution of the run method resulted in an exception 507 * that was propagated. 508 */ 509 public final Result execute() 510 throws InvalidTransactionState, InvocationRunException 511 { 512 return execute(m_defaultProperties); 513 } 514 515 /** 516 * Returns the transaction identifier for the current transaction. 517 * 518 * This only returns a valid transaction identifier when there 519 * is an active transaction. 520 * 521 * @throws java.lang.IllegalAccessError 522 * No active transaction 523 * 524 * @return The current transaction identifier. 525 */ 526 public static final Identifier getIdentifier() 527 throws IllegalAccessError 528 { 529 return new Identifier(); 530 } 531 532 /** 533 * Returns the transaction properties for the current transaction. 534 * 535 * This only returns the properties when there is an active transaction. 536 * 537 * @throws java.lang.IllegalAccessError 538 * No active transaction 539 * 540 * @return The properties for the given active transaction. 541 */ 542 public static final Properties getActiveTransactionProperties() 543 throws IllegalAccessError 544 { 545 String transactionName = getTransactionName(); 546 547 int ilevel = getIsolationLevel(); 548 assert( ilevel == ReadCommittedSnapshot || ilevel == Serializable ); 549 550 IsolationLevel isolationLevel = (ilevel == ReadCommittedSnapshot) 551 ? IsolationLevel.READ_COMMITTED_SNAPSHOT 552 : IsolationLevel.SERIALIZABLE; 553 554 return new Properties(transactionName, isolationLevel); 555 } 556 557 /** 558 * Determines if a transaction is active. 559 * 560 * @return true if a transaction is active, false otherwise. 561 */ 562 public native static boolean isActive(); 563 564 /** 565 * Returns the number of deadlocks detected and replayed for the last 566 * call to execute(). 567 * 568 * @return The number of detected deadlocks. 569 */ 570 public final int getNumberDeadlocks() 571 { 572 return m_numDeadlocks; 573 } 574 575 /** 576 * Establishes a write lock for the given object. 577 * 578 * @param obj Object to lock. 579 * 580 * 581 * @throws java.lang.IllegalAccessError 582 * No active transaction 583 * 584 * @throws NotTransactional 585 * If the object passed in is not Managed. 586 */ 587 public static final void writeLockObject(Object obj) 588 throws IllegalAccessError, NotTransactional 589 { 590 validateObject(obj, "writeLockObject"); 591 lockObject(getObjectType(obj), WRITE_LOCK, obj); 592 } 593 594 /** 595 * Establishes a read lock for the given object. 596 * <p> 597 * If the current transaction isolation level is {@link 598 * IsolationLevel#READ_COMMITTED_SNAPSHOT}, a snapshot of the data is 599 * taken and the read lock immediately released. 600 * 601 * @param obj Object to lock. 602 * 603 * @throws java.lang.IllegalAccessError 604 * No active transaction 605 * 606 * @throws NotTransactional 607 * If the object passed in is not Managed. 608 */ 609 public static final void readLockObject(Object obj) 610 throws IllegalAccessError, NotTransactional 611 { 612 validateObject(obj, "readLockObject"); 613 lockObject(getObjectType(obj), READ_LOCK, obj); 614 } 615 616 /** 617 * Determines if a write lock exists for the given object. 618 * 619 * @param obj Object to examine. 620 * 621 * @throws java.lang.IllegalAccessError 622 * No active transaction 623 * 624 * @throws NotTransactional 625 * If the object passed in is not Managed. 626 * 627 * @return true if a write lock exists on the given 628 * object, false otherwise. 629 */ 630 public static final boolean hasWriteLock(Object obj) 631 throws IllegalAccessError, NotTransactional 632 { 633 validateObject(obj, "hasWriteLock"); 634 return hasLock(getObjectType(obj), WRITE_LOCK, obj); 635 } 636 637 /** 638 * Determines if a read lock exists for the given object. 639 * <p> 640 * If the current transaction isolation level is {@link 641 * IsolationLevel#READ_COMMITTED_SNAPSHOT}, readlocks are immediately 642 * released after a snapshot is taken. In this case hasReadLock() 643 * will always return false. 644 * 645 * @param obj Object to examine 646 * 647 * @throws java.lang.IllegalAccessError 648 * No active transaction 649 * 650 * @throws NotTransactional 651 * If the object passed in is not Managed. 652 * 653 * @return true if a read lock exists on the given 654 * object, false otherwise. 655 */ 656 public static final boolean hasReadLock(Object obj) 657 throws IllegalAccessError, NotTransactional 658 { 659 validateObject(obj, "hasReadLock"); 660 return hasLock(getObjectType(obj), READ_LOCK, obj); 661 } 662 663 // 664 // Determine if this obj passed in is ManagedObject 665 // 666 @SuppressWarnings("deprecation") 667 private static int getObjectType(Object obj) 668 { 669 Class<?> cl = obj.getClass(); 670 if (cl.isAnnotationPresent( 671 com.kabira.platform.annotation.Distributed.class) 672 || cl.isAnnotationPresent(Managed.class)) 673 { 674 return MANAGED_OBJECT; 675 } 676 return JAVA_OBJECT; 677 } 678 679 // 680 // Validate the object can be used in a transaction. 681 // 682 @SuppressWarnings("deprecation") 683 private static void validateObject(Object obj, String op) 684 { 685 Class<?> cl = obj.getClass(); 686 if ((!cl.isAnnotationPresent( 687 com.kabira.platform.annotation.Distributed.class)) 688 && (!cl.isAnnotationPresent(Managed.class))) 689 { 690 throw new NotTransactional(op + " cannot be used " + 691 "on a non-Managed object"); 692 } 693 } 694 695 // 696 // These must match the LockType enum used in the C++ native methods 697 // 698 private static final int READ_LOCK = 1; 699 private static final int WRITE_LOCK = 2; 700 // 701 // These must match the ObjSrv::TxnIsolationLevel enum used in the C++ native 702 // methods. 703 // 704 private static final int Serializable = 1; 705 private static final int ReadCommittedSnapshot = 2; 706 // 707 // These must match the ObjectType enum used in the C++ native methods 708 // 709 private static final int JAVA_OBJECT = 1; 710 private static final int MANAGED_OBJECT = 2; 711 712 // 713 // Used to manage getNumDeadlocks() 714 // 715 private int m_numDeadlocks; 716 717 // 718 // Properties for transaction 719 // 720 private Properties m_defaultProperties; 721 722 // 723 // native methods 724 // 725 private native static void begin( 726 final String transactionName, int isolationLevel) 727 throws InvalidTransactionState; 728 private native void prepare(); 729 private native void commit(); 730 private native static void abort(String st); 731 private native static void deadlockBackoff( 732 String tranName, int numDeadlocks); 733 private native static void lockObject( 734 int objType, int lockType, Object obj); 735 private native static boolean hasLock( 736 int objType, int lockType, Object obj); 737 private native static final String getId(); 738 private native static final String getTransactionName(); 739 private native static final int getIsolationLevel(); 740}