/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.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) 2000, 2001,
*
* Arjuna Solutions Limited,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: GenericRecoveryCoordinator.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.jts.orbspecific.recovery.recoverycoordinators;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.objectstore.StateStatus;
import com.arjuna.ats.arjuna.objectstore.StoreManager;
import org.omg.CORBA.SystemException;
import org.omg.CosTransactions.Inactive;
import org.omg.CosTransactions.NotPrepared;
import org.omg.CosTransactions.Resource;
import org.omg.CosTransactions.Status;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.internal.jts.orbspecific.coordinator.ArjunaTransactionImple;
import com.arjuna.ats.internal.jts.orbspecific.interposition.coordinator.ServerTransaction;
import com.arjuna.ats.internal.jts.recovery.contact.StatusChecker;
import com.arjuna.ats.internal.jts.recovery.recoverycoordinators.ResourceCompletor;
import com.arjuna.ats.jts.common.jtsPropertyManager;
import com.arjuna.ats.jts.logging.jtsLogger;
/**
* This provides the ORB-independent recovery coordinator
* functionality. The ORB-specific implementations delegate their work
* to this.
* <P>
* @author Dave Ingham(dave.ingham@arjuna.com), Peter Furniss, Mark Little (mark.little@arjuna.com) Malik SAHEB (malik.saheb@arjuna.com
* @version $Id: GenericRecoveryCoordinator.java 2342 2006-03-30 13:06:17Z $
*/
public class GenericRecoveryCoordinator extends org.omg.CosTransactions.RecoveryCoordinatorPOA
{
/**
* Normal constructor. Used both for creating a RecoveryCoordinator in
* the same process as the Coordinator (where this is necessary) and
* when reactivating a RecoveryCoordinator as an implementation instance
* (i.e. <i>not</i> as POA default servant) from
* stringified data in RecoveryManager from data received in a
* RecoveryCoordinator object key.
* <p>Combines the parameters into a {@link RecoveryCoordinatorId}.
*/
public GenericRecoveryCoordinator (Uid RCUid, Uid actionUid,
Uid processUid, boolean isServerTransaction)
{
_id = new RecoveryCoordinatorId(RCUid, actionUid, processUid, isServerTransaction);
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("GenericRecoveryCoordinator "+_id+" constructed");
}
}
/**
* protected constructor used by default servant derived class (with POA orbs).
* When used a default servant, there is only one GenericRecoveryCoordinator
* instance, whose _id field is null.
*
*/
protected GenericRecoveryCoordinator()
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("GenericRecoveryCoordinator() constructing");
}
_id = null;
}
/**
* Implementation of IDL method:
* <p>
* Operation: <b>::CosTransactions::RecoveryCoordinator::replay_completion</b>.
* <pre>
* #pragma prefix "omg.org/CosTransactions/RecoveryCoordinator"
* ::CosTransactions::Status replay_completion(
* in ::CosTransactions::Resource r
* )
* raises(
* ::CosTransactions::NotPrepared
* );
* </pre>
* </p>
* This method is used when the instance is used as a particular implementation
* object (i.e. <i>not</i> as default servant). Delegates to the static
* {@link #replay_completion(RecoveryCoordinatorId, Resource) replay_completion}
* using the {@link RecoveryCoordinatorId} made in the constructor.
*/
public Status replay_completion ( Resource res ) throws SystemException, NotPrepared
{
return GenericRecoveryCoordinator.replay_completion(_id, res);
}
/**
* Respond to a replay_completion request for the RecoveryCoordinator
* identified by parameter id.
*/
protected static Status replay_completion ( RecoveryCoordinatorId id, Resource res ) throws SystemException, NotPrepared
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("GenericRecoveryCoordinator(" + id._RCUid + ").replay_completion("
+ (res != null ? "resource supplied)" : "null resource)"));
}
Status currentStatus = Status.StatusUnknown;
/*
* First check to see if the transaction is active by asking the
* per-process contact.
* If alive, return the status reported by the
* transaction. If not alive then try and recover the
* transaction from the intentions list.
*/
boolean transactionActive = true;
try
{
currentStatus = get_status(id._actionUid, id._originalProcessUid);
}
catch (Inactive e)
{
// original process is dead.
transactionActive = false;
}
if (currentStatus == Status.StatusNoTransaction)
{
/*
* There is no intentions list, so the transaction either
* committed or rolled back. However, this routine is only
* ever called by replay_completion, which means that there
* is a resource (hopefully one which was participating in
* the transaction) that is in doubt as to the
* transaction's outcome. If the transaction had committed,
* then this resource would know of the outcome. Therefore,
* it must have rolled back!
*/
/*
* Unfortunately the last statement is wrong. There is a timing
* issue here: the resource recovery may be doing an upcall while
* the downcall (from coordinator recovery) is going on and
* removing the log. What can then happen is that a resource may
* see a commit folled by a rollback.
*/
currentStatus = Status.StatusRolledBack;
}
/*
* We used to do the following swap, because the assumption was that it would
* only happen if the transaction is still in action and
* the resource hadn't received the second phase call yet.
*
* However, this is not strictly true, there are scenarios where transient
* XA failure status' returned from a resource manager during the
* second phase allows the transaction to transition to committed and this
* swap then prevents the transaction from being completed by bottom-up recovery.
*
* The reason why the assumption about the transaction being in
* action was made, is that resource holds two IORs. One to contact
* the transaction manager and another to contact recovery coordinator.
* As get_status returns a valid state each time because it will check the object
* store and we didn't differentiate between the two calls, we never went down
* the branch of transactionInactive.
*
* With the new approach there is a small window for the StatusCommitted to
* be available during the call to the first IOR if the transaction is active
* when the call is made, but completed by the parent coordinator simultaneously
* with the call. In this case, the resource gets a commit from the parent
* coordinator, plus is told to commit via bottom-up recovery. Although this
* will result in an XAER_NOTA on one of the commit calls (race so could be
* either) this is acceptable in a distributed environment. As the TX was
* prepared, the XAER_NOTA is safe to disregard.
*/
// else if ( currentStatus == Status.StatusCommitted )
// {
// /*
// * If the status returned is StatusCommitted, the only reason a
// * replay_completion request can come in is if the resource on
// * the other end has not received the second phase and hence the
// * transaction is in the process of committing and has not
// * committed.
// */
//
// currentStatus = Status.StatusCommitting;
// }
if (!transactionActive)
{
// original process is dead, so reasonable for us to try to
// recover
/*
* The RecoveredTransactionReplayer is a threaded object
* so we can get the status and return it while the
* replayer does the phase 2 commit in a new thread.
*/
String tranType = ( (id._isServerTransaction) ? ServerTransaction.typeName() : ArjunaTransactionImple.typeName() );
try {
if (id._isServerTransaction && (StoreManager.getRecoveryStore().currentState(id._actionUid, ServerTransaction.typeName() + "/JCA") != StateStatus.OS_UNKNOWN)) {
tranType = tranType + "/JCA";
}
} catch (ObjectStoreException e) {
// Can't read store
}
com.arjuna.ats.internal.jts.recovery.transactions.RecoveredTransactionReplayer replayer = new com.arjuna.ats.internal.jts.recovery.transactions.RecoveredTransactionReplayer(id._actionUid, tranType);
// this will cause the activatation attempt
currentStatus = replayer.getStatus();
/*
* If the transaction has been successfully activated and
* we've been given a new resource then add it to the
* intentions list. This will cause the old resource that
* corresponded to this RecoveryCoordinator to be replaced
* by the given one. This is achieved by the
* AbstractRecord list processing.
*/
if ( (replayer.getRecoveryStatus() != com.arjuna.ats.internal.jts.recovery.transactions.RecoveryStatus.ACTIVATE_FAILED) &&
(res != null) )
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("GenericRecoveryCoordinator - swapping Resource for RC "+id._RCUid);
}
replayer.swapResource(id._RCUid, res);
}
/*
* If we've activated then now replay phase 2. The
* replayer creates a new thread to do this.
*/
if (replayer.getRecoveryStatus() != com.arjuna.ats.internal.jts.recovery.transactions.RecoveryStatus.ACTIVATE_FAILED)
{
replayer.replayPhase2();
}
else
{
replayer.tidyup();
/*
* The transaction didn't activate so we have a
* rollback situation but we can't rollback the
* resource that we have been given through the
* intentions list but we can issue rollback
* directly. This is configurable through the System
* properties.
*/
currentStatus = Status.StatusRolledBack;
}
}
/*
* Try to call rollback on the resource directly. This is not
* strictly necessary, since the resource could do this work itself
* when it gets the StatusRolledBack return value. However, some
* resources may not work this way. If this resource wasn't involved
* in the transaction in the first place then it doesn't matter if
* we invoke it here, since it has no affect on the transaction
* outcome.
*/
if (currentStatus == Status.StatusRolledBack)
{
if (_issueRecoveryRollback)
{
ResourceCompletor resourceCompletor = new ResourceCompletor(res, ResourceCompletor.ROLLBACK);
resourceCompletor.start();
}
}
/*
* If the transaction is Active then throw the NotPrepared
* exception.
*/
if (currentStatus == Status.StatusActive)
throw new NotPrepared();
return currentStatus;
}
/**
* Construct a string, to be used somehow in the objectkey (probably)
* of a RecoveryCoordinator reference. This will be deconstructed in
* the reconstruct() which is passed such a string, to remake the
* necessary RecoveryCoordinator when a replay_completion is received for it.
*
* Put here to make it in the same class as the deconstruction
*/
public static String makeId( Uid rcUid, Uid tranUid,
Uid processUid, boolean isServerTransaction )
{
RecoveryCoordinatorId id = new RecoveryCoordinatorId(rcUid, tranUid, processUid,
isServerTransaction);
return id.makeId();
}
/**
* (re)construct a RecoveryCoordinator instance using the encoded information
* in the parameter. The encoded information was (we hope) created using
* {@link #makeId makeId} and has been passed around as part of the object key
* in the RecoveryCoordinator IOR.
*/
public static GenericRecoveryCoordinator reconstruct(String encodedRCData)
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("GenericRecoveryCoordinator.reconstruct(" + encodedRCData + ")");
}
RecoveryCoordinatorId id = RecoveryCoordinatorId.reconstruct(encodedRCData);
if (id != null) {
return new GenericRecoveryCoordinator(id);
} else {
// already traced
return null;
}
}
private static Status get_status (Uid actionUid, Uid processUid) throws Inactive
{
Status status = Status.StatusUnknown;
boolean transactionActive = true;
try
{
status = StatusChecker.get_current_status(actionUid, processUid);
}
catch (Inactive e)
{
// original process is dead.
transactionActive = false;
}
boolean hasBeenRecovering = false;
for (;;)
{
/*
* An active transaction may actually be in the process of
* recovering. In which case, we cannot add a new resource to the
* intentions list. Hold here if that is the case.
*/
if (transactionActive)
{
Object o = com.arjuna.ats.internal.jts.recovery.transactions.RecoveredTransactionReplayer.isPresent(actionUid);
if (o != null)
{
hasBeenRecovering = true;
synchronized (o)
{
try
{
o.wait();
}
catch (Exception e)
{
}
}
}
else
{
if (hasBeenRecovering)
throw new Inactive();
else
break;
}
}
else
throw new Inactive();
}
return status;
}
private GenericRecoveryCoordinator (RecoveryCoordinatorId id)
{
_id = id;
}
private RecoveryCoordinatorId _id;
private static final boolean _issueRecoveryRollback = jtsPropertyManager.getJTSEnvironmentBean()
.isIssueRecoveryRollback();
}