[jboss-svn-commits] JBL Code SVN: r29929 - in labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts: tomcat and 1 other directory.

jboss-svn-commits at lists.jboss.org jboss-svn-commits at lists.jboss.org
Tue Nov 3 13:21:16 EST 2009


Author: whitingjr
Date: 2009-11-03 13:21:16 -0500 (Tue, 03 Nov 2009)
New Revision: 29929

Added:
   labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/
   labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/TransactionalResourceFactory.java
   labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/XADataSourceWrapper.java
Log:
Pulled in classes to integrate TransactionalDriver so Connections are associtated with global transactions.

Added: labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/TransactionalResourceFactory.java
===================================================================
--- labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/TransactionalResourceFactory.java	                        (rev 0)
+++ labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/TransactionalResourceFactory.java	2009-11-03 18:21:16 UTC (rev 29929)
@@ -0,0 +1,221 @@
+/*
+ * 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 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) 2008,
+ * @author JBoss Inc.
+ */
+
+package org.jboss.jbossts.tomcat;
+
+import javax.naming.spi.ObjectFactory;
+
+import java.util.Hashtable;
+import java.util.*;
+import java.lang.reflect.Method;
+import javax.naming.Name;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.RefAddr;
+import javax.sql.XADataSource;
+
+
+/**
+ * TransactionalResourceFactory instances are the integration point to Tomcat 6.
+ * By configuring a Resource using this as the factory, we can expose a
+ * db driver's native XADataSource to tomcat via the TransactionalDriver, thereby
+ * ensuring connections are enlisted in the transaction correctly and that
+ * recovery works.
+ * <p/>
+ * Usually wired up in Tomcat 6 via an entry
+ * in the <GlobalNamingResources> section of conf/server.xml
+ * Don't put it in a webapps local META-INF/context.xml directly, it needs the
+ * server's classloader and global JNDI for recovery to work.
+ * <p/>
+ * <Resource name="jdbc/TestDBGlobal" auth="Container"
+ * type="javax.sql.DataSource"
+ * factory="org.jboss.jbossts.tomcat.TransactionalResourceFactory"
+ * XADataSourceImpl="oracle.jdbc.xa.client.OracleXADataSource"
+ * xa.setUser="username"
+ * xa.setPassword="password"
+ * xa.setURL="jdbc:oracle:thin:@hostname:1521:SID"
+ * />
+ *
+ * @author Jonathan Halliday jonathan.halliday at redhat.com
+ * @version $Id$
+ * @see http://tomcat.apache.org/tomcat-6.0-doc/config/globalresources.html
+ * @since 2008-05
+ */
+public class TransactionalResourceFactory implements ObjectFactory
+{
+    /*
+        Implementation Note: many methods here do kludgy things to integrate with tomcat.
+        They are protected so that subclasses can override to use alternative kludges if desired.
+     */
+
+
+    /**
+     * Create a new XADataSource instance.
+     *
+     * @param obj The reference object describing the DataSource configuration
+     */
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable environment) throws Exception
+    {
+        Reference ref = (Reference) obj;
+        Enumeration addrs = ref.getAll();
+        HashMap<String, String> params = new HashMap();
+
+        System.out.println("TransactionalResourceFactory ");
+
+        // convert the Addresses into easier to handle String key/values
+        while (addrs.hasMoreElements())
+        {
+            RefAddr addr = (RefAddr) addrs.nextElement();
+            String attrName = addr.getType();
+            String attrValue = (String) addr.getContent();
+            params.put(attrName, attrValue);
+        }
+
+        String classname = params.get("XADataSourceImpl");
+        if (classname == null)
+        {
+            throw new NamingException("No XADataSourceImpl value specified");
+        }
+
+        // instantiate the underlying implementation and configure it with the supplied params
+        XADataSource xaDataSource = loadXADataSource(classname);
+        processParameters(xaDataSource, params);
+
+        // instantiate and return a wrapper that will intercept subsequent calls to the object.
+        String fullJNDIName = convertName(name.toString());
+        return new XADataSourceWrapper(fullJNDIName, xaDataSource);
+    }
+
+    /**
+     * The name we are provided with by tomcat is the last part of the value in the xml attribute
+     * i.e if name="jdbc/TestDB" in conf/server.xml then name here = "TestDB".
+     * What we need to pass down to the arjuna code is a fully qualified name, so we prefix it here.
+     * This will break if the actual prefix in the xml is something other than "java:/jdbc/".
+     * TODO: figure out if we can grab the fully qualified name from tomcat somehow.
+     *
+     * @param name
+     * @return
+     */
+    protected String convertName(String name)
+    {
+        return "java:/jdbc/" + name;
+    }
+
+    /**
+     * Take an unconfigured XADataSource and configure it
+     * according to the supplied parameters.
+     * <p/>
+     * Since each vendor's XADataSource implementation has a different API,
+     * this is done via. reflection in the following manner:
+     * Parameters with names beginning "xa." have this prefix stripped off their name,
+     * the remainder of which is regarded as a method name on the XADataSource.
+     * The value is passed though to the method call unmodified.
+     * e.g. "xa.setUsername"=>"myName"  maps to xaDataSource.setUsername("myName");
+     *
+     * @param xaDataSource
+     * @param params
+     * @throws NamingException
+     */
+    protected void processParameters(XADataSource xaDataSource, Map<String, String> params) throws NamingException
+    {
+        for (String key : params.keySet())
+        {
+            if ("factory".equals(key) || "scope".equals(key) || "auth".equals(key) || "XADataSourceImpl".equals(key))
+            {
+                continue;
+            }
+
+            if (!key.startsWith("xa."))
+            {
+                // TODO log warning of unknown param?
+                continue;
+            }
+
+            callMethod(xaDataSource, key.substring(3), params.get(key));
+        }
+    }
+
+    /**
+     * Use reflection to call the named method on an xaDataSource implementation.
+     * Note: subclass and override this if you need handling of non-String values.
+     *
+     * @param xaDataSource
+     * @param name
+     * @param value
+     * @throws NamingException
+     */
+    protected void callMethod(XADataSource xaDataSource, String name, String value) throws NamingException
+    {
+        try
+        {
+            Method method = xaDataSource.getClass().getMethod(name, new Class[]{java.lang.String.class});
+            method.invoke(xaDataSource, value);
+        }
+        catch (Exception e)
+        {
+            NamingException ex = new NamingException("Unable to invoke " + name + " on " + xaDataSource.getClass().getName());
+            ex.initCause(e);
+            throw ex;
+        }
+    }
+
+    /**
+     * Load and instantiate the given XADataSource implementation class.
+     *
+     * @param classname
+     * @return
+     * @throws NamingException
+     */
+    protected XADataSource loadXADataSource(String classname) throws NamingException
+    {
+
+        Class clazz = null;
+
+        try
+        {
+            clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
+        }
+        catch (Exception e)
+        {
+            NamingException ex = new NamingException("Unable to load " + classname);
+            ex.initCause(e);
+            throw ex;
+        }
+
+        XADataSource xaDataSource = null;
+
+        try
+        {
+            xaDataSource = (XADataSource) clazz.newInstance();
+        }
+        catch (Exception e)
+        {
+            NamingException ex = new NamingException("Unable to instantiate " + classname);
+            ex.initCause(e);
+            throw ex;
+        }
+
+        return xaDataSource;
+    }
+}
+

