All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arjuna.wst11.messaging.engines.CoordinatorEngine Maven / Gradle / Ivy

There is a newer version: 4.17.43.Final
Show newest version
package com.arjuna.wst11.messaging.engines;

import com.arjuna.webservices.SoapFault;
import com.arjuna.webservices.SoapFaultType;
import com.arjuna.webservices.logging.WSTLogger;
import com.arjuna.webservices.util.TransportTimer;
import com.arjuna.webservices11.SoapFault11;
import com.arjuna.webservices11.wsaddr.AddressingHelper;
import org.jboss.ws.api.addressing.MAP;
import com.arjuna.webservices11.wsarj.ArjunaContext;
import com.arjuna.webservices11.wsarj.InstanceIdentifier;
import com.arjuna.webservices11.wsat.CoordinatorInboundEvents;
import com.arjuna.webservices11.wsat.State;
import com.arjuna.webservices11.wsat.AtomicTransactionConstants;
import com.arjuna.webservices11.wsat.client.ParticipantClient;
import com.arjuna.webservices11.wsat.processors.CoordinatorProcessor;
import com.arjuna.wsc11.messaging.MessageId;
import org.oasis_open.docs.ws_tx.wsat._2006._06.Notification;

import javax.xml.namespace.QName;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import java.util.TimerTask;

/**
 * The coordinator state engine
 * @author kevin
 */
public class CoordinatorEngine implements CoordinatorInboundEvents
{
    /**
     * Flag indicating this is a coordinator for a durable participant.
     */
    private final boolean durable ;
    /**
     * The coordinator id.
     */
    private final String id ;
    /**
     * The instance identifier.
     */
    private final InstanceIdentifier instanceIdentifier ;
    /**
     * The participant endpoint reference.
     */
    private final W3CEndpointReference participant ;
    /**
     * The current state.
     */
    private State state ;
    /**
     * The flag indicating that this coordinator has been recovered from the log.
     */
    private boolean recovered ;
    /**
     * The flag indicating a read only response.
     */
    private boolean readOnly ;
    /**
     * The associated timer task or null.
     */
    private TimerTask timerTask ;

    /**
     * Construct the initial engine for the coordinator.
     * @param id The coordinator id.
     * @param durable true if the participant is durable, false if volatile.
     * @param participant The participant endpoint reference.
     */
    public CoordinatorEngine(final String id, final boolean durable, final W3CEndpointReference participant)
    {
        this(id, durable, participant, false, State.STATE_ACTIVE) ;
    }

    /**
     * Construct the engine for the coordinator in a specified state.
     * @param id The coordinator id.
     * @param durable true if the participant is durable, false if volatile.
     * @param participant The participant endpoint reference.
     * @param state The initial state.
     */
    public CoordinatorEngine(final String id, final boolean durable, final W3CEndpointReference participant, boolean recovered, final State state)
    {
        this.id = id ;
        this.instanceIdentifier = new InstanceIdentifier(id) ;
        this.durable = durable ;
        this.participant = participant ;
        this.state = state ;
        this.recovered = recovered;

        // unrecovered participants are always activated
        // we only need to reactivate recovered participants which were successfully prepared
        // any others will only have been saved because of a heuristic outcome e.g. a comms
        // timeout at prepare will write a heuristic record for an ABORTED TX including a
        // participant in state PREPARING. we can safely drop it since we implement presumed abort.

        if (!recovered || state == State.STATE_PREPARED_SUCCESS) {
            CoordinatorProcessor.getProcessor().activateCoordinator(this, id) ;
        }
    }

    /**
     * Handle the aborted event.
     * @param aborted The aborted notification.
     * @param map The addressing context.
     * @param arjunaContext The arjuna context.
     *
     * None -> None (ignore)
     * Active -> Aborting (forget)
     * Preparing -> Aborting (forget)
     * PreparedSuccess -> PreparedSuccess (invalid state)
     * Committing -> Committing (invalid state)
     * Aborting -> Aborting (forget)
     */
    public synchronized void aborted(final Notification aborted, final MAP map, final ArjunaContext arjunaContext)
    {
        final State current = state ;
        if (current == State.STATE_ACTIVE)
        {
            changeState(State.STATE_ABORTING) ;
        }
        else if ((current == State.STATE_PREPARING) || (current == State.STATE_ABORTING))
        {
            forget() ;
        }
    }

