/* * JBoss, Home of Professional Open Source * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. * See the copyritypeght.txt in the distribution for a full listing * of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2005-2006, * @author JBoss Inc. */ /* * Copyright (C) 2002, * * Arjuna Technologies Limited, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: SubordinateCoordinator.java,v 1.1 2005/05/19 12:13:39 nmcl Exp $ */ package com.arjuna.mwlabs.wscf.model.sagas.arjunacore.subordinate; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.coordinator.*; import com.arjuna.mw.wsas.activity.Outcome; import com.arjuna.mw.wsas.completionstatus.CompletionStatus; import com.arjuna.mw.wsas.exceptions.SystemException; import com.arjuna.mw.wsas.exceptions.WrongStateException; import com.arjuna.mw.wsas.exceptions.ProtocolViolationException; import com.arjuna.mwlabs.wscf.model.sagas.arjunacore.BACoordinator; import java.util.HashMap; import java.util.Collection; /** * This class represents a specific coordination instance. It inherits from * ArjunaCore TwoPhaseCoordinator via the BACoordinator. This is the * subordinate coordinator implementation which we use when doing * interposition. * * This implementation registers itself as a coordinator completion participant in its parent * activity. It has to use the coordinator completion protocol because it relies upon dispatch * of a complete request from the parent to drive logging of i) the subordinate transaction state * then ii) the state of its registered proxy participant. These operations must both happen in that * order and only when the parent transaction is preparing to close. Note that this does not * imply that the transaction is closed, merely that it goes through phase 1 of the transaction * termination protocol (PREPARE) leaving a transaction log record on the disk (albeit in state * COMMITTING). It must still be possible to leave phase 2 open for either close (COMMIT) * or compensate (ROLLBACK). * * Why? Well, if the participant logs its state first either at parent complete or, if participant * completion is in use, before parent complete, then a crash risks losing details of the subordinate * transaction -- including potentialy completed participants which may require closing or compensating. * Note that this danger bypasses any question of whether or when subsequently the subordinate transaction * state might be logged -- the participant cannot log its state and guarantee to close or compensate * correctly without having first ensured that it can guarantee to close or compensate subordinate * participants. * * Contrariwise, if the subordinate transaction logs its state first and a crash occurs before the proxy * participant state can be saved then it is still possible during recovery to correlate the presence of * the logged subordinate transaction with the absence of its proxy participant and automatically * compensate the subordinate transaction's completed participants. This will match the action of the parent * since absence of the proxy participant requires it also to compensate. It is not possible to commit * any earlier than parent complete because it is still legitimate for participants to register before * that event. * * So, the implication of this is that when the proxy notifies complete to this coordinator it must * perform the usual complete processing and also drive phase 1 of the commit process. Later, when a * close request is dispatched, it can drive phase 2 of the commit process. * * If there is a failure during phase 1 of the commit process then the subordinate transaction will * not be logged. In this case the proxy participant must notify a fail to the parent transaction and * avoid logging its own state. This is safe because a crash will automatically cause a subsequent * resend of the complete request to return fail because the proxy participant is unknown. * * If phase 1 completes successfully then the proxy participant must log its state and notify completed * to the parent trasaction. If a crash occurs between logging the subordinate transaction state and * logging the proxy state then the absence of the proxy participant can be reconciled during recovery * and the subordinate transaction can be automatically compensated. If a crash occurs between logging the * proxy participant and notifying completed to the parent transaction then the participant will be recreated * during recovery and a subsequent resend of complete can be answered with completed. Note that this means * complete requests for unknown participants must only be answered after the first pass of the BA participant * recovery module has completed. * * @author Mark Little (mark.little@arjuna.com) * @version $Id: SubordinateCoordinator.java,v 1.1 2005/05/19 12:13:39 nmcl Exp $ * @since 2.0. */ public class SubordinateBACoordinator extends BACoordinator { // TODO - modify to use above protocol! /** * normal constructor */ public SubordinateBACoordinator() { super(); activated = true; } /** * constructor for recovered coordinator * @param recovery */ public SubordinateBACoordinator(Uid recovery) { super(recovery); activated = false; } /** * If the application requires and if the coordination protocol supports it, * then this method can be used to execute a coordination protocol on the * currently enlisted participants at any time prior to the termination of * the coordination scope. * * This implementation only supports coordination at the end of the * activity. * * @param cs The completion status to use when determining how to * execute the protocol. * * @exception com.arjuna.mw.wsas.exceptions.WrongStateException * Thrown if the coordinator is in a state the does not allow * coordination to occur. * @exception com.arjuna.mw.wsas.exceptions.ProtocolViolationException * Thrown if the protocol is violated in some manner during * execution. * @exception com.arjuna.mw.wsas.exceptions.SystemException * Thrown if any other error occurs. * * @return The result of executing the protocol, or null. */ public Outcome coordinate (CompletionStatus cs) throws WrongStateException, ProtocolViolationException, SystemException { throw new ProtocolViolationException(); } public int end (boolean reportHeuristics) { return ActionStatus.INVALID; } /** * this is driven by a coordinator-completion participant registered on behalf of the coordinator * and is required to propagate the complete to all registered coordinator-completion participants. */ public void complete () throws WrongStateException, SystemException { // if this goes wrong here then we will throw an exception super.complete(); // now we need to run phase one of commit // TODO -- need to do completion processing here? int outcome = super.prepare(true); // if we have prepared ok (or are read only) then status will be COMMITTING if (outcome == TwoPhaseOutcome.PREPARE_NOTOK) { // phase 1 failed so we need to run phase 2 abort // this will set status to ABORTED phase2Abort(true); } // no need to return anything as the caller can just check the status } /** * this is driven by a coordinator-completion participant registered on behalf of the coordinator * and is required to propagate the close to all registered participants. */ public int close () throws SystemException { int status = status(); int result; if (status == ActionStatus.COMMITTING) { // TODO -- need to do completion processing here? // we already completed and ran phase 1 so do a phase 2 commit phase2Commit(true); result = status(); } else { // we have not yet completed so we can rely upon the parent implementation to do // everything we need result = super.close(); } // if we have completed then remove the coordinator from the recovered coordinators table if (status() != ActionStatus.COMMITTING) { SubordinateBACoordinator.removeRecoveredCoordinator(this); } // run any callback associated with this transaction runCallback(get_uid().stringForm()); return result; } /** * this is driven by a coordinator-completion participant registered on behalf of the coordinator * and is required to propagate the cancel to all registered participants. */ public int cancel () { int status = status(); int result; // TODO -- check if there is a window here where status could change to COMMITTING if (status == ActionStatus.COMMITTING) { phase2Abort(true); result = status(); } else { result = super.cancel(); } SubordinateBACoordinator.removeRecoveredCoordinator(this); // run any callback associated with this transaction runCallback(get_uid().stringForm()); return result; } /** * called by the durable participant during recovery processing * TODO clarify when and why this gets called and what to do about it */ public void unknown() { } /** * called by the durable participant during recovery processing * TODO clarify when and why this gets called and what to do about it */ public void error() { } public String type () { return "/StateManager/BasicAction/AtomicAction/Sagas/SubordinateCoordinator"; } /** * unique string used as prefix for participant ids to ensure they can be identified at recovery */ public static String PARTICIPANT_PREFIX = "org.jboss.jbossts.xts.ba.subordinate.participant."; /** * return a uid for the corodinator completion participant registered on behalf of this coordinator */ public String getCoordinatorCompletionParticipantid() { return PARTICIPANT_PREFIX + get_uid().stringForm() + "_CCP"; } protected static synchronized void addRecoveredCoordinator(SubordinateBACoordinator coordinator) { recoveredCoordinators.put(coordinator.get_uid().stringForm(), coordinator); } protected static synchronized void removeRecoveredCoordinator(SubordinateBACoordinator coordinator) { recoveredCoordinators.remove(coordinator.get_uid().stringForm()); } public static synchronized void addActiveProxy(String id) { activeProxies.put(id, Boolean.TRUE); } public static synchronized void removeActiveProxy(String id) { activeProxies.remove(id); } protected void setActivated() { activated = true; } public boolean isActivated() { return activated; } /** * test whether a transaction has been restored without its proxy participant. this indicates that * we crashed between preparing the suborindate TX and logging the proxy participant. * @return true, if orphaned */ public boolean isOrphaned() { String id = get_uid().stringForm(); if (isActiveProxy(id)) { return false; } // the proxy may have been removed because this tx has been resolved while we were checking if (getRecoveredCoordinator(id) == null) { return false; } // ok we have a tx but no proxy so this is really an orphan return true; } private static synchronized boolean isActiveProxy(String proxyId) { return activeProxies.get(proxyId) == Boolean.TRUE; } public static synchronized SubordinateBACoordinator getRecoveredCoordinator(String coordinatorId) { return recoveredCoordinators.get(coordinatorId); } public static synchronized SubordinateBACoordinator[] listRecoveredCoordinators() { Collection<SubordinateBACoordinator> values = recoveredCoordinators.values(); int length = values.size(); return values.toArray(new SubordinateBACoordinator[length]); } /** * flag identifying whether this coordinator is active, set true for normal transactions and false * for recovered transactions until they are activated */ private boolean activated; private static final HashMap<String, SubordinateBACoordinator> recoveredCoordinators = new HashMap<String, SubordinateBACoordinator>(); private static final HashMap<String, Boolean> activeProxies = new HashMap<String, Boolean>(); /** * we need to remove the association between parent and subordinate context at completion * of commit or rollback -- we use a callback mechanism keyed by transaction id to achieve this */ private static final HashMap<String, SubordinateCallback> callbacks = new HashMap<String, SubordinateCallback>(); /** * class implemented by any code which wishes to register a callabck */ public static abstract class SubordinateCallback { private SubordinateCallback next; // in case multiple callbacks are registered public abstract void run(); } /** * register a callback to be called when a subordinate transaction with a specific key executes * a commit or rollback. the callback will not be called in the case of a crash * @param key * @param callback */ public static void addCallback(String key, SubordinateCallback callback) { SubordinateCallback old = callbacks.put(key, callback); // chian any existign callback so we ensure to call them all callback.next = old; } private void runCallback(String key) { SubordinateCallback callback = callbacks.get(key); while (callback != null) { callback.run(); callback = callback.next; } } }