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}