Added: labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/XADataSourceWrapper.java
===================================================================
--- labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/XADataSourceWrapper.java	                        (rev 0)
+++ labs/jbosstm/workspace/whitingjr/trunk/performance/src/main/java/org/jboss/jbossts/tomcat/XADataSourceWrapper.java	2009-11-03 18:21:16 UTC (rev 29929)
@@ -0,0 +1,229 @@
+/*
+ * 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 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) 2008,
+ * @author JBoss Inc.
+ */
+
+package org.jboss.jbossts.tomcat;
+
+import javax.sql.XADataSource;
+import javax.sql.XAConnection;
+import javax.sql.DataSource;
+import javax.naming.Context;
+import java.sql.SQLException;
+import java.sql.Connection;
+import java.io.PrintWriter;
+import java.util.Properties;
+
+import com.arjuna.ats.jdbc.TransactionalDriver;
+import com.arjuna.ats.jdbc.common.jdbcPropertyManager;
+import com.arjuna.common.util.propertyservice.PropertyManager;
+
+
+/**
+ * This class provides a JNDI DataSource based approach to
+ * management of transaction aware database connections.
+ * Its got some vaguely tomcat6 specific JNDI weirdness in it,
+ * so don't expect it to work in other environments.
+ * <p/>
+ * This class serves a dual purpose. To web applications that look it up via
+ * their comp/env JNDI, it's a regular DataSource on which they can call getConnection().
+ * To JBossTS TransactionalDriver and recovery code that looks it up via
+ * the tomcat server's global JNDI context,
+ * it's a XADataSource from which they can obtain a XAResource.
+ * Hence it implements both DataSource and XADataSource.
+ *
+ * @author Jonathan Halliday jonathan.halliday at redhat.com
+ * @version $Id$
+ * @see http://tomcat.apache.org/tomcat-6.0-doc/config/globalresources.html
+ * @since 2008-05
+ */
+public class XADataSourceWrapper implements XADataSource, DataSource
+{
+    private XADataSource _theXADataSource;
+    public final TransactionalDriver theTransactionalDriver = new TransactionalDriver();
+    protected String _name;
+
+    /**
+     * Create a wrapper around the provided XADataSource implementation,
+     * which should be registered in tomcat's global JNDI with the specified name.
+     * Note: the registration is not done here, it's someone elses problem.
+     * See TransactionalResourceFactory for example usage.
+     *
+     * @param name          should be the fully qualifed JNDI name of the XADataSource, in
+     *                      tomcat's global JNDI, not a webapp specific JNDI context.
+     * @param theDataSource
+     */
+    public XADataSourceWrapper(String name, XADataSource theDataSource)
+    {
+        _theXADataSource = theDataSource;
+        _name = name;
+    }
+
+    /**
+     * Obtain a direct reference to the wrapped object. This is not
+     * recommended but may be necessary to e.g. call vendor specific methods.
+     *
+     * @return
+     */
+    public XADataSource getUnwrappedXADataSource()
+    {
+        return _theXADataSource;
+    }
+
+    ///////////////////////
+
+    // Implementation of the DataSource API is done by reusing the arjuna
+    // TransactionalDriver. Its already got all the smarts for checking tx
+    // context, enlisting resources etc so we just delegate to it.
+    // All we need is some fudging to make the JNDI name stuff behave.
+
+    /**
+     * Obtain a connection to the database.
+     * Note: Pooling behaviour depends on the vendor's underlying XADataSource implementation.
+     *
+     * @return
+     * @throws SQLException
+     */
+    public Connection getConnection() throws SQLException
+    {
+        String url = TransactionalDriver.arjunaDriver + _name;
+        // although we are not setting any properties, the driver will barf if we pass 'null'.
+        Properties properties = new Properties();
+        return getTransactionalConnection(url, properties);
+    }
+
+    /**
+     * Obtain a connection to the database using the supplied authentication credentials.
+     *
+     * @param username
+     * @param password
+     * @return
+     * @throws SQLException
+     */
+    public Connection getConnection(String username, String password) throws SQLException
+    {
+        String url = TransactionalDriver.arjunaDriver + _name;
+        Properties properties = new Properties();
+        properties.setProperty(TransactionalDriver.userName, username);
+        properties.setProperty(TransactionalDriver.password, password);
+        return getTransactionalConnection(url, properties);
+    }
+
+    /*
+     * This is where most of the tomcat specific weirdness resides. You probably
+     * want to subclass and override this method for reuse in env other than tomcat.
+     */
+    protected Connection getTransactionalConnection(String url, Properties properties) throws SQLException
+    {
+
+        // For ref, the url the TransactionalDriver expects is the arjuna driver's
+        // special prefix followed by a JNDI name.
+        // via ConnectionImple the IndirectRecoverableConnection.createDataSource method
+        // attempts to look it up in JNDI. There are two problems with this:
+
+        //  First problem,
+        // it always calls InitialContext(env), never InitalContext().
+        // This we work around by copying into the arjuna config, the system
+        // properties it needs to populate the env:
+
+        // caution: ensure the tx lifecycle listener is configured in tomcat or there will be a
+        // possible race here, as recovery needs these properties too and may start first
+        //jdbcPropertyManager.propertyManager.setProperty("Context.INITIAL_CONTEXT_FACTORY", properties.getProperty(Context.INITIAL_CONTEXT_FACTORY));
+        //jdbcPropertyManager.propertyManager.setProperty("Context.URL_PKG_PREFIXES", properties.getProperty(Context.URL_PKG_PREFIXES));
+
+        // Second problem: this method has almost certainly been called by a webapp,
+        // which has its own InitialContext. Whilst the datasource is in there, we
+        // can't be certain it's under the same name as its global name. We also
+        // don't want any hassle with the webapp classloader, which may go away
+        // whilst recovery is still active. Hence we need to temporarily set things
+        // such that we use the server's global InitialContext for the lookup
+        // instead of the webapp one. Tomcat figures out the InitialContext based
+        // on classloader, so we fool it by changing the Thread context from the
+        // webapps classloader to its parent (the server's classloader):
+        ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(webappClassLoader.getParent());
+        Connection connection;
+        try
+        {
+            connection = this.theTransactionalDriver.connect(url, properties);
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(webappClassLoader);
+        }
+
+        return connection;
+    }
+
+    ///////////////////////
+
+    // Implementation of XADataSource API is just a straightforward wrap/delegate.
+    // Note that some of these methods also appear in the DataSource API.
+    // We don't really care, it's the underlying implementations problem
+    // to disambiguate them if required.
+
+    public boolean isWrapperFor(Class<?> iface) throws SQLException
+    {
+        return iface.isAssignableFrom(XADataSource.class);
+    }
+
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        if(isWrapperFor(iface)) {
+            return (T)getUnwrappedXADataSource();
+        } else {
+            throw new SQLException("Not a wrapper for "+iface.getCanonicalName());
+        }
+    }
+
+    public XAConnection getXAConnection() throws SQLException
+    {
+        return _theXADataSource.getXAConnection();
+    }
+
+    public XAConnection getXAConnection(String user, String password) throws SQLException
+    {
+        return _theXADataSource.getXAConnection(user, password);
+    }
+
+    public PrintWriter getLogWriter() throws SQLException
+    {
+        return _theXADataSource.getLogWriter();
+    }
+
+    public void setLogWriter(PrintWriter out) throws SQLException
+    {
+        _theXADataSource.setLogWriter(out);
+    }
+
+    public void setLoginTimeout(int seconds) throws SQLException
+    {
+        _theXADataSource.setLoginTimeout(seconds);
+    }
+
+    public int getLoginTimeout() throws SQLException
+    {
+        return _theXADataSource.getLoginTimeout();
+    }
+
+   public TransactionalDriver getTransactionalDriver()
+   {
+      return theTransactionalDriver;
+   }
+}



More information about the jboss-svn-commits mailing list