/* * Copyright 2008-2017 by Emeric Vernat * * This file is part of Java Melody. * * 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 net.bull.javamelody; 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.util.Collections; import java.util.Properties; import java.util.logging.Logger; /** * Driver jdbc "proxy" pour le monitoring. * C'est la classe de ce driver qui doit être déclarée si un driver jdbc et non une dataSource est utilisé. * Dans la déclaration une propriété jdbc 'driver' doit contenir le nom de la classe du vrai driver. * D'autres propriétés jdbc comme url, username ou password peuvent être déclarées. * @author Emeric Vernat */ public class JdbcDriver implements Driver { // cette classe est publique pour être déclarée dans une configuration jdbc static final JdbcDriver SINGLETON = new JdbcDriver(); // initialisation statique du driver static { try { DriverManager.registerDriver(SINGLETON); LOG.debug("JDBC driver registered"); // on désinstalle et on réinstalle les autres drivers pour que le notre soit en premier // (notamment, si le jar du driver contient un fichier java.sql.Driver dans META-INF/services // pour initialiser automatiquement le driver contenu dans le jar) for (final Driver driver : Collections.list(DriverManager.getDrivers())) { if (driver != SINGLETON) { DriverManager.deregisterDriver(driver); DriverManager.registerDriver(driver); } } } catch (final SQLException e) { // ne peut arriver throw new IllegalStateException(e); } } /** {@inheritDoc} */ @Override public Connection connect(String url, Properties info) throws SQLException { if ("false".equals(info.get("javamelody"))) { // if property javamelody=false then it's not for us // (we pass here from the DriverManager.getConnection below) return null; } String myUrl = url; // we load first the driver class from the info or the url, to be sure that it will be found String proxiedDriver = info.getProperty("driver"); if (proxiedDriver == null && myUrl != null) { // if not in the info, the driver class could also be passed at the end of the url, for example ...?driver=org.h2.Driver final int index = myUrl.indexOf("driver="); if (index != -1) { proxiedDriver = myUrl.substring(index + "driver=".length()); myUrl = myUrl.substring(0, index - 1); } } if (proxiedDriver == null) { // if the driver is not defined in the info or in the url // it could still be found automatically if the driver is in the classpath // or (in WEB-INF/lib and if the jdbc drivers are not loaded by the JDK before this webapp) // but we don't want to create proxies and increment counts for the connections inside datasources // so we only accept and go further if driver is defined in the info or in the url return null; } try { // on utilise Thread.currentThread().getContextClassLoader() car le driver peut ne pas être // dans le même classLoader que les classes de javamelody // Class driverClass = Class.forName(proxiedDriver, true, Thread.currentThread().getContextClassLoader()); // et non Class.forName(proxiedDriver); } catch (final ClassNotFoundException e) { throw new SQLException(e.getMessage(), e); } final Properties myInfo = (Properties) info.clone(); myInfo.remove("driver"); myInfo.put("javamelody", "false"); Parameters.initJdbcDriverParameters(myUrl, myInfo); // we could call driverClass.newInstance().connect(myUrl, myInfo) // possibly by looking the driver which accepts the url in DriverManager.getDrivers() // but we prefer calling the standard DriverManager.getConnection(myUrl, myInfo) return JdbcWrapper.SINGLETON .createConnectionProxy(DriverManager.getConnection(myUrl, myInfo)); } /** {@inheritDoc} */ @Override public boolean acceptsURL(String url) throws SQLException { // test sur dbcp nécessaire pour le cas où le monitoring est utilisé avec le web.xml global // et le répertoire lib global de tomcat et également pour les anomalies 1&2 (sonar, grails) // (rq: Thread.currentThread().getStackTrace() a été mesuré à environ 3 micro-secondes) for (final StackTraceElement element : Thread.currentThread().getStackTrace()) { if (element.getClassName().endsWith("dbcp.BasicDataSource")) { return false; } } return true; } /** {@inheritDoc} */ @Override public int getMajorVersion() { return -1; } /** {@inheritDoc} */ @Override public int getMinorVersion() { return -1; } /** {@inheritDoc} */ @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return new DriverPropertyInfo[0]; } /** {@inheritDoc} */ @Override public boolean jdbcCompliant() { return true; } /** {@inheritDoc} */ @Override public String toString() { return getClass().getSimpleName() + "[lastConnectUrl=" + Parameters.getLastConnectUrl() + ", lastConnectInfo=" + Parameters.getLastConnectInfo() + ']'; } /** * Définition de la méthode getParentLogger ajoutée dans l'interface Driver en jdk 1.7. * @return Logger * @throws SQLFeatureNotSupportedException e */ @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); } }