/* * Copyright 2013 Sylvain LAURENT * * 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. */ package ch.sla.jdbcperflogger.driver; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.text.MessageFormat; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jdt.annotation.Nullable; import ch.sla.jdbcperflogger.DriverConfig; import ch.sla.jdbcperflogger.Logger; import ch.sla.jdbcperflogger.logger.PerfLoggerRemoting; /** * This is the JDBC Driver implementation of the performance logger. * * @author slaurent * */ public class WrappingDriver implements Driver { public final static String URL_PREFIX = "jdbcperflogger:"; private final static Logger LOGGER = Logger.getLogger(WrappingDriver.class); public final static WrappingDriver INSTANCE = new WrappingDriver(); private final static Map<String, Driver> underlyingDrivers = new ConcurrentHashMap<String, Driver>(); private static boolean registered; static { load(); } public static synchronized Driver load() { if (!registered) { try { DriverManager.registerDriver(INSTANCE); } catch (final SQLException e) { throw new RuntimeException(e); } registered = true; } return INSTANCE; } public static synchronized void unload() { if (registered) { try { DriverManager.deregisterDriver(INSTANCE); // TODO : properly stop threads and sockets registered = false; } catch (final SQLException e) { throw new RuntimeException(e); } } } private static final AtomicInteger connectionCounter = new AtomicInteger(); public WrappingDriver() { } @Override @Nullable public Connection connect(final String url, @Nullable final Properties info) throws SQLException { if (!acceptsURL(url)) { return null; } LOGGER.debug("connect url=[" + url + "]"); final String unWrappedUrl = extractUrlForWrappedDriver(url); Driver underlyingDriver = null; final String underlyingDriverClassName = DriverConfig.INSTANCE.getClassNameForJdbcUrl(unWrappedUrl); if (underlyingDriverClassName != null) { underlyingDriver = underlyingDrivers.get(underlyingDriverClassName); } if (underlyingDriver == null && underlyingDriverClassName != null) { try { final Class<?> underlyingDriverClass = Class.forName(underlyingDriverClassName); underlyingDriver = (Driver) underlyingDriverClass.newInstance(); underlyingDrivers.put(underlyingDriverClassName, underlyingDriver); } catch (final ClassNotFoundException e) { final String msg = MessageFormat.format("Cannot find driver class {0} for JDBC url {1}", underlyingDriverClassName, unWrappedUrl); LOGGER.warn(msg, e); } catch (final InstantiationException e) { throw new SQLException(e); } catch (final IllegalAccessException e) { throw new SQLException(e); } } if (underlyingDriver == null) { // unknown driver, just use the DriverManager to attempt to locate it try { underlyingDriver = DriverManager.getDriver(unWrappedUrl); } catch (final SQLException e) { throw new SQLException("Cannot get underlying JDBC driver for [" + unWrappedUrl + "]. The underlying driver must be either registered with the DriverManager or listed " + "in a jdbcperflogger.xml file, see documentation.", e); } } final Driver finalUnderlyingDriver = underlyingDriver; final Connection connection = wrapConnection(unWrappedUrl, info, new Callable<Connection>() { @Override public Connection call() throws Exception { return finalUnderlyingDriver.connect(unWrappedUrl, info); } }); return connection; } public @Nullable Connection wrapConnection(final String url, final @Nullable Properties info, final Callable<Connection> underlyingConnectionCreator) throws SQLException { final long startNanos = System.nanoTime(); Connection connection; try { connection = underlyingConnectionCreator.call(); } catch (final Exception e) { if (e.getCause() instanceof SQLException) { throw (SQLException) e.getCause(); } else { throw new SQLException(e); } } if (connection == null) { // short-circuit, the underlying driver was not the right one return null; } if (Proxy.isProxyClass(connection.getClass()) && Proxy.getInvocationHandler(connection).getClass() == LoggingConnectionInvocationHandler.class) { // the connection may have already been wrapped if the caller asks for a jdbcperflogger: prefixed url while // using the java agent at the same time. In that case, just return the connection without wrapping it again return connection; } final long connectionCreationDuration = System.nanoTime() - startNanos; final Properties cleanedConnectionProperties = new Properties(); if (info != null) { for (final String str : info.stringPropertyNames()) { if (!str.toLowerCase().contains("password")) { cleanedConnectionProperties.setProperty(str, info.getProperty(str)); } } } final LoggingConnectionInvocationHandler connectionInvocationHandler = new LoggingConnectionInvocationHandler( connectionCounter.incrementAndGet(), connection, url, cleanedConnectionProperties); connection = (Connection) Proxy.newProxyInstance(WrappingDriver.class.getClassLoader(), Utils.extractAllInterfaces(connection.getClass()), connectionInvocationHandler); PerfLoggerRemoting.connectionCreated(connectionInvocationHandler, connectionCreationDuration); return connection; } @Override public boolean acceptsURL(@Nullable final String url) throws SQLException { return url != null && url.startsWith(URL_PREFIX); } @Override public DriverPropertyInfo[] getPropertyInfo(@Nullable final String url, @Nullable final Properties info) throws SQLException { return new DriverPropertyInfo[0]; } @Override public int getMajorVersion() { return 1; } @Override public int getMinorVersion() { return 0; } @Override public boolean jdbcCompliant() { return false; } @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } private String extractUrlForWrappedDriver(final String url) { return url.substring(URL_PREFIX.length()); } }