/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file 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 org.jboss.test.jca.adapter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import javax.resource.ResourceException; import javax.resource.spi.ConnectionEvent; import javax.resource.spi.ConnectionEventListener; import javax.resource.spi.ConnectionRequestInfo; import javax.resource.spi.LocalTransaction; import javax.resource.spi.ManagedConnection; import javax.resource.spi.ManagedConnectionMetaData; import javax.resource.spi.ResourceAdapterInternalException; import javax.security.auth.Subject; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.jboss.logging.Logger; import org.jboss.tm.TxUtils; /** * TestManagedConnection.java * * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a> * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a> * @version <tt>$Revision: 81036 $</tt> */ public class TestManagedConnection implements ManagedConnection, XAResource, LocalTransaction { public static final String STARTED = "STARTED"; public static final String SUSPENDED = "SUSPENDED"; public static final String ENDED = "ENDED"; public static final String PREPARED = "PREPARED"; public static final String LOCAL_NONE = "LOCAL_NONE"; public static final String LOCAL_TRANSACTION = "LOCAL_TRANSACTION"; public static final String LOCAL_COMMITTED = "LOCAL_COMMITTED"; public static final String LOCAL_ROLLEDBACK = "LOCAL_ROLLEDBACK"; private final int id; private Logger log = Logger.getLogger(getClass()); private TestManagedConnectionFactory mcf; private HashSet handles = new HashSet(); private HashSet listeners = new HashSet(); private GlobalXID currentXid; private AtomicBoolean destroyed = new AtomicBoolean(false); private boolean failInPrepare = false; private boolean failInCommit = false; private static boolean failInStart = false; private static boolean failInEnd = false; private static int xaCode; private String localState = LOCAL_NONE; public static void setFailInStart(boolean fis, int xa) { failInStart = fis; xaCode = xa; } public static void setFailInEnd(boolean fie, int xa) { failInEnd = fie; xaCode = xa; } public TestManagedConnection (final TestManagedConnectionFactory mcf, final Subject subject, final TestConnectionRequestInfo cri, final int id) { this.mcf = mcf; this.id = id; } void setFailInPrepare(final boolean fail, final int xaCode) { this.failInPrepare = fail; this.xaCode = xaCode; } void setFailInCommit(final boolean fail, final int xaCode) { this.failInCommit = fail; this.xaCode = xaCode; } // implementation of javax.resource.spi.ManagedConnection interface public synchronized void destroy() throws ResourceException { log.info("Destroying connection: " + this); if (destroyed.get()) return; cleanup(); destroyed.set(true); currentXid = null; } public synchronized void cleanup() throws ResourceException { log.info("cleanup: " + this +" handles=" + handles); checkDestroyedResourceException(); for (Iterator i = handles.iterator(); i.hasNext(); ) { TestConnection c = (TestConnection)i.next(); c.setMc(null); i.remove(); } } public synchronized Object getConnection(Subject param1, ConnectionRequestInfo param2) throws ResourceException { log.info("getConnection " + this); checkDestroyedResourceException(); if (param2 != null && ((TestConnectionRequestInfo) param2).failure.equals("getConnectionResource")) throw new ResourceException(this.toString()); if (param2 != null && ((TestConnectionRequestInfo) param2).failure.equals("getConnectionRuntime")) throw new RuntimeException(this.toString()); TestConnection c = new TestConnection(this); handles.add(c); return c; } public synchronized void associateConnection(Object p) throws ResourceException { log.info("associateConnecton " + this + " connection=" + p); checkDestroyedResourceException(); if (p instanceof TestConnection) { ((TestConnection)p).setMc(this); handles.add(p); } else { throw new ResourceException("wrong kind of Connection " + p); } } public synchronized void addConnectionEventListener(ConnectionEventListener cel) { log.info("addCEL: " + this + " " + cel); listeners.add(cel); } public synchronized void removeConnectionEventListener(ConnectionEventListener cel) { log.info("removeCEL: " + this + " " + cel); listeners.remove(cel); } public synchronized XAResource getXAResource() throws ResourceException { checkDestroyedResourceException(); return this; } public LocalTransaction getLocalTransaction() throws ResourceException { return this; } public ManagedConnectionMetaData getMetaData() throws ResourceException { return null; } public void setLogWriter(PrintWriter param1) throws ResourceException { } public PrintWriter getLogWriter() throws ResourceException { return null; } // implementation of javax.transaction.xa.XAResource interface public List getListeners() { List result = null; synchronized (listeners) { result = new ArrayList(listeners); } return result; } public void start(Xid xid, int flags) throws XAException { long sleepInStart = mcf.getSleepInStart(); if (flags == TMNOFLAGS && sleepInStart != 0) doSleep(sleepInStart); synchronized (this) { if(failInStart) { XAException xaex = new XAException(xaCode + "for" + this); xaex.errorCode = xaCode; broadcastConnectionError(xaex); throw xaex; } GlobalXID gid = new GlobalXID(xid); String flagString = TxUtils.getXAResourceFlagsAsString(flags); log.info("start with xid=" + gid + " flags=" + flagString + " for " + this); checkDestroyedXAException(); Map xids = getXids(); synchronized (xids) { String state = (String) xids.get(gid); if (state == null && flags != TMNOFLAGS) { XAException xaex = new XAException("Invalid start state=" + state + " xid=" + gid + " flags=" + flagString + " for " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } if (state != null && state != SUSPENDED && state != ENDED && (state != STARTED || ((flags & TMJOIN) == 0)) && (state != STARTED || ((flags & TMRESUME) == 0)) ) { XAException xaex = new XAException("Invalid start state=" + state + " xid=" + gid + " flags=" + flagString + " for " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } if ((flags & TMJOIN) != 0 && mcf.failJoin) { XAException xaex = new XAException("Join is not allowed " + state + " xid=" + gid + " flags=" + flagString + " for " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } xids.put(gid, STARTED); } this.currentXid = gid; } } public void end(final Xid xid, final int flags) throws XAException { if(failInEnd) { XAException xaex = new XAException(xaCode + "for" + this); xaex.errorCode = xaCode; broadcastConnectionError(xaex); throw xaex; } long sleepInEnd = mcf.getSleepInEnd(); if (flags != TMSUCCESS && sleepInEnd != 0) doSleep(sleepInEnd); synchronized (this) { GlobalXID gid = new GlobalXID(xid); String flagString = TxUtils.getXAResourceFlagsAsString(flags); log.info("end with xid=" + gid + " flags=" + flagString + " for " + this); // checkDestroyedXAException(); (check is broken, don't use with JBossTS) Map xids = getXids(); synchronized (xids) { String state = (String) xids.get(gid); if (state != STARTED && state != SUSPENDED && state != ENDED) { XAException xaex = new XAException("Invalid end state=" + state + " xid=" + gid + " " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } if ((flags & TMSUSPEND) == 0) xids.put(gid, ENDED); else xids.put(gid, SUSPENDED); } this.currentXid = null; } } public synchronized void commit(Xid xid, boolean onePhase) throws XAException { GlobalXID gid = new GlobalXID(xid); log.info("commit with xid=" + gid + " onePhase=" + onePhase + " for " + this); checkDestroyedXAException(); if (failInCommit) { XAException xaex = new XAException(xaCode + " for " + this); xaex.errorCode = xaCode; throw xaex; } Map xids = getXids(); synchronized (xids) { String state = (String) xids.get(gid); if (onePhase) { if (state != SUSPENDED && state != ENDED) { XAException xaex = new XAException("Invalid one phase commit state=" + state + " xid=" + gid + " " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } } else { if (state != PREPARED) { XAException xaex = new XAException("Invalid two phase commit state=" + state + " xid=" + gid + " " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } } xids.remove(gid); } } public synchronized void rollback(Xid xid) throws XAException { GlobalXID gid = new GlobalXID(xid); log.info("rollback with xid=" + gid + " for " + this); checkDestroyedXAException(); Map xids = getXids(); synchronized (xids) { String state = (String) xids.get(gid); if (state != SUSPENDED && state != ENDED && state != PREPARED) { XAException xaex = new XAException("Invalid rollback state=" + state + " xid=" + gid + " " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } xids.remove(gid); } } public synchronized int prepare(Xid xid) throws XAException { GlobalXID gid = new GlobalXID(xid); log.info("prepare with xid=" + gid + " for " + this); checkDestroyedXAException(); Map xids = getXids(); synchronized (xids) { String state = (String) xids.get(gid); if (state != SUSPENDED && state != ENDED) { XAException xaex = new XAException("Invalid prepare state=" + state + " xid=" + gid + " " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } if (failInPrepare) { XAException xae = new XAException(xaCode + " for " + this); xae.errorCode = xaCode; throw xae; } xids.put(gid, PREPARED); return XA_OK; } } public synchronized void forget(Xid xid) throws XAException { GlobalXID gid = new GlobalXID(xid); log.info("forget with xid=" + gid + " for " + this); checkDestroyedXAException(); Map xids = getXids(); synchronized (xids) { xids.remove(gid); } } public Xid[] recover(int param1) throws XAException { return null; } public boolean isSameRM(XAResource xar) throws XAException { if (xar == null || xar instanceof TestManagedConnection == false) return false; TestManagedConnection other = (TestManagedConnection) xar; return (mcf == other.mcf); } public int getTransactionTimeout() throws XAException { return 0; } public boolean setTransactionTimeout(int param1) throws XAException { return false; } public String getLocalState() { return localState; } public void begin() throws ResourceException { localState = LOCAL_TRANSACTION; } public void sendBegin() throws ResourceException { begin(); ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_STARTED); Collection copy = new ArrayList(listeners); for (Iterator i = copy.iterator(); i.hasNext(); ) { ConnectionEventListener cel = (ConnectionEventListener)i.next(); try { cel.localTransactionStarted(event); } catch (Throwable ignored) { log.warn("Ignored", ignored); } } } public void commit() throws ResourceException { localState = LOCAL_COMMITTED; } public void sendCommit() throws ResourceException { commit(); ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_COMMITTED); Collection copy = new ArrayList(listeners); for (Iterator i = copy.iterator(); i.hasNext(); ) { ConnectionEventListener cel = (ConnectionEventListener)i.next(); try { cel.localTransactionCommitted(event); } catch (Throwable ignored) { log.warn("Ignored", ignored); } } } public void rollback() throws ResourceException { localState = LOCAL_ROLLEDBACK; } public void sendRollback() throws ResourceException { rollback(); ConnectionEvent event = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK); Collection copy = new ArrayList(listeners); for (Iterator i = copy.iterator(); i.hasNext(); ) { ConnectionEventListener cel = (ConnectionEventListener)i.next(); try { cel.localTransactionRolledback(event); } catch (Throwable ignored) { log.warn("Ignored", ignored); } } } synchronized boolean isInTx() { log.info("isInTx: " + this); return currentXid != null; } Map getXids() { return mcf.getXids(); } void connectionClosed(TestConnection handle) { if (destroyed.get()) return; log.info("Connetion closed handle=" + handle + " for " + this); ConnectionEvent ce = new ConnectionEvent(this ,ConnectionEvent.CONNECTION_CLOSED); ce.setConnectionHandle(handle); Collection copy = new ArrayList(listeners); for (Iterator i = copy.iterator(); i.hasNext(); ) { log.info("notifying 1 cel connectionClosed"); ConnectionEventListener cel = (ConnectionEventListener)i.next(); try { cel.connectionClosed(ce); } catch (Throwable ignored) { log.warn("Ignored", ignored); } } synchronized (this) { handles.remove(handle); } } protected void broadcastConnectionError(Throwable e) { if(destroyed.get()) return; Exception ex = null; if (e instanceof Exception) ex = (Exception) e; else ex = new ResourceAdapterInternalException("Unexpected error", e); ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_ERROR_OCCURRED, ex); Collection copy = null; synchronized(listeners) { copy = new ArrayList(listeners); } for (Iterator i = copy.iterator(); i.hasNext(); ) { ConnectionEventListener cel = (ConnectionEventListener)i.next(); try { cel.connectionErrorOccurred(ce); } catch (Throwable t) { } } } void connectionError(TestConnection handle, Exception e) { if (destroyed.get()) return; log.info("Connetion error handle=" + handle + " for " + this, e); ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_ERROR_OCCURRED, e); ce.setConnectionHandle(handle); Collection copy = new ArrayList(listeners); for (Iterator i = copy.iterator(); i.hasNext(); ) { ConnectionEventListener cel = (ConnectionEventListener)i.next(); try { cel.connectionErrorOccurred(ce); } catch (Throwable ignored) { } } } void checkDestroyedResourceException() throws ResourceException { if (destroyed.get()) throw new ResourceException("Already destroyed " + this); } void checkDestroyedXAException() throws XAException { if (destroyed.get()) { XAException xaex = new XAException("Already destroyed " + this); xaex.errorCode = XAException.XAER_PROTO; throw xaex; } } public synchronized String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("TestManagedConnection#").append(id); buffer.append("{"); buffer.append("xid=").append(currentXid); buffer.append(" destroyed=").append(destroyed.get()); buffer.append("}"); return buffer.toString(); } public void doSleep(long sleep) { boolean interrupted = false; try { Thread.sleep(sleep); } catch (InterruptedException e) { interrupted = true; } if (interrupted) Thread.currentThread().interrupt(); } public class GlobalXID { byte[] gid; int hashCode; String toString; public GlobalXID(Xid xid) { gid = xid.getGlobalTransactionId(); for (int i = 0; i < gid.length; ++i) hashCode += 37 * gid[i]; toString = new String(gid).trim(); } public int hashCode() { return hashCode; } public String toString() { return toString; } public boolean equals(Object obj) { if (obj == null || obj instanceof GlobalXID == false) return false; GlobalXID other = (GlobalXID) obj; return toString.equals(other.toString); } } }