    /**
     * Handle the committed event.
     * @param committed The committed notification.
     * @param map The addressing context.
     * @param arjunaContext The arjuna context.
     *
     * None -> None (ignore)
     * Active -> Aborting (invalid state)
     * Preparing -> Aborting (invalid state)
     * PreparedSuccess -> PreparedSuccess (invalid state)
     * Committing -> Committing (forget)
     * Aborting -> Aborting (invalid state)
     */
    public synchronized void committed(final Notification committed, final MAP map, final ArjunaContext arjunaContext)
    {
        final State current = state ;
        if (current == State.STATE_ACTIVE)
        {
            changeState(State.STATE_ABORTING) ;
        }
        else if ((current == State.STATE_PREPARING) || (current == State.STATE_COMMITTING))
        {
            forget() ;
        }
    }

    /**
     * Handle the prepared event.
     * @param prepared The prepared notification.
     * @param map The addressing context.
     * @param arjunaContext The arjuna context.
     *
     * None -> Durable: (send rollback), Volatile: Invalid state: none
     * Active -> Aborting (invalid state)
     * Preparing -> PreparedSuccess (Record Vote)
     * PreparedSuccess -> PreparedSuccess (ignore)
     * Committing -> Committing (resend Commit)
     * Aborting -> Aborting (resend Rollback and forget)
     */
    public void prepared(final Notification prepared, final MAP map, final ArjunaContext arjunaContext)
    {
        final State current ;
        synchronized(this)
        {
            current = state ;
            if (current == State.STATE_ACTIVE)
            {
                changeState(State.STATE_ABORTING) ;
            }
            else if (current == State.STATE_PREPARING)
            {
                changeState(State.STATE_PREPARED_SUCCESS) ;
            }
        }
        if (current == State.STATE_COMMITTING)
        {
            sendCommit() ;
        }
        else if ((current == State.STATE_ABORTING))
        {
            if (durable) {
                sendRollback();
            } else {
                sendUnknownTransaction(map, arjunaContext) ;
            }
            forget();
        }
        else if ((current == null) && !readOnly)
        {
            if (durable)
            {
                sendRollback() ;
            }
            else
            {
        	sendUnknownTransaction(map, arjunaContext) ;
            }
        }
    }

    /**
     * Handle the readOnly event.
     * @param readOnly The readOnly notification.
     * @param map The addressing context.
     * @param arjunaContext The arjuna context.
     *
     * None -> None (ignore)
     * Active -> Active (forget)
     * Preparing -> Preparing (forget)
     * PreparedSuccess -> PreparedSuccess (invalid state)
     * Committing -> Committing (invalid state)
     * Aborting -> Aborting (forget)
     */
    public synchronized void readOnly(final Notification readOnly, final MAP map, final ArjunaContext arjunaContext)
    {
        final State current = state ;
        if ((current == State.STATE_ACTIVE) || (current == State.STATE_PREPARING) ||
            (current == State.STATE_ABORTING))
        {
            if (current != State.STATE_ABORTING)
            {
                this.readOnly = true ;
            }
            forget() ;
        }
    }

    /**
     * Handle the soap fault event.
     * @param soapFault The soap fault.
     * @param map The addressing context.
     * @param arjunaContext The arjuna context.
     *
     */
    public void soapFault(final SoapFault soapFault, final MAP map, final ArjunaContext arjunaContext)
    {
        if (WSTLogger.logger.isTraceEnabled())
        {
            final InstanceIdentifier instanceIdentifier = arjunaContext.getInstanceIdentifier() ;
            final SoapFaultType soapFaultType = soapFault.getSoapFaultType() ;
            final QName subCode = soapFault.getSubcode() ;
            WSTLogger.logger.tracev("Unexpected SOAP fault for coordinator {0}: {1} {2}", new Object[] {instanceIdentifier, soapFaultType, subCode}) ;
        }
    }

