org.nuxeo.runtime.datasource.PooledDataSourceFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.runtime.datasource.PooledDataSourceFactory.java

Source

/*
 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Stephane Lacoin
 */
package org.nuxeo.runtime.datasource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.InvalidPropertyException;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.LocalTransactionException;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.resource.spi.ResourceAdapterInternalException;
import javax.resource.spi.ResourceAllocationException;
import javax.security.auth.Subject;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.xa.XAResource;

import org.apache.commons.logging.LogFactory;
import org.nuxeo.runtime.datasource.PooledDataSourceRegistry.PooledDataSource;
import org.nuxeo.runtime.jtajca.NuxeoConnectionManagerConfiguration;
import org.nuxeo.runtime.jtajca.NuxeoConnectionManagerFactory;
import org.nuxeo.runtime.jtajca.NuxeoContainer;
import org.nuxeo.runtime.jtajca.NuxeoContainer.ConnectionManagerWrapper;
import org.nuxeo.runtime.transaction.TransactionHelper;
import org.tranql.connector.AbstractManagedConnection;
import org.tranql.connector.CredentialExtractor;
import org.tranql.connector.ExceptionSorter;
import org.tranql.connector.ManagedConnectionHandle;
import org.tranql.connector.UserPasswordManagedConnectionFactory;
import org.tranql.connector.jdbc.AutocommitSpecCompliant;
import org.tranql.connector.jdbc.ConnectionHandle;
import org.tranql.connector.jdbc.KnownSQLStateExceptionSorter;
import org.tranql.connector.jdbc.LocalDataSourceWrapper;
import org.tranql.connector.jdbc.TranqlDataSource;
import org.tranql.connector.jdbc.XADataSourceWrapper;

public class PooledDataSourceFactory implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> environment) {
        class NuxeoDataSource extends TranqlDataSource implements PooledDataSource {

            protected ConnectionManagerWrapper wrapper;

            public NuxeoDataSource(ManagedConnectionFactory mcf, ConnectionManagerWrapper wrapper) {
                super(mcf, wrapper);
                this.wrapper = wrapper;
            }

            @Override
            public void dispose() {
                wrapper.dispose();
            }

            @Override
            public Connection getConnection(boolean noSharing) throws SQLException {
                if (!noSharing) {
                    return getConnection();
                }
                wrapper.getManager().enterNoSharing();
                try {
                    return getConnection();
                } finally {
                    wrapper.getManager().exitNoSharing();
                }
            }

