/*
* 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) 2005
*
* Arjuna Solutions Limited,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id$
*/
package com.arjuna.ats.internal.jta.resources.arjunacore;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import com.arjuna.ats.arjuna.coordinator.ExceptionDeferrer;
import com.arjuna.ats.arjuna.coordinator.OnePhaseResource;
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.jta.logging.jtaLogger;
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;
/**
* One Phase resource wrapper for XAResources.
* @author Kevin Conner (Kevin.Conner@arjuna.com)
* @version $Id$
* @since ATS 4.1
*/
public class XAOnePhaseResource implements OnePhaseResource, ExceptionDeferrer
{
/**
* The one phase XA resource.
*/
private XAResource xaResource ;
/**
* The recoverable XA connection.
*/
private RecoverableXAConnection recoverableXAConnection ;
/**
* The transaction identified.
*/
private Xid xid ;
/**
* Any XAException that occurs.
*/
List<Throwable> deferredExceptions;
/**
* Default constructor for deserialising resource.
*/
public XAOnePhaseResource()
{
}
/**
* Construct the one phase wrapper for the specified resource.
* @param xaResource The XA resource being wrapped.
* @param xid The transaction identifier.
* @param params additional params to pass through.
*/
public XAOnePhaseResource(final XAResource xaResource, final Xid xid, final Object[] params)
{
this.xaResource = xaResource ;
this.xid = xid ;
if ((params != null) && (params.length >= XAResourceRecord.XACONNECTION))
{
final Object param = params[XAResourceRecord.XACONNECTION] ;
if (param instanceof RecoverableXAConnection)
recoverableXAConnection = (RecoverableXAConnection)param ;
}
}
/**
* Commit the one phase resource.
* @return TwoPhaseOutcome.FINISH_OK, TwoPhaseOutcome.ONE_PHASE_ERROR or TwoPhaseOutcome.FINISH_ERROR
*/
public int commit()
{
boolean doForget = false;
try
{
// TODO we don't do an end here yet we do in 2PC. Check!!
xaResource.commit(xid, true) ;
return TwoPhaseOutcome.FINISH_OK ;
}
catch (final XAException xae)
{
addDeferredThrowable(xae);
if (jtaLogger.logger.isTraceEnabled()) {
jtaLogger.logger.trace("XAOnePhaseResource.commit(" + xid + ") " + xae.getMessage());
}
jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(XAHelper.xidToString(xid),
xaResource.toString(), XAHelper.printXAErrorCode(xae), xae);
switch (xae.errorCode)
{
case XAException.XA_HEURHAZ:
case XAException.XA_HEURMIX:
return TwoPhaseOutcome.HEURISTIC_HAZARD;
case XAException.XA_HEURCOM:
doForget = true;
return TwoPhaseOutcome.FINISH_OK;
case XAException.XA_HEURRB:
doForget = true;
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:
return TwoPhaseOutcome.HEURISTIC_HAZARD; // something committed or rolled back without asking us!
case XAException.XAER_INVAL: // resource manager failed, did it rollback?
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:
default:
return TwoPhaseOutcome.FINISH_ERROR; // recovery should retry
}
}
catch (final Throwable ex)
{
String xidString = xid == null ? "< >" : XAHelper.xidToString(xid);
String resourceString = xaResource == null ? "null XA resource" : xaResource.toString();
if (jtaLogger.logger.isTraceEnabled()) {
jtaLogger.logger.trace("XAOnePhaseResource.commit(" + xid + ") " + ex.getMessage());
}
jtaLogger.i18NLogger.warn_resources_arjunacore_commitxaerror(xidString, resourceString, "-", ex);
}
finally
{
try
{
if (doForget)
xaResource.forget(xid);
}
catch (final Throwable ex)
{
if (jtaLogger.logger.isTraceEnabled()) {
jtaLogger.logger.trace("XAOnePhaseResource.commit(" + xid + ") called forget and got " + ex.getMessage());
}
}
}
return TwoPhaseOutcome.ONE_PHASE_ERROR; // presume abort.
}
/**
* Commit the one phase resource.
* @return TwoPhaseOutcome.FINISH_OK or TwoPhaseOutcome.FINISH_ERROR
*/
public int rollback()
{
try
{
xaResource.rollback(xid) ;
return TwoPhaseOutcome.FINISH_OK ;
}
catch (final XAException xae)
{
addDeferredThrowable(xae);
jtaLogger.i18NLogger.warn_resources_arjunacore_XAOnePhaseResource_rollbackexception(XAHelper.xidToString(xid), xae);
}
catch (final Throwable ex)
{
if (jtaLogger.logger.isTraceEnabled()) {
jtaLogger.logger.trace("XAOnePhaseResource.rollback(" + xid + ") " + ex.getMessage());
}
}
return TwoPhaseOutcome.FINISH_ERROR ;
}
/**
* Pack the state of the resource.
* @param os The object output state.
*/
public void pack(final OutputObjectState os)
throws IOException
{
XidImple.pack(os, xid);
if (recoverableXAConnection != null)
{
os.packInt(RecoverableXAConnection.AUTO_RECOVERY);
os.packString(recoverableXAConnection.getClass().getName());
recoverableXAConnection.packInto(os);
}
else
{
os.packInt(RecoverableXAConnection.OBJECT_RECOVERY) ;
final byte[] data ;
try
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
final ObjectOutputStream oos = new ObjectOutputStream(baos) ;
oos.writeObject(xaResource) ;
oos.flush() ;
oos.close() ;
data = baos.toByteArray() ;
}
catch (final IOException ioe)
{
final String message = jtaLogger.i18NLogger.get_resources_arjunacore_XAOnePhaseResource_pack();
IOException ioException = new IOException(message);
ioException.initCause(ioe);
throw ioException;
}
os.packBytes(data) ;
}
}
/**
* Unpack the state of the resource.
* @param is The object input state.
*/
public void unpack(final InputObjectState is)
throws IOException
{
XidImple.unpack(is) ;
final int recoveryType = is.unpackInt() ;
switch(recoveryType)
{
case RecoverableXAConnection.AUTO_RECOVERY:
final String recoverableXAConnectionClassName = is.unpackString() ;
recoverableXAConnection = ClassloadingUtility.loadAndInstantiateClass(RecoverableXAConnection.class, recoverableXAConnectionClassName, null);
if(recoverableXAConnection == null) {
throw generateUnpackError(new ClassNotFoundException());
}
recoverableXAConnection.unpackFrom(is) ;
try
{
xaResource = recoverableXAConnection.getResource() ;
}
catch (final SQLException sqle)
{
throw generateUnpackError(sqle) ;
}
break ;
case RecoverableXAConnection.OBJECT_RECOVERY:
final byte[] data = is.unpackBytes() ;
try
{
final ByteArrayInputStream bais = new ByteArrayInputStream(data) ;
final ObjectInputStream ois = new ObjectInputStream(bais) ;
xaResource = (XAResource)ois.readObject() ;
}
catch (final ClassNotFoundException cnfe)
{
throw generateUnpackError(cnfe) ;
}
catch (final IOException ioe)
{
throw generateUnpackError(ioe) ;
}
catch (final ClassCastException cce)
{
throw generateUnpackError(cce) ;
}
break ;
default:
final String message = jtaLogger.i18NLogger.get_resources_arjunacore_XAOnePhaseResource_unpackType(Integer.toString(recoveryType));
throw new IOException(message);
}
}
@Override
public String toString()
{
return "XAOnePhaseResource("+xaResource+")";
}
/**
* Generate the IOException for the corresponding unpack exception.
* @param ex The exception caught in unpack.
* @return The corresponding IOException to be thrown.
*/
private static IOException generateUnpackError(final Exception ex)
{
final String message = jtaLogger.i18NLogger.get_resources_arjunacore_XAOnePhaseResource_unpack();
return new IOException(message, ex) ;
}
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);
}
}