    /**
     * Handle the prepare event.
     *
     * None -> None (invalid state)
     * Active -> Preparing (send prepare)
     * Preparing -> Preparing (resend prepare)
     * PreparedSuccess -> PreparedSuccess (do nothing)
     * Committing -> Committing (invalid state)
     * Aborting -> Aborting (invalid state)
     */
    public State prepare()
    {
        final State current ;
        synchronized(this)
        {
            current = state ;
            if (current == State.STATE_ACTIVE)
            {
                changeState(State.STATE_PREPARING) ;
            }
        }

        if ((current == State.STATE_ACTIVE) || (current == State.STATE_PREPARING))
        {
            sendPrepare() ;
        }

        waitForState(State.STATE_PREPARING, TransportTimer.getTransportTimeout()) ;

        synchronized(this)
        {
            if (state != State.STATE_PREPARING)
            {
                return state ;
            }

            if (timerTask != null)
            {
        	timerTask.cancel() ;

                timerTask = null;
            }

            // ok, we leave the participant stub active because the coordinator will attempt
            // to roll it back when it notices that this has failed

            return state ;
        }
    }

    /**
     * Handle the commit event.
     *
     * None -> None (invalid state)
     * Active -> Active (invalid state)
     * Preparing -> Preparing (invalid state)
     * PreparedSuccess -> Committing (send commit)
     * Committing -> Committing (resend commit)
     * Aborting -> Aborting (invalid state)
     */
    public State commit()
    {
        final State current ;
        synchronized(this)
        {
            current = state ;
            if (current == State.STATE_PREPARED_SUCCESS)
            {
                changeState(State.STATE_COMMITTING) ;
            }
        }

        if ((current == State.STATE_PREPARED_SUCCESS) || (current == State.STATE_COMMITTING))
        {
            sendCommit() ;
        }

        waitForState(State.STATE_COMMITTING, TransportTimer.getTransportTimeout()) ;

        synchronized(this)
        {
            if (state != State.STATE_COMMITTING)
            {
                // if this is a recovered participant then forget will not have
                // deactivated the entry so that this (recovery) thread can
                // detect it and update its log entry. so we need to deactivate
                // the entry here.

                if (recovered) {
                    CoordinatorProcessor.getProcessor().deactivateCoordinator(this) ;
                }

                return state ;
            }

            // the participant is still uncommitted so it will be rewritten to the log.
            // it remains activated in case a committed message comes in between now and
            // the next scan. the recovery code will detect this active participant when
            // rescanning the log and use it instead of recreating a new one.
            // we need to mark this one as recovered so it does not get deleted until
            // the next scan

            recovered = true;

            return State.STATE_COMMITTING;
        }
    }

    /**
     * Handle the rollback event.
     *
     * None -> None (invalid state)
     * Active -> Aborting (send rollback)
     * Preparing -> Aborting (send rollback)
     * PreparedSuccess -> Aborting (send rollback)
     * Committing -> Committing (invalid state)
     * Aborting -> Aborting (do nothing)
     */
    public State rollback()
    {
        final State current ;
        synchronized(this)
        {
            current = state ;
            if ((current == State.STATE_ACTIVE) || (current == State.STATE_PREPARING) ||
                (current == State.STATE_PREPARED_SUCCESS))
            {
                changeState(State.STATE_ABORTING) ;
            }
        }

        if ((current == State.STATE_ACTIVE) || (current == State.STATE_PREPARING) ||
            (current == State.STATE_PREPARED_SUCCESS))
        {
            sendRollback() ;
        }
        else if (current == State.STATE_ABORTING)
        {
            forget() ;
        }

        waitForState(State.STATE_ABORTING, TransportTimer.getTransportTimeout()) ;

        synchronized(this)
        {
            if (state != State.STATE_ABORTING)
            {
                // means state must be null and the participant has already been deactivated

                return state ;
            }

            // the participant has not confirmed that it is aborted so it will be written to the
            // log in the transaction's heuristic list. it needs to be deactivated here
            // so that subsequent ABORTED messages are handled correctly, either by sending
            // an UnknownTransaction fault or a rollback depending upon whether it is
            // volatile or durable, respectively

            forget();

            return State.STATE_ABORTING;
        }
    }