            @Override
            public Logger getParentLogger() throws SQLFeatureNotSupportedException {
                throw new SQLFeatureNotSupportedException("not yet available");
            }
        }
        Reference ref = (Reference) obj;
        ManagedConnectionFactory mcf;
        ConnectionManagerWrapper cm;
        try {
            mcf = createFactory(ref, ctx);
            cm = createManager(ref, ctx);
        } catch (ResourceException | NamingException e) {
            throw new RuntimeException(e);
        }
        return new NuxeoDataSource(mcf, cm);
    }

    protected ConnectionManagerWrapper createManager(Reference ref, Context ctx) throws ResourceException {
        NuxeoConnectionManagerConfiguration config = NuxeoConnectionManagerFactory.getConfig(ref);
        String className = ref.getClassName();
        config.setXAMode(XADataSource.class.getName().equals(className));
        return NuxeoContainer.initConnectionManager(config);
    }

    protected ManagedConnectionFactory createFactory(Reference ref, Context ctx)
            throws NamingException, InvalidPropertyException {
        String className = ref.getClassName();
        if (XADataSource.class.getName().equals(className)) {
            String user = refAttribute(ref, "User", "");
            String password = refAttribute(ref, "Password", "");
            String name = refAttribute(ref, "dataSourceJNDI", null);
            XADataSource ds = NuxeoContainer.lookup(name, XADataSource.class);
            XADataSourceWrapper wrapper = new XADataSourceWrapper(ds);
            wrapper.setUserName(user);
            wrapper.setPassword(password);
            return wrapper;
        }
        if (javax.sql.DataSource.class.getName().equals(className)) {
            String user = refAttribute(ref, "username", "");
            if (user.isEmpty()) {
                user = refAttribute(ref, "user", "");
                if (!user.isEmpty()) {
                    LogFactory.getLog(PooledDataSourceFactory.class)
                            .warn("wrong attribute 'user' in datasource descriptor, should use 'username' instead");
                }
            }
            String password = refAttribute(ref, "password", "");
            String dsname = refAttribute(ref, "dataSourceJNDI", "");
            if (!dsname.isEmpty()) {
                javax.sql.DataSource ds = NuxeoContainer.lookup(dsname, DataSource.class);
                LocalDataSourceWrapper wrapper = new LocalDataSourceWrapper(ds);
                wrapper.setUserName(user);
                wrapper.setPassword(password);
                return wrapper;
            }
            String name = refAttribute(ref, "driverClassName", null);
            String url = refAttribute(ref, "url", null);
            String sqlExceptionSorter = refAttribute(ref, "sqlExceptionSorter",
                    DatasourceExceptionSorter.class.getName());
            boolean commitBeforeAutocommit = Boolean.valueOf(refAttribute(ref, "commitBeforeAutocommit", "true"))
                    .booleanValue();
            JdbcConnectionFactory factory = new JdbcConnectionFactory();
            factory.setDriver(name);
            factory.setUserName(user);
            factory.setPassword(password);
            factory.setConnectionURL(url);
            factory.setExceptionSorterClass(sqlExceptionSorter);
            factory.setCommitBeforeAutocommit(commitBeforeAutocommit);
            return factory;
        }
        throw new IllegalArgumentException("unsupported class " + className);
    }

    static class JdbcConnectionFactory implements UserPasswordManagedConnectionFactory, AutocommitSpecCompliant {
        private static final long serialVersionUID = 4317141492511322929L;
        private Driver driver;
        private String url;
        private String user;
        private String password;
        private ExceptionSorter exceptionSorter = new KnownSQLStateExceptionSorter();
        private boolean commitBeforeAutocommit = false;

        private PrintWriter log;

        @Override
        public Object createConnectionFactory() throws ResourceException {
            throw new NotSupportedException("ConnectionManager is required");
        }

        @Override
        public Object createConnectionFactory(ConnectionManager connectionManager) throws ResourceException {
            return new TranqlDataSource(this, connectionManager);
        }

        @Override
        public ManagedConnection createManagedConnection(Subject subject,
                ConnectionRequestInfo connectionRequestInfo) throws ResourceException {

            class ManagedJDBCConnection extends AbstractManagedConnection<Connection, ConnectionHandle> {
                final CredentialExtractor credentialExtractor;
                final LocalTransactionImpl localTx;
                final LocalTransactionImpl localClientTx;
                final boolean commitBeforeAutoCommit;

                Exception fatalError;

                ManagedJDBCConnection(UserPasswordManagedConnectionFactory mcf, Connection physicalConnection,
                        CredentialExtractor credentialExtractor, ExceptionSorter exceptionSorter,
                        boolean commitBeforeAutoCommit) {
                    super(mcf, physicalConnection, exceptionSorter);
                    this.credentialExtractor = credentialExtractor;
                    localTx = new LocalTransactionImpl(true);
                    localClientTx = new LocalTransactionImpl(false);
                    this.commitBeforeAutoCommit = commitBeforeAutoCommit;
                }

                @Override
                public boolean matches(ManagedConnectionFactory mcf, Subject subject,
                        ConnectionRequestInfo connectionRequestInfo) throws ResourceAdapterInternalException {
                    return credentialExtractor.matches(subject, connectionRequestInfo,
                            (UserPasswordManagedConnectionFactory) mcf);
                }

                @Override
                public LocalTransaction getClientLocalTransaction() {
                    return localClientTx;
                }

                @Override
                public LocalTransaction getLocalTransaction() throws ResourceException {
                    return localTx;
                }

                Connection physicalConnection() throws ResourceException {
                    return physicalConnection;
                }

                @Override
                protected void localTransactionStart(boolean isSPI) throws ResourceException {
                    Connection c = physicalConnection();
                    try {
                        c.setAutoCommit(false);
                    } catch (SQLException e) {
                        throw new LocalTransactionException("Unable to disable autoCommit", e);
                    }
                    super.localTransactionStart(isSPI);
                }

                @Override
                protected void localTransactionCommit(boolean isSPI) throws ResourceException {
                    Connection c = physicalConnection();
                    try {
                        if (commitBeforeAutoCommit) {
                            c.commit();
                        }
                    } catch (SQLException e) {
                        try {
                            c.rollback();
                        } catch (SQLException e1) {
                            if (log != null) {
                                e.printStackTrace(log);
                            }
                        }
                        throw new LocalTransactionException("Unable to commit", e);
                    } finally {
                        try {
                            c.setAutoCommit(true);
                        } catch (SQLException e) {
                            throw new ResourceAdapterInternalException("Unable to enable autoCommit after rollback",
                                    e);
                        }
                    }
                    super.localTransactionCommit(isSPI);
                }

                @Override
                protected void localTransactionRollback(boolean isSPI) throws ResourceException {
                    Connection c = physicalConnection;
                    try {
                        c.rollback();
                    } catch (SQLException e) {
                        throw new LocalTransactionException("Unable to rollback", e);
                    }
                    super.localTransactionRollback(isSPI);
                    try {
                        c.setAutoCommit(true);
                    } catch (SQLException e) {
                        throw new ResourceAdapterInternalException("Unable to enable autoCommit after rollback", e);
                    }
                }

                @Override
                public XAResource getXAResource() throws ResourceException {
                    throw new NotSupportedException("XAResource not available from a LocalTransaction connection");
                }

                @Override
                protected void closePhysicalConnection() throws ResourceException {
                    Connection c = physicalConnection;
                    try {
                        c.close();
                    } catch (SQLException e) {
                        throw new ResourceAdapterInternalException("Error attempting to destroy managed connection",
                                e);
                    }
                }

                @Override
                public ManagedConnectionMetaData getMetaData() throws ResourceException {
                    throw new NotSupportedException("no metadata available yet");
                }

                @Override
                public void connectionError(Exception e) {
                    if (fatalError != null) {
                        return;
                    }

                    if (isFatal(e)) {
                        fatalError = e;
                        if (exceptionSorter.rollbackOnFatalException()) {
                            if (TransactionHelper.isTransactionActive()) {
                                // will roll-back at tx end through #localTransactionRollback
                                TransactionHelper.setTransactionRollbackOnly();
                            } else {
                                attemptRollback();
                            }
                        }
                    }
                }

                @Override
                public void cleanup() throws ResourceException {
                    super.cleanup();
                    if (fatalError != null) {
                        ResourceException error = new ResourceException(
                                String.format("fatal error occurred on %s, destroying", this), fatalError);
                        LogFactory.getLog(ManagedJDBCConnection.class).warn(error.getMessage(), error.getCause());
                        throw error;
                    }
                }

                protected boolean isFatal(Exception e) {
                    if (exceptionSorter.isExceptionFatal(e)) {
                        return true;
                    }
                    try {
                        return !physicalConnection.isValid(10);
                    } catch (SQLException cause) {
                        return false; // could not state
                    } catch (LinkageError cause) {
                        return false; // not compliant JDBC4 driver
                    }
                }

                @Override
                protected void attemptRollback() {
                    try {
                        physicalConnection.rollback();
                    } catch (SQLException e) {
                        // ignore.... presumably the connection is actually dead
                    }
                }

                @Override
                public String toString() {
                    return super.toString() + ". jdbc=" + physicalConnection;
                }
            }

            CredentialExtractor credentialExtractor = new CredentialExtractor(subject, connectionRequestInfo, this);
            Connection sqlConnection = getPhysicalConnection(subject, credentialExtractor);
            return new ManagedJDBCConnection(this, sqlConnection, credentialExtractor, exceptionSorter,
                    commitBeforeAutocommit);
        }

        protected Connection getPhysicalConnection(Subject subject, CredentialExtractor credentialExtractor)
                throws ResourceException {
            try {
                if (!driver.acceptsURL(url)) {
                    throw new ResourceAdapterInternalException("JDBC Driver cannot handle url: " + url);
                }
            } catch (SQLException e) {
                throw new ResourceAdapterInternalException("JDBC Driver rejected url: " + url);
            }

            Properties info = new Properties();
            String user = credentialExtractor.getUserName();
            if (user != null) {
                info.setProperty("user", user);
            }
            String password = credentialExtractor.getPassword();
            if (password != null) {
                info.setProperty("password", password);
            }
            try {
                return driver.connect(url, info);
            } catch (SQLException e) {
                throw new ResourceAllocationException("Unable to obtain physical connection to " + url, e);
            }
        }

        @Override
        public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set set, Subject subject,
                ConnectionRequestInfo connectionRequestInfo) throws ResourceException {
            for (@SuppressWarnings("unchecked")
            Iterator<Object> i = set.iterator(); i.hasNext();) {
                Object o = i.next();
                if (o instanceof ManagedConnectionHandle) {
                    ManagedConnectionHandle<?, ?> mc = (ManagedConnectionHandle<?, ?>) o;
                    if (mc.matches(this, subject, connectionRequestInfo)) {
                        return mc;
                    }
                }
            }
            return null;
        }

        @Override
        public PrintWriter getLogWriter() {
            return log;
        }

        @Override
        public void setLogWriter(PrintWriter log) {
            this.log = log;
        }

        void setDriver(String driver) throws InvalidPropertyException {
            if (driver == null || driver.length() == 0) {
                throw new InvalidPropertyException("Empty driver class name");
            }
            try {
                @SuppressWarnings("unchecked")
                Class<Driver> driverClass = (Class<Driver>) Class.forName(driver);
                this.driver = driverClass.newInstance();
            } catch (ClassNotFoundException e) {
                throw new InvalidPropertyException("Unable to load driver class: " + driver, e);
            } catch (InstantiationException e) {
                throw new InvalidPropertyException("Unable to instantiate driver class: " + driver, e);
            } catch (IllegalAccessException e) {
                throw new InvalidPropertyException("Unable to instantiate driver class: " + driver, e);
            } catch (ClassCastException e) {
                throw new InvalidPropertyException("Class is not a " + Driver.class.getName() + ": " + driver, e);
            }
        }

        void setConnectionURL(String url) throws InvalidPropertyException {
            if (url == null || url.length() == 0) {
                throw new InvalidPropertyException("Empty connection URL");
            }
            this.url = url;
        }

        @Override
        public String getUserName() {
            return user;
        }

        void setUserName(String user) {
            this.user = user;
        }

        @Override
        public String getPassword() {
            return password;
        }

        void setPassword(String password) {
            this.password = password;
        }

        @Override
        public Boolean isCommitBeforeAutocommit() {
            return Boolean.valueOf(commitBeforeAutocommit);
        }

        void setCommitBeforeAutocommit(Boolean commitBeforeAutocommit) {
            this.commitBeforeAutocommit = commitBeforeAutocommit != null && commitBeforeAutocommit.booleanValue();
        }

        void setExceptionSorterClass(String className) throws InvalidPropertyException {
            if (className == null || className.length() == 0) {
                throw new InvalidPropertyException("Empty class name");
            }
            try {
                @SuppressWarnings("unchecked")
                Class<ExceptionSorter> clazz = (Class<ExceptionSorter>) Class.forName(className);
                exceptionSorter = clazz.newInstance();
            } catch (ClassNotFoundException e) {
                throw new InvalidPropertyException("Unable to load class: " + className, e);
            } catch (IllegalAccessException e) {
                throw new InvalidPropertyException("Unable to instantiate class: " + className, e);
            } catch (InstantiationException e) {
                throw new InvalidPropertyException("Unable to instantiate class: " + className, e);
            } catch (ClassCastException e) {
                throw new InvalidPropertyException(
                        "Class is not a " + ExceptionSorter.class.getName() + ": " + driver, e);
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (obj instanceof JdbcConnectionFactory) {
                JdbcConnectionFactory other = (JdbcConnectionFactory) obj;
                return url == other.url || url != null && url.equals(other.url);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return url == null ? 0 : url.hashCode();
        }

        @Override
        public String toString() {
            return "Pooled JDBC Driver Connection Factory [" + user + "@" + url + "]";
        }

    }

    protected String refAttribute(Reference ref, String key, String defvalue) {
        RefAddr addr = ref.get(key);
        if (addr == null) {
            if (defvalue == null) {
                throw new IllegalArgumentException(key + " address is mandatory");
            }
            return defvalue;
        }
        return (String) addr.getContent();
    }

}