/* * 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: XAResourceRecord.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.jta.resources.arjunacore; 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.ExceptionDeferrer; import com.arjuna.ats.arjuna.coordinator.RecordType; import com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome; import com.arjuna.ats.arjuna.coordinator.TxControl; import com.arjuna.ats.arjuna.recovery.RecoveryManager; import com.arjuna.ats.arjuna.recovery.RecoveryModule; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.arjuna.state.OutputObjectState; import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule; import com.arjuna.ats.internal.jta.resources.XAResourceErrorHandler; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple; import com.arjuna.ats.internal.jta.xa.TxInfo; import com.arjuna.ats.jta.common.jtaPropertyManager; import com.arjuna.ats.jta.logging.jtaLogger; import com.arjuna.ats.jta.recovery.SerializableXAResourceDeserializer; import com.arjuna.ats.jta.recovery.XARecoveryResource; import com.arjuna.ats.jta.utils.XAHelper; import com.arjuna.ats.jta.xa.RecoverableXAConnection; import com.arjuna.ats.jta.xa.XidImple; import com.arjuna.common.internal.util.ClassloadingUtility; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.jboss.tm.FirstResource; import org.jboss.tm.LastResource; /** * @author Mark Little (mark_little@hp.com) * @version $Id: XAResourceRecord.java 2342 2006-03-30 13:06:17Z $ * @since JTS 1.2.4. */ public class XAResourceRecord extends AbstractRecord implements ExceptionDeferrer { public static final int XACONNECTION = 0; private static final Uid START_XARESOURCE = Uid.minUid(); private static final Uid END_XARESOURCE = Uid.maxUid(); /** * Any XAException that occurs. */ List<Throwable> deferredExceptions; /** * The params represent specific parameters we need to recreate the * connection to the database in the event of a failure. If they're not set * then recovery is out of our control. * * Could also use it to pass other information, such as the readonly flag. */ public XAResourceRecord(TransactionImple tx, XAResource res, Xid xid, Object[] params) { super(new Uid(), null, ObjectType.ANDPERSISTENT); if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.XAResourceRecord ( " + xid + ", " + res + " ), record id=" + order()); } _theXAResource = res; if(_xaResourceRecordWrappingPlugin != null) { _xaResourceRecordWrappingPlugin.transcribeWrapperData(this); } _recoveryObject = null; _tranID = xid; _valid = true; if (params != null) { if (params.length >= XACONNECTION) { if (params[XACONNECTION] instanceof RecoverableXAConnection) _recoveryObject = (RecoverableXAConnection) params[XACONNECTION]; } } _prepared = false; _heuristic = TwoPhaseOutcome.FINISH_OK; _theTransaction = tx; } public final Xid getXid() { return _tranID; } public Uid order() { if (_theXAResource instanceof FirstResource) return START_XARESOURCE; else if (_theXAResource instanceof LastResource) return END_XARESOURCE; return super.order(); } public boolean propagateOnCommit() { return false; // cannot ever be nested! } public int typeIs() { return RecordType.JTA_RECORD; } public Object value() { return _theXAResource; } public void setValue(Object o) { jtaLogger.i18NLogger.warn_resources_arjunacore_setvalue("XAResourceRecord::set_value()"); } public int nestedAbort() { return TwoPhaseOutcome.FINISH_OK; } public int nestedCommit() { return TwoPhaseOutcome.FINISH_OK; } /* * XA is not subtransaction aware. */ public int nestedPrepare() { return TwoPhaseOutcome.PREPARE_OK; // do nothing } public int topLevelPrepare() { if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.topLevelPrepare for " + this + ", record id=" + order()); } if (!_valid || (_theXAResource == null) || (_tranID == null)) { jtaLogger.i18NLogger.warn_resources_arjunacore_preparenulltx("XAResourceRecord.prepare"); removeConnection(); return TwoPhaseOutcome.PREPARE_NOTOK; } try { endAssociation(XAResource.TMSUCCESS, TxInfo.NOT_ASSOCIATED); _prepared = true; if (_theXAResource.prepare(_tranID) == XAResource.XA_RDONLY) { if (TxControl.isReadonlyOptimisation()) { // we won't be called again, so we need to tidy up now removeConnection(); } return TwoPhaseOutcome.PREPARE_READONLY; } else return TwoPhaseOutcome.PREPARE_OK; } catch (XAException e1) { addDeferredThrowable(e1); jtaLogger.i18NLogger.warn_resources_arjunacore_preparefailed(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e1), e1); /* * XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or * XAER_PROTO. */ if (_rollbackOptimization) // won't have rollback called on it removeConnection(); switch (e1.errorCode) { case XAException.XAER_RMERR: case XAException.XAER_RMFAIL: case XAException.XA_RBROLLBACK: case XAException.XA_RBEND: case XAException.XA_RBCOMMFAIL: case XAException.XA_RBDEADLOCK: case XAException.XA_RBINTEGRITY: case XAException.XA_RBOTHER: case XAException.XA_RBPROTO: case XAException.XA_RBTIMEOUT: case XAException.XAER_INVAL: case XAException.XAER_PROTO: case XAException.XAER_NOTA: // resource may have arbitrarily rolled back (shouldn't, but ...) return TwoPhaseOutcome.PREPARE_NOTOK; // will not call rollback default: return TwoPhaseOutcome.HEURISTIC_HAZARD; // we're not really sure (shouldn't get here though). } } catch (Exception e2) { jtaLogger.i18NLogger.warn_resources_arjunacore_preparefailed(XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2); if (_rollbackOptimization) // won't have rollback called on it removeConnection(); return TwoPhaseOutcome.PREPARE_NOTOK; } } public int topLevelAbort() { if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.topLevelAbort for " + this + ", record id=" + order()); } if (!_valid) return TwoPhaseOutcome.FINISH_ERROR; if (_theTransaction != null && _theTransaction.getXAResourceState(_theXAResource) == TxInfo.OPTIMIZED_ROLLBACK) { /* * Already rolledback during delist. */ return TwoPhaseOutcome.FINISH_OK; } if (_tranID == null) { jtaLogger.i18NLogger.warn_resources_arjunacore_rollbacknulltx("XAResourceRecord.rollback"); return TwoPhaseOutcome.FINISH_ERROR; } else { if (_theXAResource == null) _theXAResource = getNewXAResource(); if (_theXAResource != null) { if (_heuristic != TwoPhaseOutcome.FINISH_OK) return _heuristic; try { if (!_prepared) { endAssociation(XAResource.TMFAIL, TxInfo.FAILED); } } catch (XAException e1) { addDeferredThrowable(e1); if ((e1.errorCode >= XAException.XA_RBBASE) && (e1.errorCode < XAException.XA_RBEND)) { /* * Has been marked as rollback-only. We still * need to call rollback. */ } else if ((e1.errorCode == XAException.XAER_RMERR) || (e1.errorCode == XAException.XAER_RMFAIL)){ try { _theXAResource.rollback(_tranID); } catch (XAException e2) { addDeferredThrowable(e2); jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e2), e2); removeConnection(); return TwoPhaseOutcome.FINISH_ERROR; } } else { jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e1), e1); removeConnection(); return TwoPhaseOutcome.FINISH_ERROR; } } catch(RuntimeException e) { jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e); throw e; } try { _theXAResource.rollback(_tranID); } catch (XAException e1) { if (notAProblem(e1, false)) { // some other thread got there first (probably) } else { addDeferredThrowable(e1); jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e1), e1); switch (e1.errorCode) { case XAException.XAER_RMERR: if (!_prepared) break; // just do the finally block case XAException.XA_HEURHAZ: _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; case XAException.XA_HEURCOM: _heuristic = TwoPhaseOutcome.HEURISTIC_COMMIT; return TwoPhaseOutcome.HEURISTIC_COMMIT; case XAException.XA_HEURMIX: _heuristic = TwoPhaseOutcome.HEURISTIC_MIXED; return TwoPhaseOutcome.HEURISTIC_MIXED; case XAException.XAER_NOTA: if (_recovered) break; // rolled back previously and recovery completed case XAException.XA_HEURRB: // forget? case XAException.XA_RBROLLBACK: case XAException.XA_RBEND: case XAException.XA_RBCOMMFAIL: case XAException.XA_RBDEADLOCK: case XAException.XA_RBINTEGRITY: case XAException.XA_RBOTHER: case XAException.XA_RBPROTO: case XAException.XA_RBTIMEOUT: break; default: return TwoPhaseOutcome.FINISH_ERROR; } } } catch (Exception e2) { jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2); return TwoPhaseOutcome.FINISH_ERROR; } finally { if (!_prepared) removeConnection(); } } else { jtaLogger.i18NLogger.warn_resources_arjunacore_noresource(XAHelper.xidToString(_tranID)); if (XAResourceRecord._assumedComplete) { jtaLogger.i18NLogger.info_resources_arjunacore_assumecomplete(XAHelper.xidToString(_tranID)); return TwoPhaseOutcome.FINISH_OK; } else return TwoPhaseOutcome.FINISH_ERROR; } } return TwoPhaseOutcome.FINISH_OK; } public int topLevelCommit() { if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.topLevelCommit for " + this + ", record id=" + order()); } if (!_prepared) return TwoPhaseOutcome.NOT_PREPARED; if (_tranID == null) { jtaLogger.i18NLogger.warn_resources_arjunacore_commitnulltx("XAResourceRecord.commit"); return TwoPhaseOutcome.FINISH_ERROR; } else { if (_theXAResource == null) _theXAResource = getNewXAResource(); if (_theXAResource != null) { if (_heuristic != TwoPhaseOutcome.FINISH_OK) return _heuristic; /* * No need for end call here since we can only get to this * point by going through prepare. */ try { _theXAResource.commit(_tranID, false); } catch (XAException e1) { if (notAProblem(e1, true)) { // some other thread got there first (probably) } else { addDeferredThrowable(e1); jtaLogger.i18NLogger.warn_resources_arjunacore_commitxaerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e1), e1); /* * XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX, * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or * XAER_PROTO. */ switch (e1.errorCode) { case XAException.XA_HEURHAZ: _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; case XAException.XA_HEURCOM: // what about forget? // OTS doesn't support // this code here. break; case XAException.XA_HEURRB: case XAException.XA_RBROLLBACK: // could really do with an ABORTED status in TwoPhaseOutcome to differentiate case XAException.XA_RBCOMMFAIL: case XAException.XA_RBDEADLOCK: case XAException.XA_RBINTEGRITY: case XAException.XA_RBOTHER: case XAException.XA_RBPROTO: case XAException.XA_RBTIMEOUT: case XAException.XA_RBTRANSIENT: case XAException.XAER_RMERR: case XAException.XAER_PROTO: // XA spec implies rollback _heuristic = TwoPhaseOutcome.HEURISTIC_ROLLBACK; return TwoPhaseOutcome.HEURISTIC_ROLLBACK; case XAException.XA_HEURMIX: return TwoPhaseOutcome.HEURISTIC_MIXED; case XAException.XAER_NOTA: if (_recovered) break; // committed previously and recovery completed else { _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; // something terminated the transaction! } case XAException.XA_RETRY: case XAException.XAER_RMFAIL: _committed = true; // will cause log to be rewritten /* * Could do timeout retry here, but that could cause other resources in the list to go down the * heuristic path (some are far too keen to do this). Fail and let recovery retry. Meanwhile * the coordinator will continue to commit the other resources immediately. */ return TwoPhaseOutcome.FINISH_ERROR; case XAException.XAER_INVAL: // resource manager failed, did it rollback? default: _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; } } } catch (Exception e2) { jtaLogger.i18NLogger.warn_resources_arjunacore_commitxaerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2); return TwoPhaseOutcome.FINISH_ERROR; } finally { removeConnection(); } } else { jtaLogger.i18NLogger.warn_resources_arjunacore_noresource(XAHelper.xidToString(_tranID)); if (XAResourceRecord._assumedComplete) { jtaLogger.i18NLogger.info_resources_arjunacore_assumecomplete(XAHelper.xidToString(_tranID)); return TwoPhaseOutcome.FINISH_OK; } else if (_jndiName != null && wasResourceContactedByRecoveryModule(_jndiName)) { jtaLogger.i18NLogger.info_resources_arjunacore_rmcompleted(XAHelper.xidToString(_tranID)); return TwoPhaseOutcome.FINISH_OK; } else return TwoPhaseOutcome.FINISH_ERROR; } } return TwoPhaseOutcome.FINISH_OK; } /** * Is the XAException a non-error when received in reply to commit or * rollback ? It normally is, but may be overridden in recovery. */ protected boolean notAProblem(XAException ex, boolean commit) { return XAResourceErrorHandler.notAProblem(_theXAResource, ex, commit); } public int nestedOnePhaseCommit() { return TwoPhaseOutcome.FINISH_ERROR; } /** * For commit_one_phase we can do whatever we want since the transaction * outcome is whatever we want. Therefore, we do not need to save any * additional recoverable state, such as a reference to the transaction * coordinator, since it will not have an intentions list anyway. */ public int topLevelOnePhaseCommit() { if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.topLevelOnePhaseCommit for " + this + ", record id=" + order()); } boolean commit = true; if (_tranID == null) { jtaLogger.i18NLogger.warn_resources_arjunacore_opcnulltx("XAResourceRecord.1pc"); return TwoPhaseOutcome.ONE_PHASE_ERROR; // rolled back!! } else { if (_theXAResource != null) { if (_heuristic != TwoPhaseOutcome.FINISH_OK) return _heuristic; XAException endHeuristic = null; XAException endRBOnly = null; try { /* * TODO in Oracle the end is not needed. Is this common * across other RMs? */ endAssociation(XAResource.TMSUCCESS, TxInfo.NOT_ASSOCIATED); } catch (XAException e1) { /* * Now it's not legal to return a heuristic from end, but * apparently Oracle does (http://jira.jboss.com/jira/browse/JBTM-343) * Since this is 1PC we can call forget: the outcome of the * transaction is the outcome of the participant. */ switch (e1.errorCode) { case XAException.XA_HEURHAZ: case XAException.XA_HEURMIX: case XAException.XA_HEURCOM: case XAException.XA_HEURRB: endHeuristic = e1; break; case XAException.XA_RBROLLBACK: case XAException.XA_RBCOMMFAIL: case XAException.XA_RBDEADLOCK: case XAException.XA_RBINTEGRITY: case XAException.XA_RBOTHER: case XAException.XA_RBPROTO: case XAException.XA_RBTIMEOUT: case XAException.XA_RBTRANSIENT: /* * Has been marked as rollback-only. We still * need to call rollback. */ endRBOnly = e1; commit = false; break; case XAException.XAER_RMERR: case XAException.XAER_NOTA: case XAException.XAER_PROTO: case XAException.XAER_INVAL: case XAException.XAER_RMFAIL: default: { addDeferredThrowable(e1); jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e1), e1); removeConnection(); return TwoPhaseOutcome.ONE_PHASE_ERROR; } } } catch(RuntimeException e) { jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e); throw e; } try { /* * Not strictly necessary since calling commit will * do the rollback if end failed as above. */ if (endHeuristic != null) // catch those RMs that terminate in end rather than follow the spec throw endHeuristic; if (commit) _theXAResource.commit(_tranID, true); else { _theXAResource.rollback(_tranID); throw endRBOnly; } } catch (XAException e1) { addDeferredThrowable(e1); jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), XAHelper.printXAErrorCode(e1), e1); /* * XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX, * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or * XAER_PROTO. XA_RB* */ switch (e1.errorCode) { case XAException.XA_HEURHAZ: case XAException.XA_HEURMIX: _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; case XAException.XA_HEURCOM: forget(); break; case XAException.XA_HEURRB: forget(); return TwoPhaseOutcome.ONE_PHASE_ERROR; case XAException.XA_RBROLLBACK: case XAException.XA_RBCOMMFAIL: case XAException.XA_RBDEADLOCK: case XAException.XA_RBINTEGRITY: case XAException.XA_RBOTHER: case XAException.XA_RBPROTO: case XAException.XA_RBTIMEOUT: case XAException.XA_RBTRANSIENT: case XAException.XAER_RMERR: return TwoPhaseOutcome.ONE_PHASE_ERROR; case XAException.XAER_NOTA: _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; // something committed or rolled back without asking us! // Some RMs do (or did) one-phase commit but interpreting end as prepare and once you’ve prepared (in end) you can commit or rollback when a timeout goes off // I *think* we’re talking about a while ago so those RMs may no longer exist. // The alternative implication is that the RM timed out the branch between the end above and the completion call, if we do make a change to assume that scenario // it is possible we could break existing deployments so changes should be considered and potentially configurable case XAException.XAER_INVAL: // resource manager failed, did it rollback? _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; case XAException.XA_RETRY: // XA does not allow this to be thrown for 1PC! case XAException.XAER_PROTO: return TwoPhaseOutcome.ONE_PHASE_ERROR; // assume rollback case XAException.XAER_RMFAIL: // This was modified as part of JBTM-XYZ - although RMFAIL is not clear there is a rollback/commit we are flagging this to the user _heuristic = TwoPhaseOutcome.HEURISTIC_HAZARD; return TwoPhaseOutcome.HEURISTIC_HAZARD; default: _committed = true; // will cause log to be rewritten return TwoPhaseOutcome.FINISH_ERROR; // recovery should retry } } catch (Exception e2) { jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2); return TwoPhaseOutcome.FINISH_ERROR; } finally { removeConnection(); } } else return TwoPhaseOutcome.ONE_PHASE_ERROR; } if (commit) return TwoPhaseOutcome.FINISH_OK; else return TwoPhaseOutcome.FINISH_ERROR; } public boolean forgetHeuristic() { if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.forget for " + this); } forget(); // remove the connection regardless of whether or not the forget operation failed removeConnection(); return _forgotten; } private void forget() { if ((_theXAResource != null) && (_tranID != null)) { try { _theXAResource.forget(_tranID); // only update the heuristic state if forget succeeded _heuristic = TwoPhaseOutcome.FINISH_OK; _forgotten = true; } catch (Exception e) { jtaLogger.i18NLogger.warn_recovery_forgetfailed( "XAResourceRecord forget failed:", e); _forgotten = false; } } } /* * Independant recovery cannot occur. Must be driven by the recovery of the * local transaction, i.e., top-down recovery. */ protected int recover() { if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.recover"); } if (_committed) { /* * A previous commit attempt failed, but we know the intention * was to commit. So let's try again. */ if (topLevelCommit() == TwoPhaseOutcome.FINISH_OK) return XARecoveryResource.RECOVERED_OK; else return XARecoveryResource.FAILED_TO_RECOVER; } else return XARecoveryResource.WAITING_FOR_RECOVERY; } public boolean save_state(OutputObjectState os, int t) { boolean res = false; try { os.packInt(_heuristic); os.packBoolean(_committed); /* * Since we don't know what type of Xid we are using, leave it up to * XID to pack. */ XidImple.pack(os, _tranID); /* * If no recovery object set then rely upon object serialisation! */ if (_recoveryObject == null) { os.packInt(RecoverableXAConnection.OBJECT_RECOVERY); os.packString(_productName); os.packString(_productVersion); os.packString(_jndiName); if (_theXAResource instanceof Serializable) { try { ByteArrayOutputStream s = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(s); o.writeObject(_theXAResource); o.close(); os.packBoolean(true); String name = _theXAResource.getClass().getName(); os.packString(name); os.packBytes(s.toByteArray()); } catch (NotSerializableException ex) { jtaLogger.i18NLogger.warn_resources_arjunacore_savestate(); return false; } } else { // have to rely upon XAResource.recover! os.packBoolean(false); } } else { os.packInt(RecoverableXAConnection.AUTO_RECOVERY); os.packString(_recoveryObject.getClass().getName()); _recoveryObject.packInto(os); } res = true; } catch (Exception e) { jtaLogger.i18NLogger.warn_resources_arjunacore_savestateerror(_theXAResource.toString(), XAHelper.xidToString(_tranID), e); res = false; } if (res) res = super.save_state(os, t); return res; } public boolean restore_state(InputObjectState os, int t) { boolean res = false; try { _heuristic = os.unpackInt(); _committed = os.unpackBoolean(); _tranID = XidImple.unpack(os); _theXAResource = null; _recoveryObject = null; if (os.unpackInt() == RecoverableXAConnection.OBJECT_RECOVERY) { _productName = os.unpackString(); _productVersion = os.unpackString(); _jndiName = os.unpackString(); boolean haveXAResource = os.unpackBoolean(); if (haveXAResource) { try { // Read the classname of the serialized XAResource String className = os.unpackString(); byte[] b = os.unpackBytes(); ByteArrayInputStream s = new ByteArrayInputStream(b); ObjectInputStream o = new ObjectInputStream(s); // Give the list of deserializers a chance to deserialize the record boolean deserialized = false; Iterator<SerializableXAResourceDeserializer> iterator = getXAResourceDeserializers().iterator(); while (iterator.hasNext()) { SerializableXAResourceDeserializer proxyXAResourceDeserializer = iterator.next(); if (proxyXAResourceDeserializer.canDeserialze(className)) { _theXAResource = proxyXAResourceDeserializer.deserialze(o); deserialized = true; break; } } // Give it a go ourselves if (!deserialized) { try { _theXAResource = (XAResource) o.readObject(); if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.restore_state - XAResource de-serialized"); } } catch (ClassNotFoundException e) { // JBTM-2550 if we fail to deserialize the object, we treat it as haveXAResource is false jtaLogger.i18NLogger.warn_resources_arjunacore_classnotfound(className); haveXAResource = false; } } o.close(); } catch (Exception ex) { // not serializable in the first place! jtaLogger.i18NLogger.warn_resources_arjunacore_restorestate(ex); return false; } } if (!haveXAResource) { /* * Lookup new XAResource via XARecoveryModule if possible. */ _theXAResource = getNewXAResource(); if (_theXAResource == null) { jtaLogger.i18NLogger.warn_resources_arjunacore_norecoveryxa( toString() ); /* * Don't prevent tx from activating because there may be * other participants that can still recover. Plus, we will * try to get a new XAResource later for this instance. */ res = true; } } } else { String creatorName = os.unpackString(); _recoveryObject = ClassloadingUtility.loadAndInstantiateClass(RecoverableXAConnection.class, creatorName, null); if(_recoveryObject == null) { throw new ClassNotFoundException(); } _recoveryObject.unpackFrom(os); _theXAResource = _recoveryObject.getResource(); if (jtaLogger.logger.isTraceEnabled()) { jtaLogger.logger.trace("XAResourceRecord.restore_state - XAResource got from " + creatorName); } } res = true; } catch (Exception e) { jtaLogger.i18NLogger.warn_resources_arjunacore_restorestateerror(_theXAResource.toString(), XAHelper.xidToString(_tranID), e); res = false; } finally { if (res) res = super.restore_state(os, t); /* * If we're here then we've restored enough to print data on * this instance. */ if (_heuristic != TwoPhaseOutcome.FINISH_OK) { jtaLogger.logger.warn("XAResourceRecord restored heuristic instance: "+this); } } return res; } public String type() { return XAResourceRecord.typeName(); } public static String typeName() { return "/StateManager/AbstractRecord/XAResourceRecord"; } public boolean doSave() { return true; } 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 a) { return false; } /** * Returns the resource manager product name. * @return the product name */ public String getProductName() { return _productName; } /** * Sets the resource manager product name. * @param productName the product name */ public void setProductName(String productName) { this._productName = productName; } /** * Returns the resource manager product version. * @return the product version */ public String getProductVersion() { return _productVersion; } /** * Sets the resource manager product version. * @param productVersion the product version */ public void setProductVersion(String productVersion) { this._productVersion = productVersion; } /** * Returns the resource manager JNDI name for e.g. xa datasource. * Note this is not used for lookup, only for information. * @return the JNDI name. */ public String getJndiName() { return _jndiName; } /** * Sets the resource manager JNDI name. * Note this is not used for lookup, only for information. * @param jndiName the JNDI name. */ public void setJndiName(String jndiName) { this._jndiName = jndiName; } public XAResourceRecord() { super(); _theXAResource = null; _recoveryObject = null; _tranID = null; _prepared = true; _heuristic = TwoPhaseOutcome.FINISH_OK; _valid = true; _theTransaction = null; _recovered = true; } public XAResourceRecord(Uid u) { super(u, null, ObjectType.ANDPERSISTENT); _theXAResource = null; _recoveryObject = null; _tranID = null; _prepared = true; _heuristic = TwoPhaseOutcome.FINISH_OK; _valid = true; _theTransaction = null; _recovered = true; } public String toString () { return "XAResourceRecord < resource:"+_theXAResource+", txid:"+_tranID+ ", heuristic: "+TwoPhaseOutcome.stringForm(_heuristic)+ ((_productName != null && _productVersion != null) ? ", product: "+_productName+"/"+_productVersion : "")+ ((_jndiName != null) ? ", jndiName: "+_jndiName : "")+ " "+super.toString()+" >"; } private List<SerializableXAResourceDeserializer> getXAResourceDeserializers() { if (serializableXAResourceDeserializers != null) { return serializableXAResourceDeserializers; } synchronized (this) { if (serializableXAResourceDeserializers != null) { return serializableXAResourceDeserializers; } serializableXAResourceDeserializers = new ArrayList<SerializableXAResourceDeserializer>(); for (RecoveryModule recoveryModule : RecoveryManager.manager().getModules()) { if (recoveryModule instanceof XARecoveryModule) { XARecoveryModule xaRecoveryModule = (XARecoveryModule) recoveryModule; serializableXAResourceDeserializers.addAll(xaRecoveryModule.getSeriablizableXAResourceDeserializers()); return serializableXAResourceDeserializers; } } } return serializableXAResourceDeserializers; } /** * This routine finds the new XAResource for the transaction that used the * old resource we couldn't serialize. It does this by looking up the * XARecoveryModule in the recovery manager and asking it for the * XAResource. The recovery manager will then look through its list of * registered XARecoveryResource implementations for the appropriate * instance. If the XARecoveryModule hasn't been initialised yet then this * routine will fail, but on the next scan it should work. */ private final XAResource getNewXAResource() { RecoveryManager recMan = RecoveryManager.manager(); Vector recoveryModules = recMan.getModules(); if (recoveryModules != null) { Enumeration modules = recoveryModules.elements(); while (modules.hasMoreElements()) { RecoveryModule m = (RecoveryModule) modules.nextElement(); if (m instanceof XARecoveryModule) { /* * Blaargh! There are better ways to do this! */ return ((XARecoveryModule) m).getNewXAResource(this); } } } return null; } private final void removeConnection() { /* * Should only be called once. Remove the connection so that user can * reuse the driver as though it were fresh (e.g., can do read only * optimisation). */ if (_recoveryObject != null) { _recoveryObject.close(); _recoveryObject = null; } if (_theTransaction != null) _theTransaction = null; } /* * Ask the transaction whether or not this XAResource is still associated * with the thread, i.e., has end already been called on it? */ private void endAssociation(int xaState, int txInfoState) throws XAException { if (_theTransaction != null) { _theTransaction.endAssociation(_tranID, _theXAResource, xaState, txInfoState); } } private boolean wasResourceContactedByRecoveryModule(final String jndiName) { final Vector<RecoveryModule> recoveryModules = RecoveryManager.manager().getModules(); for (final RecoveryModule recoveryModule : recoveryModules) { if (recoveryModule instanceof XARecoveryModule) { return ((XARecoveryModule) recoveryModule).getContactedJndiNames().contains(jndiName); } } return false; } public int getHeuristic() { return _heuristic; } public boolean isForgotten() { return _forgotten; } protected XAResource _theXAResource; private boolean _forgotten; private RecoverableXAConnection _recoveryObject; private Xid _tranID; private boolean _prepared; private boolean _valid; private int _heuristic; private boolean _committed = false; // try to optimize recovery private TransactionImple _theTransaction; private boolean _recovered = false; // extra metadata from the wrapper, if present private String _productName; private String _productVersion; private String _jndiName; private static final XAResourceRecordWrappingPlugin _xaResourceRecordWrappingPlugin = jtaPropertyManager.getJTAEnvironmentBean().getXAResourceRecordWrappingPlugin(); private static final boolean _rollbackOptimization = jtaPropertyManager.getJTAEnvironmentBean().isXaRollbackOptimization(); /* * WARNING: USE WITH EXTEREME CARE!! * * This assumes that if there is no XAResource that can deal with an Xid * after recovery, then we failed after successfully committing the transaction * but before updating the log. In which case we just need to ignore this * resource and remove the entry from the log. * * BUT if not all XAResourceRecovery instances are correctly implemented * (or present) we may end up removing participants that have not been dealt * with. Hence USE WITH EXTREME CARE!! */ private static final boolean _assumedComplete = jtaPropertyManager.getJTAEnvironmentBean().isXaAssumeRecoveryComplete(); private List<SerializableXAResourceDeserializer> serializableXAResourceDeserializers; void addDeferredThrowable(Exception e) { if (this.deferredExceptions == null) this.deferredExceptions = new ArrayList<>(); this.deferredExceptions.add(e); } @Override public void getDeferredThrowables(List<Throwable> list) { if (deferredExceptions != null) list.addAll(deferredExceptions); } }