    /**
     * Handle the comms timeout event.
     *
     * Preparing -> Preparing (resend Prepare)
     * Committing -> Committing (resend Commit)
     */
    private void commsTimeout(TimerTask caller)
    {
        final State current ;
        synchronized(this)
        {
            if (timerTask != caller) {
                // the timer was cancelled but it went off before it could be cancelled
                
                return;
            }

            current = state ;
        }

        if (current == State.STATE_PREPARING)
        {
            sendPrepare() ;
        }
        else if (current == State.STATE_COMMITTING)
        {
            sendCommit() ;
        }
    }

    /**
     * Get the coordinator id.
     * @return The coordinator id.
     */
    public String getId()
    {
        return id ;
    }

    /**
     * Get the participant endpoint reference
     * @return The participant endpoint reference
     */
    public W3CEndpointReference getParticipant()
    {
        return participant ;
    }

    /**
     * Is the participant durable?
     * @return true if durable, false otherwise.
     */
    public boolean isDurable()
    {
        return durable ;
    }

    /**
     * Is the participant recovered?
     * @return true if recovered, false otherwise.
     */
    public boolean isRecovered()
    {
        return recovered ;
    }

    /**
     * Was this a read only response?
     * @return true if a read only response, false otherwise.
     */
    public synchronized boolean isReadOnly()
    {
        return readOnly ;
    }

    /**
     * Retrieve the current state of this participant
     * @return the current state.
     */
    public synchronized State getState()
    {
        return state;
    }

    /**
     * Change the state and notify any listeners.
     * @param state The new state.
     */
    private synchronized void changeState(final State state)
    {
        if (this.state != state)
        {
            this.state = state ;
            notifyAll() ;
        }
    }

    /**
     * Wait for the state to change from the specified state.
     * @param origState The original state.
     * @param delay The maximum time to wait for (in milliseconds).
     * @return The current state.
     */
    private State waitForState(final State origState, final long delay)
    {
        final long end = System.currentTimeMillis() + delay ;
        synchronized(this)
        {
            while(state == origState)
            {
                final long remaining = end - System.currentTimeMillis() ;
                if (remaining <= 0)
                {
                    break ;
                }
                try
                {
                    wait(remaining) ;
                }
                catch (final InterruptedException ie) {} // ignore
            }
            return state ;
        }
    }

    /**
     * Forget the current coordinator.
     */
    private void forget()
    {
        // first, change state to null to indicate that the participant has completed.

        changeState(null) ;

        // participants which have not been recovered from the log can be deactivated now.

        // participants which have been recovered are left for the recovery thread to deactivate.
        // this is because the recovery thread may have timed out waiting for a response to
        // the commit message and gone on to complete its scan and suspend. the next scan
        // will detect this activated participant and note that it has completed. if a crash
        // happens in between the recovery thread can safely recreate and reactivate the
        // participant and resend the commit since the commit/committed exchange is idempotent.

        if (!recovered) {
            CoordinatorProcessor.getProcessor().deactivateCoordinator(this) ;
        }
    }

    /**
     * Send the prepare message.
     *
     */
    private void sendPrepare()
    {
        TimerTask newTimerTask = createTimerTask();
        synchronized (this) {
            // cancel any existing timer task

            if (timerTask != null) {
                timerTask.cancel();
            }

            // install the new timer task. this signals our intention to post a prepare which may need
            // rescheduling later but allows us to drop the lock on this while we are in the comms layer.
            // our intention can be revised by another thread by reassigning the field to a new task
            // or null

            timerTask = newTimerTask;
        }

        // ok now try the prepare

        try
        {
            ParticipantClient.getClient().sendPrepare(participant, createContext(), instanceIdentifier) ;
        }
        catch (final Throwable th)
        {
            if (WSTLogger.logger.isTraceEnabled())
            {
                WSTLogger.logger.tracev("Unexpecting exception while sending Prepare", th) ;
            }
        }

        // reobtain the lock before deciding whether to schedule the timer

        synchronized (this) {
            if (timerTask.equals(newTimerTask)) {
                // the timer task has not been cancelled so schedule it if appropriate
                if (state == State.STATE_PREPARING) {
                    scheduleTimer(newTimerTask);
                } else {
                    // no need to schedule it so get rid of it
                    timerTask = null;
                }
            }
        }
    }

