/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY 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 along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.arjuna.ats.jta.distributed.server.impl;
import java.io.Serializable;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.jboss.tm.XAResourceWrapper;
import com.arjuna.ats.jta.distributed.server.CompletionCounter;
import com.arjuna.ats.jta.distributed.server.LookupProvider;
/**
* I chose for this class to implement XAResourceWrapper so that I can provide a
* name to the Transaction manager for it to store in its XID.
* <p>
* In the normal situation, a ProxyXAResource is Serialized, therefore we do not
* get the chance to recover the transactions in a call to
* XAResource::recover(), therefore the ProxyXAResource must tell the remote
* side when it calls each method, whether or not to attempt to recover the
* transaction before invoking its transactional directive.
*/
public class ProxyXAResource implements XAResource, XAResourceWrapper, Serializable {
private int transactionTimeout;
private String remoteServerName;
private String localServerName;
private transient boolean nonerecovered;
private Xid migratedXid;
private transient boolean handleError;
/**
* Create a new proxy to the remote server.
*
* @param LookupProvider
* .getLookupProvider()
* @param localServerName
* @param remoteServerName
*/
public ProxyXAResource(String localServerName, String remoteServerName, Xid migratedXid, boolean handleError) {
this.localServerName = localServerName;
this.remoteServerName = remoteServerName;
this.migratedXid = migratedXid;
this.nonerecovered = true;
this.handleError = handleError;
}
/**
* Constructor for fallback bottom up recovery.
*
* @param localServerName
* @param remoteServerName
*/
public ProxyXAResource(String localServerName, String remoteServerName) {
this.localServerName = localServerName;
this.remoteServerName = remoteServerName;
}
/**
* Store the XID.
*/
@Override
public void start(Xid xid, int flags) throws XAException {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_START [" + xid + "]");
}
/**
* Reference the XID.
*/
@Override
public void end(Xid xid, int flags) throws XAException {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_END [" + xid + "]");
}
/**
* Prepare the resource, save the XID locally first, the propagate the
* prepare. This ensures that in recovery we know the XID to ask a remote
* server about.
*/
@Override
public int prepare(Xid xid) throws XAException {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_PREPARE [" + xid + "]");
Xid toPropagate = migratedXid != null ? migratedXid : xid;
int propagatePrepare = LookupProvider.getInstance().lookup(remoteServerName).prepare(toPropagate, !nonerecovered);
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_PREPARED");
return propagatePrepare;
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_COMMIT [" + xid + "]");
Xid toPropagate = migratedXid != null ? migratedXid : xid;
try {
LookupProvider.getInstance().lookup(remoteServerName).commit(toPropagate, onePhase, !nonerecovered);
} catch (Error e) {
if (handleError) {
throw new RuntimeException();
} else {
throw e;
}
}
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_COMMITED");
CompletionCounter.getInstance().incrementCommit(localServerName);
}
@Override
public void rollback(Xid xid) throws XAException {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_ROLLBACK[" + xid + "]");
Xid toPropagate = migratedXid != null ? migratedXid : xid;
try {
LookupProvider.getInstance().lookup(remoteServerName).rollback(toPropagate, !nonerecovered);
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_ROLLBACKED");
} catch (XAException e) {
// We know the remote side must have done a JBTM-927
if (e.errorCode == XAException.XAER_INVAL) {
// We know that this means that the transaction is not known at
// the remote side
CompletionCounter.getInstance().incrementRollback(localServerName);
}
throw e;
}
CompletionCounter.getInstance().incrementRollback(localServerName);
}
/**
* This will ensure that the remote server has loaded the subordinate
* transaction.
*
* @return It returns the proxies view of the XID state, returning the
* remote servers view of the XID would present an XID to the local
* server that it knows nothing about and indeed potentially the
* remote server does not have a corresponding record of the XID in
* case of failure during prepare.
*/
@Override
public Xid[] recover(int flag) throws XAException {
if ((flag & XAResource.TMSTARTRSCAN) == XAResource.TMSTARTRSCAN) {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_RECOVER [XAResource.TMSTARTRSCAN]");
}
if ((flag & XAResource.TMENDRSCAN) == XAResource.TMENDRSCAN) {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_RECOVER [XAResource.TMENDRSCAN]");
}
Xid[] toReturn = LookupProvider.getInstance().lookup(remoteServerName).recoverFor(localServerName);
if (toReturn != null) {
for (int i = 0; i < toReturn.length; i++) {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_RECOVERD: " + toReturn[i]);
}
}
return toReturn;
}
@Override
public void forget(Xid xid) throws XAException {
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_FORGET [" + xid + "]");
Xid toPropagate = migratedXid != null ? migratedXid : xid;
LookupProvider.getInstance().lookup(remoteServerName).forget(toPropagate, !nonerecovered);
System.out.println("[" + Thread.currentThread().getName() + "] ProxyXAResource (" + localServerName + ":" + remoteServerName + ") XA_FORGETED[" + xid + "]");
}
@Override
public int getTransactionTimeout() throws XAException {
return transactionTimeout;
}
@Override
public boolean setTransactionTimeout(int seconds) throws XAException {
this.transactionTimeout = seconds;
return true;
}
@Override
public boolean isSameRM(XAResource xares) throws XAException {
boolean toReturn = false;
if (xares instanceof ProxyXAResource) {
if (((ProxyXAResource) xares).remoteServerName == remoteServerName) {
toReturn = true;
}
}
return toReturn;
}
/**
* Not used by the TM.
*/
@Override
public XAResource getResource() {
return null;
}
/**
* Not used by the TM.
*/
@Override
public String getProductName() {
return null;
}
/**
* Not used by the TM.
*/
@Override
public String getProductVersion() {
return null;
}
/**
* This allows the proxy to contain meaningful information in the XID in
* case of failure to recover.
*/
@Override
public String getJndiName() {
return "ProxyXAResource: " + localServerName + " " + remoteServerName;
}
}