/* * 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) 1998, 1999, 2000, 2001, * * Arjuna Solutions Limited, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: ResourceRecord.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.jts.resources; /* * * OTS Resource Record Class Implementation * */ import java.io.IOException; import java.io.PrintWriter; import org.omg.CORBA.BAD_PARAM; import org.omg.CORBA.CompletionStatus; import org.omg.CORBA.INVALID_TRANSACTION; import org.omg.CORBA.OBJECT_NOT_EXIST; import org.omg.CORBA.SystemException; import org.omg.CORBA.TRANSACTION_ROLLEDBACK; import org.omg.CORBA.UNKNOWN; import org.omg.CosTransactions.Coordinator; import org.omg.CosTransactions.HeuristicCommit; import org.omg.CosTransactions.HeuristicHazard; import org.omg.CosTransactions.HeuristicMixed; import org.omg.CosTransactions.HeuristicRollback; import org.omg.CosTransactions.NotPrepared; import org.omg.CosTransactions.Resource; import org.omg.CosTransactions.SubtransactionAwareResource; import org.omg.CosTransactions.Vote; import com.arjuna.ats.arjuna.ObjectType; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.coordinator.AbstractRecord; import com.arjuna.ats.arjuna.coordinator.RecordType; import com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.arjuna.state.OutputObjectState; import com.arjuna.ats.internal.arjuna.common.UidHelper; import com.arjuna.ats.internal.jts.ORBManager; import com.arjuna.ats.internal.jts.orbspecific.coordinator.ArjunaTransactionImple; import com.arjuna.ats.jts.logging.jtsLogger; /** * Arjuna abstract record to handle CORBA (SubtransactionAware)Resource * interface. * * The OTS handling of Resources is bizarre (by Arjuna standards) and confusing * Our current understanding is: * * Resources registered using 'register_resource' ONLY take * part in top-level events * * SubtransactionAwareResources registered using 'register_subtran_aware' ONLY * take part in commit/abort of the action in which they are registered - i.e. * they DO NOT propagate automatically - the registering object must do the * propagation itself using the parent arg in the 'commit_subtransaction' * operation. * * If a SubtransactionAwareResource is registered with 'register_resource' then * it will be propagated to the parent when the action commit. Otherwise it is * only registered with the current transaction. * * Subtransactions do not have a 'prepare' phase which can thus lead to * inconsistency. If they underwent the full 2-phase protocol (as they do in * Arjuna), then all nested participants will have to repond successfully * to prepare before they can be told to commit. The way the OTS mandates the * protocol, we could tell some to commit before being told by another resource * that it cannot commit! We then have to go through the resources and tell * them to abort! May cause heuristic decisions! * * The only problem is if a resource which does not propagate causes a nested * action to fail. * * SubtransactionAwareResources registered using 'register_resource' do appear * to propagate. * * UGH! Braindead! * * @author Mark Little (mark@arjuna.com) * @version $Id: ResourceRecord.java 2342 2006-03-30 13:06:17Z $ * @since JTS 1.0. */ public class ResourceRecord extends com.arjuna.ats.arjuna.coordinator.AbstractRecord { /** * Constructor * * @param propagate tells us whether to propagate the resource at nested * commit or not. * @param theResource is the proxy that allows us to call out to the * object. * @param myParent is the proxy for the parent coordinator needed in * commit_subtransaction. */ public ResourceRecord (boolean propagate, Resource theResource, Coordinator myParent, Uid recCoordUid, ArjunaTransactionImple current) { super(new Uid(), null, ObjectType.ANDPERSISTENT); _parentCoordHandle = myParent; _resourceHandle = theResource; _stringifiedResourceHandle = null; _recCoordUid = (recCoordUid != null) ? (new Uid(recCoordUid)) : Uid.nullUid(); _propagateRecord = propagate; _committed = false; _rolledback = false; } public final Resource resourceHandle () { /* * After recovery we may have not been able to recreate the * _resourceHandle due to the fact that the Resource itself * may not be alive resulting in a failure to narrow the * reference returned from string_to_object. In such cases we * cache the stringied reference and retry the narrow when we * need to use the _resourceHandle as at this point the * Resource may have recovered. */ if ( (_resourceHandle == null) && (_stringifiedResourceHandle != null) ) { try { org.omg.CORBA.ORB theOrb = ORBManager.getORB().orb(); if (theOrb == null) throw new UNKNOWN(); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord: About to string_to_object on "+_stringifiedResourceHandle); } org.omg.CORBA.Object optr = theOrb.string_to_object(_stringifiedResourceHandle); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord: Successfully stringed to object, next try to narrow"); } theOrb = null; _resourceHandle = org.omg.CosTransactions.ResourceHelper.narrow(optr); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord: Successfully narrowed"); } if (_resourceHandle == null) throw new BAD_PARAM(); else { optr = null; } } catch (SystemException e) { // Failed to narrow to a Resource if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord: Failed to narrow to Resource"); } } } return _resourceHandle; } public boolean propagateOnCommit () { return _propagateRecord; } public int typeIs () { return RecordType.OTS_RECORD; } public Object value () { return _resourceHandle; } public void setValue (Object o) { jtsLogger.i18NLogger.warn_resources_rrillegalvalue("ResourceRecord.set_value"); } /** * General nesting rules: * * Only SubtransactionAware resources get registered with nested actions. * The ResourceRecord creator is assumed to ensure that plain Resources * are only registered with the appropriate top level action. * * That said the _propagateRecord flag ensures that resources registered * via register_subtran only take part in the action they where registered * in after which they are dropped */ public int nestedAbort () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord::nestedAbort() for "+order()); } /* * We shouldn't need to check committed since aborted nested actions * will drop these resources. */ SubtransactionAwareResource staResource = null; int o = TwoPhaseOutcome.FINISH_ERROR; try { /* * Must be an staResource to get here. */ staResource = org.omg.CosTransactions.SubtransactionAwareResourceHelper.narrow(resourceHandle()); if (staResource == null) throw new BAD_PARAM(0, CompletionStatus.COMPLETED_NO); } catch (Exception exEnv) { // not a sub tran resource, so ignore; o = TwoPhaseOutcome.FINISH_OK; } if (staResource != null) { try { staResource.rollback_subtransaction(); o = TwoPhaseOutcome.FINISH_OK; } catch (Exception e) { o = TwoPhaseOutcome.FINISH_ERROR; } staResource = null; } /* * Now release the parent as it is about to be destroyed * anyway. * * The parent may have already been released if abort is * being called because commit failed. */ _parentCoordHandle = null; return o; } /** * If this resource handles nesting this was done in prepare * or it should be ignored. In either case returning FINISH_OK * suffices. */ public int nestedCommit () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord::nestedCommit() for "+order()); } return TwoPhaseOutcome.FINISH_OK; } /** * OTS does not use nested prepare at all! * However, to make nested commit/abort clean we do commit_subtransaction * here. * Note that we only get a prepare from Arjuna if the action is to be * committed so this is safe in Arjuna terms. */ public int nestedPrepare () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord::nestedPrepare() for "+order()); } int o = TwoPhaseOutcome.ONE_PHASE_ERROR; SubtransactionAwareResource staResource = null; try { if (_committed) return TwoPhaseOutcome.PREPARE_OK; else _committed = true; staResource = org.omg.CosTransactions.SubtransactionAwareResourceHelper.narrow(resourceHandle()); if (staResource == null) throw new BAD_PARAM(0, CompletionStatus.COMPLETED_NO); } catch (Exception e) { /* * Not subtran aware resource, so return PREPARE_OK. * Resource will get invocations at top-level only. */ o = TwoPhaseOutcome.PREPARE_OK; } if (staResource != null) { try { staResource.commit_subtransaction(_parentCoordHandle); o = TwoPhaseOutcome.PREPARE_OK; staResource = null; } catch (Exception e) { o = TwoPhaseOutcome.ONE_PHASE_ERROR; } } /* * Now release the parent as it is about to be destroyed * anyway. */ _parentCoordHandle = null; return o; } public int topLevelAbort () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord::topLevelAbort() for "+order()); } try { if (resourceHandle() != null) { _resourceHandle.rollback(); } else return TwoPhaseOutcome.FINISH_ERROR; } catch (HeuristicCommit exEnv) { if (_rolledback) return TwoPhaseOutcome.HEURISTIC_HAZARD; // participant lied in prepare! else return TwoPhaseOutcome.HEURISTIC_COMMIT; } catch (HeuristicMixed ex1) { return TwoPhaseOutcome.HEURISTIC_MIXED; } catch (HeuristicHazard ex2) { return TwoPhaseOutcome.HEURISTIC_HAZARD; } catch (OBJECT_NOT_EXIST one) { /* * If the resource voted to roll back in prepare then it can legally * be garbage collected at that point. Hence, it may be that we get * a failure during rollback if we try to call it. If it didn't vote * to roll back then some other error has happened. */ if (_rolledback) return TwoPhaseOutcome.FINISH_OK; else return TwoPhaseOutcome.HEURISTIC_HAZARD; } catch (SystemException ex3) { jtsLogger.i18NLogger.warn_resources_rrcaught("ResourceRecord.topLevelAbort", ex3); return TwoPhaseOutcome.FINISH_ERROR; } return TwoPhaseOutcome.FINISH_OK; } public int topLevelCommit () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord::topLevelCommit() for "+order()); } try { if (resourceHandle() != null) { _resourceHandle.commit(); } else return TwoPhaseOutcome.FINISH_ERROR; } catch (NotPrepared ex1) { return TwoPhaseOutcome.NOT_PREPARED; } catch (HeuristicRollback ex2) { return TwoPhaseOutcome.HEURISTIC_ROLLBACK; } catch (HeuristicMixed ex3) { return TwoPhaseOutcome.HEURISTIC_MIXED; } catch (HeuristicHazard ex4) { return TwoPhaseOutcome.HEURISTIC_HAZARD; } catch (SystemException ex5) { jtsLogger.i18NLogger.warn_resources_rrcaught("ResourceRecord commit -", ex5); return TwoPhaseOutcome.FINISH_ERROR; } return TwoPhaseOutcome.FINISH_OK; } public int topLevelPrepare () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord::topLevelPrepare() for "+order()); } try { if (resourceHandle() != null) { switch (_resourceHandle.prepare().value()) { case Vote._VoteCommit: return TwoPhaseOutcome.PREPARE_OK; case Vote._VoteRollback: _rolledback = true; return TwoPhaseOutcome.PREPARE_NOTOK; case Vote._VoteReadOnly: return TwoPhaseOutcome.PREPARE_READONLY; } } else return TwoPhaseOutcome.PREPARE_NOTOK; } catch (HeuristicMixed ex) { return TwoPhaseOutcome.HEURISTIC_MIXED; } catch (HeuristicHazard ex) { return TwoPhaseOutcome.HEURISTIC_HAZARD; } catch (Exception e) { return TwoPhaseOutcome.PREPARE_NOTOK; } return TwoPhaseOutcome.PREPARE_NOTOK; } public int nestedOnePhaseCommit () { int res = nestedPrepare(); switch (res) { case TwoPhaseOutcome.PREPARE_OK: return nestedCommit(); case TwoPhaseOutcome.PREPARE_READONLY: return TwoPhaseOutcome.FINISH_OK; default: return TwoPhaseOutcome.FINISH_ERROR; } } public int topLevelOnePhaseCommit () { try { if (resourceHandle() != null) _resourceHandle.commit_one_phase(); } catch (HeuristicHazard e1) { return TwoPhaseOutcome.HEURISTIC_HAZARD; } catch (TRANSACTION_ROLLEDBACK e4) { return TwoPhaseOutcome.ONE_PHASE_ERROR; } catch (INVALID_TRANSACTION e5) { return TwoPhaseOutcome.ONE_PHASE_ERROR; } catch (Exception e5) { /* * Unknown error - better assume heuristic! */ return TwoPhaseOutcome.HEURISTIC_HAZARD; } return TwoPhaseOutcome.FINISH_OK; } public boolean forgetHeuristic () { try { if (resourceHandle() != null) { _resourceHandle.forget(); return true; } else { jtsLogger.i18NLogger.warn_resources_rrinvalid("ResourceRecord.forgetHeuristic"); } } catch (SystemException e) { jtsLogger.i18NLogger.warn_resources_rrcaught("ResourceRecord.forgetHeuristic", e); } return false; } /* public static AbstractRecord create () { return new ResourceRecord(); } */ public static void remove (AbstractRecord toDelete) { //toDelete = null; } public void print (PrintWriter strm) { super.print(strm); strm.print("Resource Record"); strm.print(_resourceHandle+"\t"+_parentCoordHandle+"\t"+_propagateRecord); } /** * restore_state and save_state for ResourceRecords doesn't generally * apply due to object pointers. However, we need to save something so we * can recover failed transactions. So, rather than insist that all * Resources derive from a class which we can guarantee will give us some * unique id, we simply rely on string_to_object and object_to_string to * be meaningful. */ public boolean restore_state (InputObjectState os, int t) { int isString = 0; boolean result = super.restore_state(os, t); if (!result) return false; try { isString = os.unpackInt(); /* * Do we need to restore the parent coordinator handle? */ _parentCoordHandle = null; if (isString == 1) { _stringifiedResourceHandle = os.unpackString(); /* * Could call resourceHandle() here to restore the * _resourceHandle reference but no loss in doing it * lazily. */ // Unpack recovery coordinator Uid _recCoordUid = UidHelper.unpackFrom(os); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord.restore_state: unpacked rec co with uid="+_recCoordUid); } } } catch (IOException e) { result = false; } return result; } public boolean save_state (OutputObjectState os, int t) { boolean result = super.save_state(os, t); if (!result) return false; try { /* * Do we need to save the parent coordinator handle? */ /* * If we have a _resourceHandle then we stringify it and * pack the string. Failing that if we have a cached * stringified version (in _stringifiedResourceHandle) * then we pack that. If we have neither then we're * doomed. */ if ( (resourceHandle() == null) && (_stringifiedResourceHandle == null) ) { os.packInt(-1); } else { os.packInt(1); String stringRef = null; if (_resourceHandle != null) { org.omg.CORBA.ORB theOrb = ORBManager.getORB().orb(); if (theOrb == null) throw new UNKNOWN(); stringRef = theOrb.object_to_string(_resourceHandle); theOrb = null; } else { stringRef = _stringifiedResourceHandle; } if (stringRef != null) { os.packString(stringRef); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord: packed obj ref "+stringRef); } } else { result = false; } stringRef = null; if (result) { // Pack recovery coordinator Uid UidHelper.packInto(_recCoordUid, os); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("Packed rec co uid of "+_recCoordUid); } } } } catch (IOException e) { result = false; } catch (SystemException e) { result = false; } return result; } public String type () { return "/StateManager/AbstractRecord/ResourceRecord"; } public boolean doSave () { return true; } public final Uid getRCUid () { return _recCoordUid; } public void merge (AbstractRecord a) { } public void alter (AbstractRecord a) { } public boolean shouldAdd (AbstractRecord a) { return false; } public boolean shouldAlter (AbstractRecord a) { return false; } public boolean shouldMerge (AbstractRecord a) { return false; } public boolean shouldReplace (AbstractRecord rec) { boolean replace = false; if (rec != null) { if (rec.typeIs() == typeIs()) { ResourceRecord newRec = (ResourceRecord) rec; /* * Check whether the new record corresponds to the same * RecoveryCoordinator as this one. If so replace. * Don't replace if the uids are NIL_UID */ if ( ( _recCoordUid.notEquals(Uid.nullUid()) ) && (_recCoordUid.equals(newRec.getRCUid())) ) { replace = true; } } } if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("ResourceRecord: shouldReplace() = "+replace); } return replace; } /* * Protected constructor used by crash recovery. */ public ResourceRecord (boolean propagate, Resource theResource, Uid recCoordUid) { super(new Uid(), null, ObjectType.ANDPERSISTENT); _parentCoordHandle = null; _resourceHandle = theResource; _stringifiedResourceHandle = null; _recCoordUid = (recCoordUid != null) ? (new Uid(recCoordUid)) : Uid.nullUid(); _propagateRecord = propagate; _committed = false; _rolledback = false; } public ResourceRecord () { super(); _parentCoordHandle = null; _resourceHandle = null; _stringifiedResourceHandle = null; _recCoordUid = new Uid(Uid.nullUid()); _propagateRecord = false; _committed = false; _rolledback = false; } private Coordinator _parentCoordHandle; private Resource _resourceHandle; private String _stringifiedResourceHandle; private Uid _recCoordUid; private boolean _propagateRecord; private boolean _committed; private boolean _rolledback; }