/*
* 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) 2001,
*
* Arjuna Solutions Limited,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: StatusChecker.java 2342 2006-03-30 13:06:17Z $
*
*/
package com.arjuna.ats.internal.jts.recovery.contact;
import java.util.Hashtable;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.COMM_FAILURE;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.OBJECT_NOT_EXIST;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.TRANSIENT;
import org.omg.CosTransactions.Inactive;
import org.omg.CosTransactions.NoTransaction;
import org.omg.CosTransactions.Status;
import org.omg.CosTransactions.otid_t;
import com.arjuna.ArjunaOTS.ArjunaFactory;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.jts.OTSManager;
import com.arjuna.ats.jts.logging.jtsLogger;
import com.arjuna.ats.jts.utils.Utility;
import com.arjuna.orbportability.ORBInfo;
import com.arjuna.orbportability.ORBType;
/**
* Checks the status of a transaction as known to the original process that
* created it - assuming the transaction still exists.
*
*
* (relies on the fact (true for 2.1) that any ArjunaFactory can be used to
* find the status of any transaction.
*
* Singleton class
*/
public class StatusChecker
{
/* the interface of this class is intended to allow for possible
* future extension to do statuschecking by other (non-corba) means
*
* On the other hand, there is some nasty interlinking between bits of this
* class - some redesign into separate classes would make things easier to
* follow.
*/
// lookup the relevant factory according to the uid of the FactoryContactItem
private Hashtable _itemFromUid;
/**
* A static singleton to do the work for the static methods
*/
private static final StatusChecker _checker = new StatusChecker();
/**
* get the status in the original process, given the uid of the contact
* item (which is the uid of the process)
*/
public static Status get_status(Uid transactionUid, Uid itemUid) throws Inactive
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.get_status(" + transactionUid + ", " + itemUid + ")");
}
return _checker.checkOriginalStatus(transactionUid, itemUid, true);
}
/**
* get the current status in the original process, given the uid of the contact
* item (which is the uid of the process). Note that this method is used by the
* GenericRecoveryCoordinator code only.
*/
public static Status get_current_status(Uid transactionUid, Uid itemUid) throws Inactive
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.get_current_status(" + transactionUid + ", " + itemUid + ")");
}
return _checker.checkOriginalStatus(transactionUid, itemUid, false);
}
/**
* why isn't this private
*/
public StatusChecker()
{
_itemFromUid = new Hashtable();
}
/**
* Check the status of a transaction when the contact item uid is known.
* This method *must* only be called from replay_completion, since it
* relies upon this fact to differentiate between a committed or rolled back
* transaction in the event of finding no intentions list in the object
* store.
*
* @return the status of the transaction as known in the original process.
* @throws Inactive if the original process is no longer active.
*/
public Status checkOriginalStatus (Uid transactionUid, Uid itemUid, boolean checkTheObjectStore)
throws Inactive
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.checkOriginalStatus(" + transactionUid + ", " + itemUid + ", " + checkTheObjectStore + ")");
}
FactoryContactItem item = getItem(itemUid);
if (item != null)
{
return getStatus(transactionUid, item, checkTheObjectStore);
}
else
{
// null item implies long-dead process
throw new Inactive();
}
}
/**
* try to get the status from a factory and convert to our way.
* factory must not be null
* itemUid is the store id as in _itemFromUid
*/
private Status getStatus (Uid transactionUid, FactoryContactItem item, boolean checkTheObjectStore) throws Inactive
{
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus(" + transactionUid + ", " + item + ", " + checkTheObjectStore + ")");
}
ArjunaFactory factory = item.getFactory();
if (factory != null)
{
Status otsStatus = Status.StatusUnknown;
boolean originalDead = false;
try
{
otid_t otid = Utility.uidToOtid(transactionUid);
otsStatus = factory.getCurrentStatus(otid);
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - current status = "+Utility.stringStatus(otsStatus));
}
/*
* If the factory doesn't know about the transaction, then
* check the object store for the intentions list. If not
* present, then the transaction must have rolled back.
* If present, then we don't know what's going on, since the
* factory should still have a reference to the transaction!
*/
if (otsStatus == Status.StatusNoTransaction)
{
otsStatus = factory.getStatus(otid);
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - stored status = "+Utility.stringStatus(otsStatus));
}
switch (otsStatus.value())
{
case Status._StatusNoTransaction:
/*
* A definitive NoTransaction means rolled back because of
* presumed abort protocol.
*/
// return Status.StatusRolledBack;
return otsStatus;
case Status._StatusUnknown:
return otsStatus;
default: {
/*
* We got an answer! This probably means that the
* factory has just finished with the transaction, but
* the state hasn't been removed by the file system yet
* - we don't sync the removal to improve performance.
*/
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_3(transactionUid);
otsStatus = Status.StatusUnknown;
}
break;
}
}
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - Status = "+Utility.stringStatus(otsStatus));
}
item.markAsAlive();
} catch ( NO_IMPLEMENT ex_noimp) {
// the original application has died
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - NO_IMPLEMENT = dead");
}
originalDead = true;
// orbix seems to count unreachable as transient. Over infinite time, all
// addresses are valid
} catch ( TRANSIENT ex_trans) {
if (ORBInfo.getOrbEnumValue() == ORBType.JACORB)
{
// the original application has (probably) died
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - TRANSIENT = dead");
}
originalDead = true;
}
} catch ( COMM_FAILURE ex_comm) {
if (ORBInfo.getOrbEnumValue() == ORBType.JAVAIDL)
{
// the original application has (probably) died
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - COMM_FAILURE = dead");
}
originalDead = true;
}
/*
* Probably the original application has died, but only just - do
* not mark either way.
*/
else if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - COMM_FAILURE = live");
}
} catch ( OBJECT_NOT_EXIST ex_noobj) {
// the original process must have gone away, and another one
// come up in the same place
// (or, just possibly, the original closed the ots)
originalDead = true;
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - OBJECT_NOT_EXIST = dead");
}
} catch ( BAD_PARAM ex_badparam) {
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_9();
// the transactionUid is invalid !
} catch ( NoTransaction ex_notran) {
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_10();
// the transactionUid is invalid !
// no transaction
} catch ( SystemException ex_corba ) {
// why did this happen ?
if (ORBInfo.getOrbEnumValue() == ORBType.JAVAIDL)
{
// the original application has (probably) died
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - COMM_FAILURE = dead");
}
originalDead = true;
}
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_11(ex_corba);
} catch ( Exception ex_other) {
// this really shouldn't happen
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_12(ex_other);
}
if (originalDead)
{
item.markAsDead();
// use Inactive as an indication that the parent process
// has gone
throw new Inactive();
}
else
{
return otsStatus;
}
}
else
{
// factory in item is null - process already dead
if (jtsLogger.logger.isDebugEnabled()) {
jtsLogger.logger.debug("StatusChecker.getStatus("+transactionUid+") - no factory, process previously dead");
}
/*
* In which case we can use the current, in process local factory, to
* look at the object store and get the status from that. At present
* all factories can look at the entire object store on a machine, so
* this will work. If a factory is limited to only a portion of the object
* store then we may need to create an explicit factory that has "global"
* knowledge.
*/
if ( checkTheObjectStore )
{
try
{
Status s = OTSManager.factory().getStatus(transactionUid);
/*
* If the status is committing or rolling back from a dead
* (local) process then we can direct recovery now.
*/
if (s == Status.StatusCommitting)
return Status.StatusCommitted;
else
{
if (s == Status.StatusRollingBack)
return Status.StatusRolledBack;
}
return s;
}
catch (NoTransaction e1)
{
return Status.StatusNoTransaction;
}
catch (SystemException e2)
{
return Status.StatusUnknown;
}
}
else
{
throw new Inactive();
}
}
}
/**
* find the IOR for the ArjunaFactory whose FactoryContactItem was saved with
* this uid. It is possible this Uid was created after the last scan, so if
* it isn't in the hashtable, look for real directly.
*/
private FactoryContactItem getItem (Uid uid)
{
FactoryContactItem theItem = null;
theItem = getKnownItem(uid);
if (theItem == null) {
// not previously known - see if it exists now
theItem = getNewItem(uid);
if (theItem == null) {
// if it's still null, either something has gone wrong
// - how did it get in the recoverycoordkey when the
// factory was unknown
// or it's very old and been fully deleted
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_14(uid);
// treat as long-dead process - return null
}
}
return theItem;
}
private FactoryContactItem getKnownItem(Uid uid)
{
FactoryContactItem theItem = null;
try {
theItem = (FactoryContactItem) _itemFromUid.get(uid);
return theItem;
} catch (ClassCastException ex) {
jtsLogger.i18NLogger.warn_recovery_contact_StatusChecker_15(uid, ex);
return null;
}
}
private FactoryContactItem getNewItem (Uid uid)
{
FactoryContactItem item = FactoryContactItem.recreate(uid);
if (item != null) {
// enter in the uid hashtable
_itemFromUid.put(uid,item);
}
return item;
}
}