    /**
     * Send the commit message.
     *
     */
    private void sendCommit()
    {
        TimerTask newTimerTask = createTimerTask();
        synchronized (this) {
            // cancel any existing timer task

            if (timerTask != null) {
                timerTask.cancel();
            }

            // install the new timer task. this signals our intention to post a commit which may need
            // rescheduling later but allows us to drop the lock on this while we are in the comms layer.
            // our intention can be revised by another thread by reassigning the field to a new task
            // or null

            timerTask = newTimerTask;
        }

        // ok now try the commit

        try
        {
            ParticipantClient.getClient().sendCommit(participant, createContext(), instanceIdentifier) ;
        }
        catch (final Throwable th)
        {
            if (WSTLogger.logger.isTraceEnabled())
            {
                WSTLogger.logger.tracev("Unexpecting exception while sending Commit", th) ;
            }
        }

        // reobtain the lock before deciding whether to schedule the timer

        synchronized (this) {
            if (timerTask.equals(newTimerTask)) {
                // the timer task has not been cancelled so schedule it if appropriate
                if (state == State.STATE_COMMITTING) {
                    scheduleTimer(newTimerTask);
                } else {
                    // no need to schedule it so get rid of it
                    timerTask = null;
                }
            }
        }
    }

    /**
     * Send the rollback message.
     *
     */
    private void sendRollback()
    {
        try
        {
            ParticipantClient.getClient().sendRollback(participant, createContext(), instanceIdentifier) ;
        }
        catch (final Throwable th)
        {
            if (WSTLogger.logger.isTraceEnabled())
            {
                WSTLogger.logger.tracev("Unexpecting exception while sending Rollback", th) ;
            }
        }
    }

    /**
     * Send the UnknownTransaction message.
     *
     */
    private void sendUnknownTransaction(final MAP map, final ArjunaContext arjunaContext)
    {
        try
        {
            final MAP faultMAP = AddressingHelper.createFaultContext(map, MessageId.getMessageId()) ;
            final InstanceIdentifier instanceIdentifier = arjunaContext.getInstanceIdentifier() ;

            final String message = WSTLogger.i18NLogger.get_wst11_messaging_engines_CoordinatorEngine_sendUnknownTransaction_1();
            final SoapFault soapFault = new SoapFault11(SoapFaultType.FAULT_SENDER, AtomicTransactionConstants.WSAT_ERROR_CODE_UNKNOWN_TRANSACTION_QNAME, message) ;
            ParticipantClient.getClient().sendSoapFault(faultMAP, soapFault, instanceIdentifier) ;
        }
        catch (final Throwable th)
        {
            WSTLogger.i18NLogger.warn_wst11_messaging_engines_CoordinatorEngine_sendUnknownTransaction_2(id, th);
        }
    }

    /**
     * create a timer task to handle a comms timeout
     *
     * @return the timer task
     */
    private TimerTask createTimerTask()
    {
        return new TimerTask() {
            public void run() {
                commsTimeout(this) ;
            }
        } ;
    }

    /**
     * schedule a timer task to handle a commms timeout
     * @param timerTask the timer task to be scheduled
     */

    private void scheduleTimer(TimerTask timerTask)
    {
        TransportTimer.getTimer().schedule(timerTask, TransportTimer.getTransportPeriod()) ;
    }

    /**
     * Initiate the timer.
     */
    private synchronized void initiateTimer()
    {
        if (timerTask != null)
        {
            timerTask.cancel() ;
        }
        if ((state == State.STATE_PREPARING) || (state == State.STATE_COMMITTING))
        {
            timerTask = new TimerTask() {
                public void run() {
                    commsTimeout(this) ;
                }
            } ;
            TransportTimer.getTimer().schedule(timerTask, TransportTimer.getTransportPeriod()) ;
        }
        else
        {
            timerTask = null ;
        }
    }

    /**
     * Create a context for the outgoing message.
     * @return The addressing context.
     */
    private MAP createContext()
    {
        final String messageId = MessageId.getMessageId() ;

        return AddressingHelper.createNotificationContext(messageId) ;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy