Notifiers

ActiveSpaces® Transactions supports notification of transaction resolution using the com.kabira.platform.swbuiltin.TransactionNotifier class. This class provides onPrepare, onRollback and onCommit methods that can be implemented as required to integrate with external transactional and non-transactional resources.

Multiple transaction notifiers can be created during the same transaction. The appropriate method is called for each notifier instance created as the transaction progresses. The order in which multiple notifiers is called is undefined so there should be no order assumptions in the notifiers. A notifier that is created in a transaction can be deleted before the transaction completes. In this case, the notifier is not called when the transaction completes.

The onPrepare method is always called before onCommit. The onPrepare method can be used to prepare external XA transactional resources. onPrepare can report a failure by throwing an exception. If that happens, the transaction will be rolled back.

[Note]

The onPrepare, onCommit, and onRollback methods are always invoked on the node where the transaction is executing. This is true even if the notifier is a distributed object. If other methods are called on a distributed notifier object from within the onPrepare, onCommit, or onRollback methods, they are dispatched to the remote node.

When a failure occurs compensation must be done to ensure that any work that was completed before the failure is restored to its initial state. ActiveSpaces® Transactions transactional resources are automatically restored to their initial state by rolling back any changes when a transaction aborts - eliminating any need for compensation. However, non-transactional resources (e.g. a file or network connection) that are modified during a transaction, may require state changes to be explicitly restored by the application using manual compensation. The onPrepare, onCommit, and onRollback methods can be used by applications to implement manual compensation for non-transactional resources.

Example 4.12. Transaction notifiers

//     $Revision: 1.1.2.1 $

package com.kabira.snippets.transactions;

import com.kabira.platform.Transaction;
import com.kabira.platform.swbuiltin.TransactionNotifier;

/**
 * Snippet showing transaction notifiers
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class Notifiers extends Transaction
{
    /**
     * Transaction notifier
     */
    static class Compensation extends TransactionNotifier
    {
        String        name;
        
        /*
         * Prepare notifier
         */
        @Override
        public void onPrepare()
        {
            //
            //    Perform application specific prepare processing
            //
            System.out.println(name + ": onPrepare called");           
        }

        /**
         * Rollback notifier
         */
        @Override
        public void onRollback()
        {
            //
            //    Perform application specific rollback processing
            //
            System.out.println(name + ": onRollback called");

            //
            //    Do not need to call delete.  The notifier instance
            //    deletes itself.
            //
        }

        /**
         * Commit notifier
         */
        @Override
        public void onCommit()
        {
            //
            //    Perform application specific commit processing
            //
            System.out.println(name + ": onCommit called");


            //
            //    Do not need to call delete.  The notifier instance
            //    deletes itself.
            //
        }
    }
    Transaction.Result    result;
    
    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String [] args)
    {
        Notifiers    notifiers = new Notifiers();
        
        notifiers.result = Result.COMMIT;
        notifiers.execute();
        
        notifiers.result = Result.ROLLBACK;
        notifiers.execute();
    }

    /**
     * Transaction run method
     * 
     * @throws com.kabira.platform.Transaction.Rollback
     */
    @Override
    protected void run() throws Transaction.Rollback
    {
        op1();
        op2();
        op3();
        
        if (result == Result.ROLLBACK)
        {
            throw new Transaction.Rollback();
        }
    }
    
    /**
     * Operation 1
     * <p>
     * Create a transaction notifier to compensate for this operation
     */
    public void op1()
    {
        Compensation    compensation = new Compensation();
        compensation.name = "op1";
    }
    
    /**
     * Operation 2
     * <p>
     * Create a transaction notifier to compensate for this operation
     */
    public void op2()
    {
        Compensation    compensation = new Compensation();
        compensation.name = "op2";
    }
    /**
     * Operation 3
     * <p>
     * Create a transaction notifier to compensate for this operation
     */
    public void op3()
    {
        Compensation    compensation = new Compensation();
        compensation.name = "op3";
    }
}

When Example 4.12, “Transaction notifiers” is run it outputs (annotation added):

Example 4.13. Transaction notifier output

#
#     prepare methods call before commit
#
[A] op1: onPrepare called
[A] op2: onPrepare called
[A] op3: onPrepare called

#
#     commit compensation methods
#
[A] op1: onCommit called
[A] op2: onCommit called
[A] op3: onCommit called

#
#    no prepare methods called because transaction was explicitly rolled back in execute
#    before commit processing started
#

#
#    rollback compensation methods
#
[A] op1: onRollback called
[A] op2: onRollback called
[A] op3: onRollback called

Transaction notifier restrictions

The onCommit and onRollback methods in a transaction notifier have the following restrictions:

  • Partitioned objects cannot be modified.

  • No new transaction locks can be taken.

Attempting to modify a partitioned object in the onCommit or onRollback methods will cause a com.kabira.platform.ResourceUnavailableException to be thrown causing the transactional work to be discarded.

All transaction locks must be taken before the onCommit or onRollback methods are called. This restriction also precludes any Managed Objects from being created or deleted in these methods because an object create takes an implicit write lock. It is legal to take new transaction locks in the onPrepare method.

[Warning]

Incorrect usage of transaction notifiers can cause an ActiveSpaces® Transactions node to exit with a fatal error.

Managed Objects can be deleted in the onPrepare, onCommit or onRollback methods if they are already write-locked.

See the section called “Explicit locking”.

The example below shows both legal and illegal object creates in the onCommit method.

[Warning]

Running Example 4.14, “Transaction notifier object locks” with the new Extent() line uncommented will cause a node to exit with the following fatal exception.

Attempted to create a new com.kabira.snippets.transactions.Extent object from within a commit trigger.

Example 4.14. Transaction notifier object locks

//     $Revision: 1.1.2.1 $
package com.kabira.snippets.transactions;

import com.kabira.platform.Transaction;
import com.kabira.platform.annotation.Managed;
import com.kabira.platform.swbuiltin.TransactionNotifier;

/**
 * Snippet showing illegal transaction locking in notifiers
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class NotifierLocks
{
    /**
     * Managed objects have an extent
     */
    @Managed
    public static class Extent
    {
    };

    /**
     * Non-managed objects do not have an extent
     */
    public static class NoExtent
    {
    };

    /**
     * Transaction notifier that takes an illegal lock
     */
    public static class IllegalLock extends TransactionNotifier
    {
        /**
         * Commit notifier
         */
        @Override
        public void onCommit()
        {
            System.out.println("IllegalLock: onCommit called");

            //
            //  Attempt to create a new extented object
            //  at commit time - this is illegal since an
            //  implicit write lock is taken on the extent.
            //
            //  Uncommenting this line will cause the Fluency node to
            //  exit with a fatal error.
            //
            // new Extent();
        }
    }

    /**
     * Transaction notifier that takes a legal lock
     */
    public static class LegalLock extends TransactionNotifier
    {
        /**
         * Commit notifier
         */
        @Override
        public void onCommit()
        {
            System.out.println("LegalLock: onCommit called");

            //
            //    Create a new object that does not have an extent.
            //    This is legal because no write locks are taken.
            //
            new NoExtent();
        }
    }

    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String[] args)
    {
        new Transaction("Locking")
        {
            @Override
            protected void run() throws Rollback
            {
                new LegalLock();
                new IllegalLock();
           }
        }.execute();
    }
}