/**
* perseus/connector: this is an implementation of some JCA-related technologies
* (resource adapters and managers) for the ObjectWeb consortium.
* Copyright (C) 2001-2004 France Telecom R&D
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Contact: speedo@objectweb.org
*
*/
package org.objectweb.speedo.jca;
import org.objectweb.speedo.api.Debug;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.pm.api.POManagerItf;
import org.objectweb.speedo.workingset.api.TransactionItf;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import javax.jdo.JDOFatalException;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.spi.*;
import javax.security.auth.Subject;
import javax.transaction.RollbackException;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author P. Dechamboux
*/
public class SpeedoManagedConnection
implements ManagedConnection,
javax.resource.spi.LocalTransaction,
javax.resource.cci.LocalTransaction,
XAResource,
ManagedConnectionMetaData {
public final static String EIS_PRODUCT_NAME = "Speedo Resource Adapter";
public final static String EIS_PRODUCT_VERSION = "1.0";
public final static String USER_NAME = "No user name needed to use the Speedo driver";
/**
* The logger into which traces about SpeedoManagedConnection are produced.
*/
private Logger logger;
/**
* The ManagedConnectionFactory that have requested the allocation of this
* ManagedConnection.
*/
private SpeedoManagedConnectionFactory mcf;
/**
* The logical Connections associated with this ManagedConnection. Always
* at least one connection except when the MC is in the pool.
*/
private ArrayList logicalConnections = new ArrayList(1);
/**
* The ConnectionEventListener list associated with this ManagedConnection.
*/
private ArrayList listeners = null;
/**
* It is set if the ManagedConnection execute within a LocalTransaction.
*/
private POManagerItf localTransactionPM = null;
/**
* It is the current SpeedoXAContext. It is not null when the ManagedConnection
* is used in a XA transaction through the XAResource interface.
*/
public SpeedoXAContext xac;
/**
* It is set if Speedo operation occurs outside any explicit (LocalTransaction)
* or implicit (XAResource) transaction context.
*/
private POManagerItf defaultPM = null;
protected SpeedoConnectionSpec cri = null;
/**
* Constructs a SpeedoManagedConnection.
* @param el The logger into which to produce ManagedConnection-related
* traces.
* @param fmcf The ManagedConnectionFactory that has requested the
* ManagedConnection creation.
*/
SpeedoManagedConnection(Logger el, SpeedoManagedConnectionFactory fmcf) {
logger = el;
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG,
"Constructs a new SpeedoManagedConnection");
mcf = fmcf;
listeners = null;
}
/**
* Retrieves the right POManagerItf in according the managed connection
* state (in a local transaction, in a XA transaction or out of transaction)
*
* @return a POManagerItf instance (never null).
*/
public POManagerItf getPOManager() {
if (localTransactionPM != null) {
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "return localTransactionPM: "
+ localTransactionPM);
}
return localTransactionPM;
}
if (xac != null) {
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"xac.status= " + xac.status
+ "xac.xid= " + xac.xid);
}
if (xac.status != SpeedoXAContext.STARTED) {
throw new JDOFatalException(
"XA context should have been started here: xac.status= "
+ xac.status + "xac.xid= " + xac.xid);
}
if (xac.pm == null) {
synchronized(xac) {
if (xac.pm == null) {
xac.pm = (POManagerItf) mcf.pmf.getPOManager();
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"Creates a PersistenceManager (" + xac.pm
+ ") associated to the XID: " + xac.xid);
}
}
}
}
if (!xac.synchroRegistred) {
//Register the PersistenceManager on the first use
try {
registerSynchronization();
} catch (XAException ex) {
JDOFatalException e = new JDOFatalException(
"Problem while registering a synchronization.", ex);
if (logger != null) {
logger.log(BasicLevel.ERROR, e.getMessage(), ex);
}
throw e;
}
}
return xac.pm;
}
if (defaultPM == null) {
defaultPM = (POManagerItf) mcf.pmf.getPOManager();
}
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"return the default persistence manager: " + defaultPM);
}
return defaultPM;
}
/**
* find the SpeedoXAContext matching an Xid.
* @param flag indicates expecting behavior:<UL>
* <LI>0: create the SpeedoXAContext if it does not already exist</LI>
* <LI>1: return an existing SpeedoXAContext</LI>
* <LI>otherwise: return an SpeedoXAContext or null it is not found</LI>
* </UL>
*/
public SpeedoXAContext getXAContext(Xid xid, int flag)
throws XAException {
if (mcf == null) {
//The MC has been destroyed, and the contexts has beean rolled back
return null;
}
SpeedoXAContext _xac = mcf.getXAContext(xid);
if (_xac == null) {
if (flag == 0) { //create it
try {
_xac = mcf.createXAContext(xid);
} catch (RuntimeException fe) {
Exception ie = ExceptionHelper.getNested(fe);
XAException e = new XAException(
"Error during the creation of the XA context with the xid: "
+ xid + ", error message:" + ie.getMessage());
if (logger != null) {
logger.log(BasicLevel.ERROR, ie.getMessage(), ie);
}
throw e;
}
} else if (flag == 1) { // throw an exception
XAException e = new XAException("No XA context found for the xid=" + xid);
if (logger != null) {
logger.log(BasicLevel.ERROR, e.getMessage(), e);
}
throw e;
} // else return null
}
return _xac;
}
/**
* Registers the POManagerItf of the current SpeedoXAContext to the current
* transaction, if it is not already done.
*
* @throws XAException if the registering is not possible:
* - TM not availlable
* - Pr
*/
public synchronized void registerSynchronization() throws XAException {
if (xac.synchroRegistred) {
return;
}
TransactionItf tx = xac.pm.getSpeedoTransaction();
if (tx.isActive() && logger != null) {
logger.log(BasicLevel.WARN, "Speedo TransactionItf started and the " +
"PersistenceManager is not marked as registered to " +
"the XA transaction");
}
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "registerSynchronization");
}
TransactionManager tm = mcf.tm;
if (tm == null) {
String msg = "Impossible to register the Speedo " +
"container as synchronization on the current transaction: " +
(tm == null
? "The transaction manager has not found with the JNDI name: "
+ mcf.getTransactionManagerJNDIName()
: "The TransactionItf object of the Speedo implementation does not implement javax.transaction.Synchronization");
if (logger != null) {
logger.log(BasicLevel.ERROR, msg);
}
throw new XAException(msg);
}
try {
tm.getTransaction().registerSynchronization(xac.pm);
if (!tx.isActive()) {
tx.begin();
}
xac.synchroRegistred = true;
} catch (RollbackException e) {
//The JDO container cannot be registered then the transaction
// must be rolledback
if (tx.isActive()) {
tx.rollback();
}
} catch (Exception e) {
String msg = "Impossible to register the Speedo " +
"container as synchronization on the current" +
" transaction: " + e.getMessage();
if (logger != null) {
logger.log(BasicLevel.ERROR, msg, e);
}
throw new XAException(msg);
}
}
/**
* Specifies if this ManagedConnection still has an active LocalTransaction.
* @return true if a LocalTransaction is still active.
*/
public boolean localTransactionTerminated() {
return localTransactionPM == null;
}
/**
* Dissociates a Connection from the ones that are associated to this
* ManagedConnection.
* @param conn The Connection to be dissociated.
*/
public synchronized void dissociateConnection(Object conn) throws ResourceException {
int ind = logicalConnections.indexOf(conn);
if (ind == -1) {
throw new ResourceException("Speedo Connector: cannot dissociate an unknown Connection:"
+ conn + "\n among " + logicalConnections);
}
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Dissociates Connection (" + ind + "): " + conn);
}
logicalConnections.remove(ind);
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(conn);
if (listeners != null) {
synchronized(this) {
for (int i=0; i<listeners.size();) {
ConnectionEventListener cel = (ConnectionEventListener) listeners.get(i);
cel.connectionClosed(ce);
if (i<listeners.size() && cel == listeners.get(i)) {
i++;
}// else the listeners has been removed
}
}
}
}
// IMPLEMENTATION OF METHODS FROM THE (cci)ManagedConnectionMetaData INTERFACE
public String getEISProductName() throws ResourceException {
return EIS_PRODUCT_NAME;
}
public String getEISProductVersion() throws ResourceException {
return EIS_PRODUCT_VERSION;
}
public int getMaxConnections() throws ResourceException {
// TODO: analysis of ra.xml values
return 10;
}
public String getUserName() throws ResourceException {
return USER_NAME;
}
// IMPLEMENTATION OF METHODS FROM THE (cci)ManagedConnection INTERFACE //
//---------------------------------------------------------------------//
/**
* Delegates the creation of a Connection to the ConnectionFactory.
* "subject" and "info" parameters are ignored.
*/
public Object getConnection(Subject subject, ConnectionRequestInfo info)
throws ResourceException {
SpeedoConnection rac = (SpeedoConnection) mcf.connectionFactory.createConnection();
rac.initialize(this);
associateConnection(rac);
return rac;
}
/**
* Cleans up the connection.
*/
public void cleanup() throws ResourceException {
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG, "Cleans up ManagedConnection");
int s = (logicalConnections == null ? 0 : logicalConnections.size());
if (s > 0) {
s -=1;
for(int i=s; i>=0; i--) {
((Connection) logicalConnections.get(i)).close();
}
}
if (localTransactionPM != null) {
localTransactionPM.closePOManager();
localTransactionPM = null;
}
if (defaultPM != null) {
defaultPM.closePOManager();
defaultPM = null;
}
//The specification requires to keep the XAResource associated to MC
}
/**
* Called when ManagedConnection is removed.
*/
public void destroy() throws ResourceException {
cleanup();
logger = null;
mcf = null;
logicalConnections = null;
listeners = null;
localTransactionPM = null;
}
/**
* Associates a new Connection to this ManagedConnection. Nothing is done
* if it has already been associated (enforces no duplicate).
*/
public synchronized void associateConnection(Object o) throws ResourceException {
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG, "Associates Connection: " + o + " to the MC " + this);
if (!logicalConnections.contains(o))
logicalConnections.add(o);
else if (logger != null)
logger.log(BasicLevel.WARN, "Connection: " + o
+ "has already been associated: ignored!");
}
/**
* Adds a listener to the listeners list if it has not
* already been done. This means that duplicates are ignored.
* It first creates this list if needed (creation at first add).
*/
public synchronized void addConnectionEventListener(
ConnectionEventListener listener) {
if (listeners == null)
listeners = new ArrayList();
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG, "Associates ConnectionEventListener: "
+ listener);
if (!listeners.contains(listener))
listeners.add(listener);
else if (logger != null)
logger.log(BasicLevel.WARN, "ConnectionEventListener: " + listener
+ "has already been associated: ignored!");
}
/**
* Removes a listener from the listeners list. If the list does
* not eist (null), or if this listener does not belong to this list,
* nothing is done.
*/
public synchronized void removeConnectionEventListener(
ConnectionEventListener listener) {
if (listeners == null) {
if (logger != null) {
logger.log(BasicLevel.WARN,
"No associated ConnectionEventListener: ignored!");
}
return;
}
int ind = listeners.indexOf(listener);
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG,
"Index within the listeners: " + ind);
if (ind == -1 && logger != null)
logger.log(BasicLevel.WARN, "ConnectionEventListener" + listener
+ "not associated here: ignored!");
else
listeners.remove(ind);
}
/**
* Retrieves an XA resource. Switches running mode to XA.
*/
public synchronized XAResource getXAResource() throws ResourceException {
if (logger != null) {
logger.log(BasicLevel.DEBUG, "getXAResource()");
}
if (localTransactionPM != null) {
throw new ResourceException(
"Try switching to XA mode while running a LocalTransaction.");
}
if (defaultPM != null) {
defaultPM.closePOManager();
defaultPM = null;
}
return this;
}
public LocalTransaction getLocalTransaction() throws ResourceException {
return this;
}
public ManagedConnectionMetaData getMetaData() throws ResourceException {
return this;
}
/**
* Not supported yet.
*/
public void setLogWriter(PrintWriter writer) throws ResourceException {
throw new ResourceException("Speedo Connector: logging to PrintWriter not supported yet.");
}
/**
* Not supported yet.
*/
public PrintWriter getLogWriter() throws ResourceException {
throw new ResourceException("Speedo Connector: logging to PrintWriter not supported yet.");
}
// IMPLEMENTATION OF METHODS FROM THE (cci)LocalTransaction INTERFACE //
//--------------------------------------------------------------------//
/**
* Begins the LocalTransaction if it has not already been started.
*/
public synchronized void begin() throws ResourceException {
if (localTransactionPM != null) {
throw new ResourceException("Speedo Connector: a LocalTransaction has already begun.");
}
try {
if (defaultPM != null) {
defaultPM.closePOManager();
defaultPM = null;
}
localTransactionPM = (POManagerItf)
mcf.pmf.getPOManager();
localTransactionPM.getSpeedoTransaction().begin();
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG, "LocalTransaction begin - txContext: "
+ localTransactionPM);
if (xac != null && logger != null)
logger.log(BasicLevel.WARN,
"Begins a LocalTransaction on a ManagedConnection that runs in XA mode!");
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_STARTED);
if (listeners != null) {
synchronized(this) {
for (Iterator it = listeners.iterator(); it.hasNext();) {
((ConnectionEventListener) it.next()).localTransactionStarted(ce);
}
}
}
} catch (RuntimeException fe) {
ResourceException re = new ResourceException(
"Speedo Connector: cannot begin LocalTransaction [nested exception].");
re.setLinkedException(fe);
throw re;
}
}
/**
* Commits the LocalTransaction if it is active.
*/
public synchronized void commit() throws ResourceException {
if (localTransactionPM == null)
throw new ResourceException("Speedo Connector: no LocalTransaction has been begun.");
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG, "LocalTransaction commit - txContext: "
+ localTransactionPM);
try {
localTransactionPM.getSpeedoTransaction().commit();
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
if (listeners != null) {
synchronized(this) {
for (Iterator it = listeners.iterator(); it.hasNext();) {
((ConnectionEventListener) it.next()).localTransactionCommitted(ce);
}
}
}
} catch (RuntimeException fe) {
ResourceException re = new ResourceException(
"Speedo Connector: cannot commit LocalTransaction [nested exception].");
re.setLinkedException(fe);
throw re;
} finally {
localTransactionPM.closePOManager();
localTransactionPM = null;
}
}
/**
* Rollbacks the LocalTransaction if it is active.
*/
public synchronized void rollback() throws ResourceException {
if (localTransactionPM == null)
throw new ResourceException("Speedo Connector: no LocalTransaction has been begun.");
if (Debug.ON && logger != null)
logger.log(BasicLevel.DEBUG, "LocalTransaction rollback - txContext: "
+ localTransactionPM);
try {
localTransactionPM.getSpeedoTransaction().rollback();
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK);
if (listeners != null) {
synchronized(this) {
for (Iterator it = listeners.iterator(); it.hasNext();) {
((ConnectionEventListener) it.next()).localTransactionRolledback(ce);
}
}
}
} catch (RuntimeException fe) {
ResourceException re = new ResourceException(
"Speedo Connector: cannot rollback LocalTransaction [nested exception].");
re.setLinkedException(fe);
throw re;
} finally {
localTransactionPM.closePOManager();
localTransactionPM = null;
}
}
// IMPLEMENTATION OF METHODS FROM THE (jta)XAResource INTERFACE //
//--------------------------------------------------------------//
/**
* Assigns an actual Speedo transaction context to the XAResource
* within the give DTP context.
*/
public void start(Xid xid, int i) throws XAException {
try {
switch(i) {
case XAResource.TMRESUME:
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "start(" + xid + ", TMRESUME)");
}
xac = getXAContext(xid, 1);
if (xac.pm != null) {
mcf.pmf.bindPM2Thread(xac.pm);
}
break;
case XAResource.TMJOIN:
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "start(" + xid + ", TMJOIN)");
}
xac = getXAContext(xid, 1);
break;
case XAResource.TMNOFLAGS:
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "start(" + xid + ", TMNOFLAGS)");
}
xac = getXAContext(xid, 0);
break;
default:
String msg = "Unexpected flag: " + i;
if (logger != null) {
logger.log(BasicLevel.ERROR, msg);
}
throw new XAException(msg);
}
xac.status = SpeedoXAContext.STARTED;
} catch (XAException e) {
if (logger != null) {
logger.log(BasicLevel.ERROR,
"Error in the Jdo XAResource starting (" + i + ")", e);
}
throw e;
} catch (RuntimeException e) {
if (logger != null) {
logger.log(BasicLevel.ERROR,
"Error in the Jdo XAResource starting (" + i + ")", e);
}
throw e;
}
}
/**
* Unbind this instance to the PersistenceManager.
*/
public void end(Xid xid, int i) throws XAException {
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "xid=" + xid);
}
SpeedoXAContext _xac = getXAContext(xid, 2);
if (_xac != null) {
_xac.status = SpeedoXAContext.ENDED;
if (xac == _xac) {
xac = null;
}
} // else the XAcontext has been committed or rolled back before the end
}
/**
* Used by JTA in order to verify that it has not already registered a
* XAResource to manage this transaction context from this RM. If it is the
* case, both XAResource are warned of this fact (endWithNoSameRM = false).
* This means that these XAResource will be released at "end" time.
* @param resource The resource to be compared against this one wrt RM.
*/
public boolean isSameRM(XAResource resource) throws XAException {
boolean res = (resource instanceof SpeedoManagedConnection)
&& ((SpeedoManagedConnection) resource).mcf == mcf;
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "isSameRM(" + resource + "): " + res);
}
return res;
}
// Management of transaction termination: prepare, commit, rollback
/**
* Prepares the underlying JdoTxContext (prepare phase of the 2PC).
*/
public int prepare(Xid xid) throws XAException {
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "prepare(" + xid + ")");
}
SpeedoXAContext _xac = getXAContext(xid, 1);
if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
if (_xac.pm == null) {
logger.log(BasicLevel.DEBUG, "prepare(" + xid
+ "): no PersistenceManager");
} else {
logger.log(BasicLevel.DEBUG, "prepare(" + xid
+ "): speedo Tx status="
+ _xac.pm.getSpeedoTransaction().getStatus());
}
}
return XAResource.XA_OK;
}
/**
* Unbind the PersistenceManager to the xid. The real commit is done by the
* PersistenceManager registered as a Synchronization.
*/
public void commit(Xid xid, boolean b) throws XAException {
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "commit(" + xid + ", 1PC: " + b + ")");
}
SpeedoXAContext _xac = mcf.releaseXAContext(xid, true);
if (xac == _xac) {
this.xac = null;
}
}
/**
* Unbind the PersistenceManager to the xid. The real commit is done by the
* PersistenceManager registered as a Synchronization.
*/
public void rollback(Xid xid) throws XAException {
if (Debug.ON && logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "rollback(" + xid + ")");
}
SpeedoXAContext _xac = mcf.releaseXAContext(xid, true);
if (xac == _xac) {
this.xac = null;
}
}
public void forget(Xid xid) throws XAException {
SpeedoXAContext _xac = mcf.releaseXAContext(xid, false);
if (xac == _xac) {
this.xac = null;
}
}
/**
* Gets the Xid of distributed transactions to be recovered from the Speedo
* manager.
*/
public Xid[] recover(int i) throws XAException {
return new Xid[0];
}
public int getTransactionTimeout() throws XAException {
return 0;
}
public boolean setTransactionTimeout(int i) throws XAException {
return false;
}
}