001/*
002 * (C) Copyright 2012-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.runtime.datasource;
020
021import java.lang.reflect.InvocationTargetException;
022import java.lang.reflect.Method;
023import java.sql.Connection;
024import java.sql.SQLException;
025
026import javax.naming.NamingException;
027import javax.sql.DataSource;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.common.utils.JDBCUtils;
032import org.nuxeo.runtime.api.Framework;
033import org.nuxeo.runtime.datasource.PooledDataSourceRegistry.PooledDataSource;
034
035/**
036 * Helper to acquire a JDBC {@link Connection} from a datasource name.
037 *
038 * @since 5.7
039 */
040public class ConnectionHelper {
041
042    private static final Log log = LogFactory.getLog(ConnectionHelper.class);
043
044    /**
045     * Tries to unwrap the connection to get the real physical one (returned by the original datasource).
046     * <p>
047     * This should only be used by code that needs to cast the connection to a driver-specific class to use
048     * driver-specific features.
049     *
050     * @throws SQLException if no actual physical connection was allocated yet
051     */
052    public static Connection unwrap(Connection connection) throws SQLException {
053        if (connection instanceof org.tranql.connector.jdbc.ConnectionHandle) {
054            return ((org.tranql.connector.jdbc.ConnectionHandle) connection).getAssociation().getPhysicalConnection();
055        }
056        // now try Apache DBCP unwrap (standard or Tomcat), to skip datasource wrapping layers
057        // this needs accessToUnderlyingConnectionAllowed=true in the pool config
058        try {
059            Method m = connection.getClass().getMethod("getInnermostDelegate");
060            m.setAccessible(true); // needed, method of inner private class
061            Connection delegate = (Connection) m.invoke(connection);
062            if (delegate == null) {
063                log.error("Cannot access underlying connection, you must use "
064                        + "accessToUnderlyingConnectionAllowed=true in the pool configuration");
065            } else {
066                connection = delegate;
067            }
068        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) {
069            // ignore missing method, connection not coming from Apache pool
070        }
071        return connection;
072    }
073
074    /**
075     * Gets a new connection for the given dataSource. The connection <strong>MUST</strong> be closed in a finally block
076     * when code is done using it.
077     *
078     * @param dataSourceName the datasource for which the connection is requested
079     * @return a new connection
080     */
081    public static Connection getConnection(String dataSourceName) throws SQLException {
082        return getConnection(dataSourceName, false);
083    }
084
085    /**
086     * Gets a new connection for the given dataSource. The connection <strong>MUST</strong> be closed in a finally block
087     * when code is done using it.
088     *
089     * @param dataSourceName the datasource for which the connection is requested
090     * @param noSharing {@code true} if this connection must not be shared with others
091     * @return a new connection
092     */
093    public static Connection getConnection(String dataSourceName, boolean noSharing) throws SQLException {
094        DataSource dataSource = getDataSource(dataSourceName);
095        if (dataSource instanceof PooledDataSource) {
096            return ((PooledDataSource) dataSource).getConnection(noSharing);
097        } else {
098            return JDBCUtils.getConnection(dataSource);
099        }
100    }
101
102    /**
103     * Gets a datasource from a datasource name, or in test mode use test connection parameters.
104     *
105     * @param dataSourceName the datasource name
106     * @return the datasource
107     */
108    private static DataSource getDataSource(String dataSourceName) throws SQLException {
109        try {
110            return DataSourceHelper.getDataSource(dataSourceName);
111        } catch (NamingException e) {
112            if (Framework.isTestModeSet()) {
113                String url = Framework.getProperty("nuxeo.test.vcs.url");
114                String user = Framework.getProperty("nuxeo.test.vcs.user");
115                String password = Framework.getProperty("nuxeo.test.vcs.password");
116                if (url != null && user != null) {
117                    return new DataSourceFromUrl(url, user, password); // driver?
118                }
119            }
120            throw new SQLException("Cannot find datasource: " + dataSourceName, e);
121        }
122    }
123
124}