/*
* 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.twophase.arjunacore.subordinate;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.coordinator.*;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.arjuna.state.OutputObjectState;
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.twophase.arjunacore.ATCoordinator;
import java.io.IOException;
import java.util.HashMap;
import java.util.Collection;
/**
* This class represents a specific coordination instance. It is essentially an
* ArjunaCore TwoPhaseCoordinator, which gives us access to two-phase with
* synchronization support but without thread management. This is the
* subordinate coordinator implementation which we use when doing
* interposition.
*
* @author Mark Little (mark.little@arjuna.com)
* @version $Id: SubordinateATCoordinator.java,v 1.1 2005/05/19 12:13:39 nmcl Exp $
* @since 2.0.
*/
public class SubordinateATCoordinator extends ATCoordinator
{
/**
* normal constructor
*/
public SubordinateATCoordinator()
{
super();
activated = true;
isReadonly = false;
}
/**
* bridge wrapper constructor
*/
public SubordinateATCoordinator(String subordinateType)
{
super();
activated = true;
isReadonly = false;
this.subordinateType = subordinateType;
}
/**
* constructor for recovered coordinator
* @param recovery
*/
public SubordinateATCoordinator(Uid recovery)
{
super(recovery);
activated = false;
isReadonly = false;
subordinateType = null;
}
/**
* 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 WrongStateException
* Thrown if the coordinator is in a state the does not allow
* coordination to occur.
* @exception ProtocolViolationException
* Thrown if the protocol is violated in some manner during
* execution.
* @exception 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;
}
public int cancel ()
{
return ActionStatus.INVALID;
}
/**
* this is driven by a volatile participant registered on behalf of the coordinator
*
* @return true if the beforeCompletion succeeds otherwise false.
*/
public boolean prepareVolatile()
{
return super.beforeCompletion();
}
/**
* this is driven by a durable participant registered on behalf of the coordinator and does a
* normal prepare mninus the before completion processing which has already been performed
* @return the result of preparing the transaction
*/
public int prepare ()
{
int status = super.prepare(true);
isReadonly = (status == TwoPhaseOutcome.PREPARE_READONLY);
return status;
}
/**
* this is driven by a volatile participant registered on behalf of the coordinator
*/
public void commitVolatile()
{
if (isReadonly) {
super.afterCompletion(ActionStatus.COMMITTED);
} else {
super.afterCompletion(finalStatus);
}
}
/**
* this is driven by a durable participant registered on behalf of the coordinator and does a
* normal commit minus the after completion processing which will be driven by a volatile
* participant also registerd for this coordinator..
*/
public void commit ()
{
super.phase2Commit(true);
int status;
switch (super.getHeuristicDecision())
{
case TwoPhaseOutcome.PREPARE_OK:
case TwoPhaseOutcome.FINISH_OK:
status = super.status();
break;
case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
status = ActionStatus.H_ROLLBACK;
break;
case TwoPhaseOutcome.HEURISTIC_COMMIT:
status = ActionStatus.H_COMMIT;
break;
case TwoPhaseOutcome.HEURISTIC_MIXED:
status = ActionStatus.H_MIXED;
break;
case TwoPhaseOutcome.HEURISTIC_HAZARD:
default:
status = ActionStatus.H_HAZARD;
break;
}
this.finalStatus = status;
// if we have completed then remove the coordinator from the recovered coordinatros table
if (status != ActionStatus.COMMITTING) {
SubordinateATCoordinator.removeRecoveredCoordinator(this);
}
// run any callback associated with this transaction
runCallback(get_uid().stringForm());
}
/**
* this is driven by a volatile participant registered on behalf of the coordinator
*/
public void rollbackVolatile()
{
if (isReadonly) {
super.afterCompletion(ActionStatus.ABORTED);
} else {
super.afterCompletion(finalStatus);
}
}
/**
* this is driven by a durable participant registered on behalf of the coordinator and does a
* normal commit minus the after completion processing which will be driven by a volatile
* participant also registerd for this coordinator..
*/
public void rollback ()
{
super.phase2Abort(true);
int status;
switch (super.getHeuristicDecision())
{
case TwoPhaseOutcome.PREPARE_OK:
case TwoPhaseOutcome.FINISH_OK:
status = super.status();
break;
case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
status = ActionStatus.H_ROLLBACK;
break;
case TwoPhaseOutcome.HEURISTIC_COMMIT:
status = ActionStatus.H_COMMIT;
break;
case TwoPhaseOutcome.HEURISTIC_MIXED:
status = ActionStatus.H_MIXED;
break;
case TwoPhaseOutcome.HEURISTIC_HAZARD:
default:
status = ActionStatus.H_HAZARD;
break;
}
// iemove the coordinator from the recovered coordinatros table
SubordinateATCoordinator.removeRecoveredCoordinator(this);
// run any callback associated with this transaction
runCallback(get_uid().stringForm());
this.finalStatus = status;
}
/**
* 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()
{
}
/**
* type string used to locate TX log records in the tx object store hierarchy
*/
public final static String TRANSACTION_TYPE = "/StateManager/BasicAction/AtomicAction/TwoPhaseCoordinator/TwoPhase/SubordinateATCoordinator";
public String type ()
{
return TRANSACTION_TYPE;
}
/**
* 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.at.subordinate.participant.";
/**
* return a uid for the volatile participant registered on behalf of this corodinator
*/
public String getVolatile2PhaseId()
{
return PARTICIPANT_PREFIX + get_uid().stringForm() + "_V";
}
/**
* return a uid for the durable participant registered on behalf of this corodinator
*/
public String getDurable2PhaseId()
{
return PARTICIPANT_PREFIX + get_uid().stringForm() + "_D";
}
protected static synchronized void addRecoveredCoordinator(SubordinateATCoordinator coordinator)
{
recoveredCoordinators.put(coordinator.get_uid().stringForm(), coordinator);
}
protected static synchronized void removeRecoveredCoordinator(SubordinateATCoordinator 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 whether the at is 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 SubordinateATCoordinator getRecoveredCoordinator(String coordinatorId)
{
return recoveredCoordinators.get(coordinatorId);
}
public static synchronized SubordinateATCoordinator[] listRecoveredCoordinators()
{
Collection<SubordinateATCoordinator> values = recoveredCoordinators.values();
int length = values.size();
return values.toArray(new SubordinateATCoordinator[length]);
}
/**
* standard AT subordinate tx type for an AT subordinate created below another AT transaction
*/
public static final String SUBORDINATE_TX_TYPE_AT_AT = "org.jboss.jbossts.xts.at.at.subordinate";
public String getSubordinateType()
{
return subordinateType;
}
@Override
public boolean save_state(OutputObjectState os, int ot) {
// also need to save the subordinate type
if (super.save_state(os, ot)) {
try {
os.packString(subordinateType);
return true;
} catch (IOException ioe) {
}
}
return false;
}
@Override
public boolean restore_state(InputObjectState os, int ot) {
// also need to restore the subordinate type
if (super.restore_state(os, ot)) {
try {
subordinateType = os.unpackString();
return true;
} catch (IOException ioe) {
}
}
return false;
}
/**
* this saves the status after the subtransaction commit or rollback so it can be referred to during
* afterCompletion processing.
*/
private int finalStatus = ActionStatus.CREATED;
/**
* flag identifying whether this coordinator is active, set true for normal transactions and false
* for recovered transactions until they are activated
*/
private boolean activated;
/**
* flag identifying whether prepare returned READ_ONLY and hence whether special case handling of
* commitVolatile and rollbackVolatile calls is required
*/
private boolean isReadonly;
/**
* string identifying which type of subordinate transaction this is. the standard subordinate type is
* XTSATRecoveryManager.SUBORDINATE_TX_TYPE_AT_AT which identifies a subordinate of another AT transaction.
* Alternative types can occur as a result of transaction bridging e.g. the AT transaction may be a
* subordinate of an XA transaction. different types of subordinate can be scanned and rolled back
* independently from other subordinate types.
*/
private String subordinateType;
private static final HashMap<String, SubordinateATCoordinator> recoveredCoordinators = new HashMap<String, SubordinateATCoordinator>();
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;
}
}
}