/**
* 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.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.LoggerFactory;
import org.objectweb.util.monolog.api.Loggable;
import org.objectweb.util.monolog.Monolog;
import org.objectweb.util.monolog.wrapper.printwriter.LoggerImpl;
import org.objectweb.speedo.api.Debug;
import org.objectweb.speedo.api.SpeedoProperties;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.pm.api.POManagerItf;
import org.objectweb.speedo.pm.api.POManagerFactoryItf;
import java.io.PrintWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.FileNotFoundException;
import java.util.Set;
import java.util.Properties;
import java.util.HashMap;
import java.util.Iterator;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
import javax.transaction.TransactionManager;
import javax.transaction.xa.Xid;
import javax.transaction.xa.XAException;
import javax.jdo.JDOHelper;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* @author P. Dechamboux
*/
public abstract class SpeedoManagedConnectionFactory
implements ManagedConnectionFactory,
SpeedoAttributeController {
private final static String[] DEFAULT_JNDI_NAMES = {
"javax.transaction.UserTransaction", //JOnAS
"javax.transaction.TransactionManager", //WebLogic
"java:/TransactionManager", //JBoss
"jta/usertransaction", //WebSphere
"java:comp/UserTransaction" //Orion
};
/**
* The logger into which traces about SpeedoManagedConnectionFactory are
* produced.
*/
private Logger logger;
/**
* It is assumed that only one ConnectionFactory is actually created by
* a ManagedConnectionFactory.
*/
protected SpeedoConnectionFactory connectionFactory;
/**
* For creating all loggers related to JDO and its adapter.
*/
private LoggerFactory loggerFactory = null;
/**
* The factory for managing JDO transaction contexts.
*/
public POManagerFactoryItf pmf = null;
/**
* The name of the property file of the PersistenceManagerFactory associated
* with this JDO connector.
*/
private String propertiesFileName = null;
/**
* The factory for managing JDO transaction contexts.
*/
private ConnectionManager connectionManager = null;
private PrintWriter printWriter = null;
public boolean started = false;
/**
* the JNDI Name of the transaction manager
*/
private String tmName = null;
protected TransactionManager tm = null;
/**
* The hashed structure that stores JdoTransaction that have been associated
* with a particular XID.
*/
private HashMap xid2xac = new HashMap();
/**
* This abstract method is used to create a ConnectionFactory. It should be
* implemented by the ManagedConnectionFactory associated with the actual
* implementation of the JCA driver (either for JDO or for EJB).
*/
protected abstract SpeedoConnectionFactory createConnectionFactory(Logger l,
SpeedoManagedConnectionFactory fmcf,
ConnectionManager cm,
byte transactionMode) throws ResourceException;
/**
* Lookup in JNDI the transaction manager under the name specified in
* parameter. The 'tm' variable is assigned if the transaction manager is
* found.
* @param name is the jndi name
* @return true if an instance implementing
* javax.transaction.TransactionManager is availlable in JNDI, otherwise
* false.
*/
private boolean lookupTM(String name) {
InitialContext ictx = null;
try {
ictx = new InitialContext();
Object o = ictx.lookup(name);
if (o == null) {
logger.log(BasicLevel.WARN,
"JNDI retrieves a null value for the name '" + name + "'.");
}
if (!(o instanceof TransactionManager)) {
logger.log(BasicLevel.WARN,
"JNDI retrieves an object which is not a javax.transaction.TransactionManager (JNDI name: " + name + "): " + o);
return false;
}
tm = (TransactionManager) o;
logger.log(BasicLevel.INFO, "The TransactionManager was found in JNDI with the name '" + name + "'.");
return true;
} catch (Exception e) {
logger.log(BasicLevel.WARN,
"Error when lookup the transaction manager in JNDI with the name '"
+ name + "'", e);
return false;
} finally {
if (ictx != null) {
try {
ictx.close();
} catch (NamingException e) {
}
}
}
}
/**
* Starts this SpeedoManagedConnectionFactory.
*/
public synchronized void start() throws ResourceException {
if (started) {
return;
}
//Logging initialisation
if (loggerFactory == null) {
if (Monolog.monologFactory == null) {
loggerFactory = Monolog.initialize();
} else {
loggerFactory = Monolog.monologFactory;
}
}
if (logger == null) {
logger = loggerFactory.getLogger("org.objectweb.speedo.jca");
}
//Load properties file of the JDO driver
if (pmf == null) {
Properties p = loadProperties();
if (p.get(SpeedoProperties.MANAGED) == null) {
//By default the transaction demarcation is done via the JTA API
p.put(SpeedoProperties.MANAGED, "true");
}
logger.log(BasicLevel.DEBUG, "Properties loaded:" + p);
findTransactionManager(p);
// In managed environnement the mapping structure must be created
// before the server lauching. Indeed some data supports do not
// accept to create data strucutre into a XA transaction.
String str = p.getProperty(SpeedoProperties.MAPPING_STRUCTURE);
if (str == null
|| !str.equals(SpeedoProperties.MAPPING_STRUCTURE_DN)) {
p.put(SpeedoProperties.MAPPING_STRUCTURE,
SpeedoProperties.MAPPING_STRUCTURE_DN);
logger.log(BasicLevel.WARN, "The mapping structure cannot be" +
" managed by Speedo into a managed environnement " +
"(XA transaction): "
+ SpeedoProperties.MAPPING_STRUCTURE + " is forced to "
+ SpeedoProperties.MAPPING_STRUCTURE_DN);
}
//the speedo property to define the trasaction mode within a j2ee context
byte txMode = getByteTxMode(p.getProperty(SpeedoProperties.TRANSACTION_MODE));
connectionFactory = createConnectionFactory(logger, this, connectionManager, txMode);
logger.log(BasicLevel.INFO, "ConnectionManager allocated");
//fetch the real PersistenceManagerFactory
try {
pmf = (POManagerFactoryItf) JDOHelper.getPersistenceManagerFactory(p);
} catch (Exception e) {
Exception ie = ExceptionHelper.getNested(e);
ResourceException re = new ResourceException("Impossible to instanciate Speedo: ");
logger.log(BasicLevel.ERROR, re.getMessage(), ie);
re.setLinkedException(ie);
throw re;
}
logger.log(BasicLevel.INFO, "SpeedoManagedConnectionFactory started");
}
started = true;
}
/**
* Loads the properties file from the classloader or from the file system.
*
* @param p is the Properties to fill.
* @throws ResourceException
*/
private Properties loadProperties() throws ResourceException {
if (propertiesFileName == null) {
throw new ResourceException(
"No name provided for the properties file of the associated PersistenceManagerFactory");
}
InputStream is = getClass().getClassLoader().getResourceAsStream(
propertiesFileName);
if (is == null) {
File f = new File(propertiesFileName);
try {
if (f.exists()) {
is = new FileInputStream(propertiesFileName);
logger.log(BasicLevel.DEBUG, "Properties file '"
+ propertiesFileName
+ "' found in the file system");
}
} catch (FileNotFoundException e) {
} finally {
if (is == null) {
throw new ResourceException(
"Unable to load properties file: "
+ propertiesFileName);
}
}
} else {
logger.log(BasicLevel.DEBUG, "Properties file '"
+ propertiesFileName + "' found in the classpath");
}
Properties p = new Properties();
try {
p.load(is);
} catch (IOException e) {
throw new ResourceException("Unable to load properties file: "
+ propertiesFileName);
}
return p;
}
/**
* Try to find the TransactionManager with:
* - the name specified in the RA configuration xml file
* - the name specified the properties of the JDO driver
* - names used in some application server
*
* @param p is the Speedo properties
* @return the reference to the TransactionManager
* @throw a ResourceException is the TransactionManager cannot be found
*/
private TransactionManager findTransactionManager(Properties p)
throws ResourceException {
if (tm != null) {
return tm;
}
if (tmName != null && lookupTM(tmName)) {
p.setProperty(SpeedoProperties.TM_NAME, tmName);
} else {
//lookup in the properties of JDO driver if the TM name is
// specified
String tmName2 = p.getProperty(SpeedoProperties.TM_NAME);
if (tmName2 == null || !lookupTM(tmName2)) {
logger.log(BasicLevel.DEBUG,
"Try to find the transaction manager with default JNDI names.");
int i = 0;
while (i < DEFAULT_JNDI_NAMES.length
&& !lookupTM(DEFAULT_JNDI_NAMES[i])) {
i++;
}
if (i < DEFAULT_JNDI_NAMES.length) {
p.setProperty(SpeedoProperties.TM_NAME,
DEFAULT_JNDI_NAMES[i]);
}
}
}
if (tm == null) {
throw new ResourceException(
"A javax.transaction.TransactionManager instance is required,"
+ " in order to register the JDO driver as a Synchronization on transaction"
+ (tmName == null ? "(No JNDI name specified)"
: "(Bad JNDI Name)"));
}
logger.log(BasicLevel.DEBUG, "JDOTransactionItf manager found: " + tm);
return tm;
}
protected void finalize() throws Throwable {
stop();
super.finalize();
}
/**
* Stops this SpeedoManagedConnectionFactory.
*/
public void stop() throws ResourceException {
pmf = null;
}
/**
* Delegates the creation of a Connection to the ConnectionFactory.
*/
public Object createConnection() throws ResourceException {
return connectionFactory.createConnection();
}
// --------------------- SpeedoXAContext Management ----------------------- //
/**
* Looks for a JdoTxContext associated with the particular transaction.
* @param xid The DTP transaction identifier.
*/
SpeedoXAContext getXAContext(Xid xid) {
SpeedoXAContext xac = (SpeedoXAContext) xid2xac.get(xid);
POManagerItf txc = null;
if (xac != null) {
txc = xac.pm;
}
if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG,
"Looking for the TxContext associated with XID (" + xid
+ "): " + txc);
return xac;
}
/**
* Creates a JdoTxContext and associates it with the given DTP transaction.
* @param xid The DTP transaction identifier.
*/
SpeedoXAContext createXAContext(Xid xid) {
if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
Object pm = pmf.lookup();
if (pm != null) {
logger.log(BasicLevel.DEBUG,
"Unbind the PM from the current thread: "
+ "\t-current pm=" + pm
+ "\t-xid=" + xid);
} else {
logger.log(BasicLevel.DEBUG,
"No PM to unbind from the current thread, xid=" + xid);
}
}
pmf.unbindPM();
SpeedoXAContext xac = new SpeedoXAContext(xid);
if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"Creates an SpeedoXAContext associated with the XID: " + xid);
}
xid2xac.put(xid, xac);
return xac;
}
/**
* Releases a DTP transaction context and its related resources.
* @param xid The DTP transaction identifier.
*/
SpeedoXAContext releaseXAContext(Xid xid, boolean mustExist) throws XAException {
SpeedoXAContext xac = (SpeedoXAContext) xid2xac.remove(xid);
if (xac == null) {
if (mustExist) {
String msg = "Impossible to release the SpeedoXAContext, xid=" + xid;
logger.log(BasicLevel.ERROR, msg);
throw new XAException(msg);
}
} else if (!xac.synchroRegistred && xac.pm != null) {
logger.log(BasicLevel.WARN, "Closing a persistenceManager because it has not been registered as a Synchronization on the transaction (pm="
+ xac.pm + "), xid: " + xid);
if (xac.pm.getSpeedoTransaction().isActive()) {
xac.pm.getSpeedoTransaction().rollback();
}
xac.pm.closePOManager();
} else if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Dissociates the JdoTxContext ("
+ xac.pm + ") associated with XID: " + xid);
}
return xac;
}
// IMPLEMENTATION OF METHODS FROM THE SpeedoAttributeController INTERFACE
/**
* Gives access to the name of the property file for initializing the
* underlying JDO implementation.
* @return The name of the property file.
*/
public String getPropertyFile() {
return propertiesFileName;
}
/**
* Assigns to this JDO connector the name of the property file for
* initializing the underlying JDO implementation.
* @param pf The name of the property file.
*/
public void setPropertyFile(String pf) {
propertiesFileName = pf;
}
public String getTransactionManagerJNDIName() {
return tmName;
}
public void setTransactionManagerJNDIName(String jndiname) throws ResourceException {
tmName = jndiname;
}
public void setTransactionManager(TransactionManager tm) {
this.tm = tm;
}
// IMPLEMENTATION OF METHODS FROM THE (cci)ManagedConnectionFactory INTERFACE
/**
* Creates a JDOConnectionFactory; yields the existing one if any.
* @param cm The ConnectionManager to be used by the created
* ConnectionFactory (may be null).
*/
public Object createConnectionFactory(ConnectionManager cm)
throws ResourceException {
if (!started) {
start();
}
connectionFactory.setConnectionManager(cm);
return connectionFactory;
}
/**
* Creates a JDOConnectionFactory; yields the existing one if any.
*/
public Object createConnectionFactory() throws ResourceException {
return createConnectionFactory(null);
}
/**
* Creates a new SpeedoManagedConnection.
*/
public ManagedConnection createManagedConnection(
Subject subject,
ConnectionRequestInfo info) throws ResourceException {
if (logger == null || pmf == null) {
start();
}
SpeedoManagedConnection jmc = new SpeedoManagedConnection(logger, this);
if (info != null) {
if (info instanceof SpeedoConnectionSpec) {
jmc.cri = (SpeedoConnectionSpec) info;
} else {
throw new ResourceException("Impossible to create a " +
"ManagedConnection with this kind of ConnectionRequestInfo: "
+ info);
}
}
return jmc;
}
/**
* No matching rules supported. Always yields the first element of the set
* if any.
*/
public ManagedConnection matchManagedConnections(
Set set,
Subject subject,
ConnectionRequestInfo info) throws ResourceException {
if (set.size() == 0)
return null;
Iterator it = set.iterator();
if (!it.hasNext()) {
return null;
}
SpeedoManagedConnection jmc = (SpeedoManagedConnection) it.next();
if (info != null) {
if (info instanceof SpeedoConnectionSpec) {
jmc.cri = (SpeedoConnectionSpec) info;
} else {
throw new ResourceException("Impossible to create a " +
"ManagedConnection with this kind of ConnectionRequestInfo: "
+ info);
}
}
return jmc;
}
/**
* If he given PrintWrtier is a Loggable implementation then the inner
* logger and the inner loggerFactory are used. Otherwise the a basic Logger
* implementation is used over the specified PrintWriter.
*/
public void setLogWriter(PrintWriter writer) throws ResourceException {
if (logger == null) {
if (writer instanceof Loggable) {
logger = ((Loggable) writer).getLogger();
loggerFactory = ((Loggable) writer).getLoggerFactory();
} else {
LoggerImpl li = new LoggerImpl(writer);
logger = li;
loggerFactory = li;
}
}
printWriter = writer;
}
/**
* Retrieves the printwriter used for the logging.
*/
public PrintWriter getLogWriter() throws ResourceException {
return printWriter;
}
//PRIVATE METHODS
private byte getByteTxMode(String mode) {
if (mode == null || mode.length() == 0 || mode.equals(SpeedoProperties.TRANSACTION_MODE_NORMAL)) {
return SpeedoProperties.TRANSACTION_BMODE_NORMAL;
}
if (mode.equals(SpeedoProperties.TRANSACTION_MODE_REQUIRED)) {
return SpeedoProperties.TRANSACTION_BMODE_REQUIRED;
}
return SpeedoProperties.TRANSACTION_BMODE_UT;
}
}