/* * 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.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameClassPair; import javax.naming.NamingException; import javax.naming.NoInitialContextException; import javax.naming.Referenceable; import javax.servlet.ServletContext; import javax.sql.DataSource; import org.apache.tomcat.dbcp.dbcp.BasicDataSource; /** * Classe utilitaire pour JdbcWrapper. * @author Emeric Vernat */ final class JdbcWrapperHelper { private static final String MAX_ACTIVE_PROPERTY_NAME = "maxActive"; private static final Map<String, DataSource> SPRING_DATASOURCES = new LinkedHashMap<String, DataSource>(); private static final Map<String, DataSource> JNDI_DATASOURCES_BACKUP = new LinkedHashMap<String, DataSource>(); private static final BasicDataSourcesProperties TOMCAT_BASIC_DATASOURCES_PROPERTIES = new BasicDataSourcesProperties(); private static final BasicDataSourcesProperties DBCP_BASIC_DATASOURCES_PROPERTIES = new BasicDataSourcesProperties(); private static final BasicDataSourcesProperties TOMCAT_JDBC_DATASOURCES_PROPERTIES = new BasicDataSourcesProperties(); private static final Map<Class<?>, Constructor<?>> PROXY_CACHE = Collections .synchronizedMap(new WeakHashMap<Class<?>, Constructor<?>>()); /** * Propriétés des BasicDataSources si elles viennent de Tomcat-DBCP ou de DBCP seul. * @author Emeric Vernat */ private static class BasicDataSourcesProperties { private final Map<String, Map<String, Object>> properties = new LinkedHashMap<String, Map<String, Object>>(); BasicDataSourcesProperties() { super(); } boolean isEmpty() { return properties.isEmpty(); } int getMaxActive() { int result = 0; for (final Map<String, Object> dataSourceProperties : properties.values()) { final Integer maxActive = (Integer) dataSourceProperties .get(MAX_ACTIVE_PROPERTY_NAME); if (maxActive == null) { return -1; } result += maxActive; } return result; } Map<String, Map<String, Object>> getDataSourcesProperties() { final Map<String, Map<String, Object>> result = new LinkedHashMap<String, Map<String, Object>>(); for (final Map.Entry<String, Map<String, Object>> entry : properties.entrySet()) { result.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue())); } return Collections.unmodifiableMap(result); } void put(String dataSourceName, String key, Object value) { Map<String, Object> dataSourceProperties = properties.get(dataSourceName); if (dataSourceProperties == null) { dataSourceProperties = new LinkedHashMap<String, Object>(); properties.put(dataSourceName, dataSourceProperties); } dataSourceProperties.put(key, value); } } private JdbcWrapperHelper() { super(); } static void registerSpringDataSource(String name, DataSource dataSource) { SPRING_DATASOURCES.put(name, dataSource); } static void rebindDataSource(ServletContext servletContext, String jndiName, DataSource dataSource, DataSource dataSourceProxy) throws Throwable { final Object lock = changeContextWritable(servletContext, null); final InitialContext initialContext = new InitialContext(); initialContext.rebind(jndiName, dataSourceProxy); JNDI_DATASOURCES_BACKUP.put(jndiName, dataSource); changeContextWritable(servletContext, lock); initialContext.close(); } static void rebindInitialDataSources(ServletContext servletContext) throws Throwable { try { final InitialContext initialContext = new InitialContext(); for (final Map.Entry<String, DataSource> entry : JNDI_DATASOURCES_BACKUP.entrySet()) { final String jndiName = entry.getKey(); final DataSource dataSource = entry.getValue(); final Object lock = changeContextWritable(servletContext, null); initialContext.rebind(jndiName, dataSource); changeContextWritable(servletContext, lock); } initialContext.close(); } finally { JNDI_DATASOURCES_BACKUP.clear(); } } static Map<String, DataSource> getJndiAndSpringDataSources() throws NamingException { Map<String, DataSource> dataSources; try { dataSources = new LinkedHashMap<String, DataSource>(getJndiDataSources()); } catch (final NoInitialContextException e) { dataSources = new LinkedHashMap<String, DataSource>(); } dataSources.putAll(SPRING_DATASOURCES); return dataSources; } static Map<String, DataSource> getJndiDataSources() throws NamingException { final Map<String, DataSource> dataSources = new LinkedHashMap<String, DataSource>(2); final String datasourcesParameter = Parameters.getParameter(Parameter.DATASOURCES); if (datasourcesParameter == null) { dataSources.putAll(getJndiDataSourcesAt("java:comp/env/jdbc")); // pour jboss sans jboss-env.xml ou sans resource-ref dans web.xml : dataSources.putAll(getJndiDataSourcesAt("java:/jdbc")); // pour JavaEE 6 : // (voir par exemple http://smokeandice.blogspot.com/2009/12/datasourcedefinition-hidden-gem-from.html) dataSources.putAll(getJndiDataSourcesAt("java:global/jdbc")); // pour WebLogic 10 et WebSphere 7, cf issue 68 dataSources.putAll(getJndiDataSourcesAt("jdbc")); } else if (!datasourcesParameter.trim().isEmpty()) { final InitialContext initialContext = new InitialContext(); for (final String datasource : datasourcesParameter.split(",")) { final String jndiName = datasource.trim(); // ici, on n'ajoute pas java:/comp/env // et on suppose qu'il n'en faut pas ou que cela a été ajouté dans le paramétrage final DataSource dataSource = (DataSource) initialContext.lookup(jndiName); dataSources.put(jndiName, dataSource); } initialContext.close(); } return Collections.unmodifiableMap(dataSources); } private static Map<String, DataSource> getJndiDataSourcesAt(String jndiPrefix) throws NamingException { final InitialContext initialContext = new InitialContext(); final Map<String, DataSource> dataSources = new LinkedHashMap<String, DataSource>(2); try { for (final NameClassPair nameClassPair : Collections .list(initialContext.list(jndiPrefix))) { // note: il ne suffit pas de tester // (DataSource.class.isAssignableFrom(Class.forName(nameClassPair.getClassName()))) // car nameClassPair.getClassName() vaut "javax.naming.LinkRef" sous jboss 5.1.0.GA // par exemple, donc on fait le lookup pour voir final String jndiName; if (nameClassPair.getName().startsWith("java:")) { // pour glassfish v3 jndiName = nameClassPair.getName(); } else { jndiName = jndiPrefix + '/' + nameClassPair.getName(); } final Object value = initialContext.lookup(jndiName); if (value instanceof DataSource) { dataSources.put(jndiName, (DataSource) value); } } } catch (final NamingException e) { // le préfixe ("comp/env/jdbc", "/jdbc" ou "java:global/jdbc", etc) n'existe pas dans jndi, // (dans glassfish 3.0.1, c'est une NamingException et non une NameNotFoundException) return dataSources; } initialContext.close(); return dataSources; } static int getMaxConnectionCount() { if (!TOMCAT_BASIC_DATASOURCES_PROPERTIES.isEmpty()) { return TOMCAT_BASIC_DATASOURCES_PROPERTIES.getMaxActive(); } else if (!DBCP_BASIC_DATASOURCES_PROPERTIES.isEmpty()) { return DBCP_BASIC_DATASOURCES_PROPERTIES.getMaxActive(); } else if (!TOMCAT_JDBC_DATASOURCES_PROPERTIES.isEmpty()) { return TOMCAT_JDBC_DATASOURCES_PROPERTIES.getMaxActive(); } return -1; } static Map<String, Map<String, Object>> getBasicDataSourceProperties() { if (!TOMCAT_BASIC_DATASOURCES_PROPERTIES.isEmpty()) { return TOMCAT_BASIC_DATASOURCES_PROPERTIES.getDataSourcesProperties(); } else if (!DBCP_BASIC_DATASOURCES_PROPERTIES.isEmpty()) { return DBCP_BASIC_DATASOURCES_PROPERTIES.getDataSourcesProperties(); } else if (!TOMCAT_JDBC_DATASOURCES_PROPERTIES.isEmpty()) { return TOMCAT_JDBC_DATASOURCES_PROPERTIES.getDataSourcesProperties(); } return Collections.emptyMap(); } // CHECKSTYLE:OFF static void pullDataSourceProperties(String name, DataSource dataSource) { // CHECKSTYLE:ON final String dataSourceClassName = dataSource.getClass().getName(); if ("org.apache.tomcat.dbcp.dbcp.BasicDataSource".equals(dataSourceClassName) && dataSource instanceof BasicDataSource) { pullTomcatDbcpDataSourceProperties(name, dataSource); } else if ("org.apache.tomcat.dbcp.dbcp2.BasicDataSource".equals(dataSourceClassName) && dataSource instanceof org.apache.tomcat.dbcp.dbcp2.BasicDataSource) { pullTomcatDbcp2DataSourceProperties(name, dataSource); } else if ("org.apache.commons.dbcp.BasicDataSource".equals(dataSourceClassName) && dataSource instanceof org.apache.commons.dbcp.BasicDataSource) { pullCommonsDbcpDataSourceProperties(name, dataSource); } else if ("org.apache.commons.dbcp2.BasicDataSource".equals(dataSourceClassName) && dataSource instanceof org.apache.commons.dbcp2.BasicDataSource) { pullCommonsDbcp2DataSourceProperties(name, dataSource); } else if ("org.apache.tomcat.jdbc.pool.DataSource".equals(dataSourceClassName) && dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) { pullTomcatJdbcDataSourceProperties(name, dataSource); } } private static void pullTomcatDbcpDataSourceProperties(String name, DataSource dataSource) { // si tomcat et si dataSource standard, alors on récupère des infos final BasicDataSource tomcatDbcpDataSource = (BasicDataSource) dataSource; final BasicDataSourcesProperties properties = TOMCAT_BASIC_DATASOURCES_PROPERTIES; // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t, // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow) properties.put(name, MAX_ACTIVE_PROPERTY_NAME, tomcatDbcpDataSource.getMaxActive()); properties.put(name, "poolPreparedStatements", tomcatDbcpDataSource.isPoolPreparedStatements()); properties.put(name, "defaultCatalog", tomcatDbcpDataSource.getDefaultCatalog()); properties.put(name, "defaultAutoCommit", tomcatDbcpDataSource.getDefaultAutoCommit()); properties.put(name, "defaultReadOnly", tomcatDbcpDataSource.getDefaultReadOnly()); properties.put(name, "defaultTransactionIsolation", tomcatDbcpDataSource.getDefaultTransactionIsolation()); properties.put(name, "driverClassName", tomcatDbcpDataSource.getDriverClassName()); properties.put(name, "initialSize", tomcatDbcpDataSource.getInitialSize()); properties.put(name, "maxIdle", tomcatDbcpDataSource.getMaxIdle()); properties.put(name, "maxOpenPreparedStatements", tomcatDbcpDataSource.getMaxOpenPreparedStatements()); properties.put(name, "maxWait", tomcatDbcpDataSource.getMaxWait()); properties.put(name, "minEvictableIdleTimeMillis", tomcatDbcpDataSource.getMinEvictableIdleTimeMillis()); properties.put(name, "minIdle", tomcatDbcpDataSource.getMinIdle()); properties.put(name, "numTestsPerEvictionRun", tomcatDbcpDataSource.getNumTestsPerEvictionRun()); properties.put(name, "testOnBorrow", tomcatDbcpDataSource.getTestOnBorrow()); properties.put(name, "testOnReturn", tomcatDbcpDataSource.getTestOnReturn()); properties.put(name, "testWhileIdle", tomcatDbcpDataSource.getTestWhileIdle()); properties.put(name, "timeBetweenEvictionRunsMillis", tomcatDbcpDataSource.getTimeBetweenEvictionRunsMillis()); properties.put(name, "validationQuery", tomcatDbcpDataSource.getValidationQuery()); } private static void pullCommonsDbcpDataSourceProperties(String name, DataSource dataSource) { // si dbcp et si dataSource standard, alors on récupère des infos final org.apache.commons.dbcp.BasicDataSource dbcpDataSource = (org.apache.commons.dbcp.BasicDataSource) dataSource; final BasicDataSourcesProperties properties = DBCP_BASIC_DATASOURCES_PROPERTIES; // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t, // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow) properties.put(name, MAX_ACTIVE_PROPERTY_NAME, dbcpDataSource.getMaxActive()); properties.put(name, "poolPreparedStatements", dbcpDataSource.isPoolPreparedStatements()); properties.put(name, "defaultCatalog", dbcpDataSource.getDefaultCatalog()); properties.put(name, "defaultAutoCommit", dbcpDataSource.getDefaultAutoCommit()); properties.put(name, "defaultReadOnly", dbcpDataSource.getDefaultReadOnly()); properties.put(name, "defaultTransactionIsolation", dbcpDataSource.getDefaultTransactionIsolation()); properties.put(name, "driverClassName", dbcpDataSource.getDriverClassName()); properties.put(name, "initialSize", dbcpDataSource.getInitialSize()); properties.put(name, "maxIdle", dbcpDataSource.getMaxIdle()); properties.put(name, "maxOpenPreparedStatements", dbcpDataSource.getMaxOpenPreparedStatements()); properties.put(name, "maxWait", dbcpDataSource.getMaxWait()); properties.put(name, "minEvictableIdleTimeMillis", dbcpDataSource.getMinEvictableIdleTimeMillis()); properties.put(name, "minIdle", dbcpDataSource.getMinIdle()); properties.put(name, "numTestsPerEvictionRun", dbcpDataSource.getNumTestsPerEvictionRun()); properties.put(name, "testOnBorrow", dbcpDataSource.getTestOnBorrow()); properties.put(name, "testOnReturn", dbcpDataSource.getTestOnReturn()); properties.put(name, "testWhileIdle", dbcpDataSource.getTestWhileIdle()); properties.put(name, "timeBetweenEvictionRunsMillis", dbcpDataSource.getTimeBetweenEvictionRunsMillis()); properties.put(name, "validationQuery", dbcpDataSource.getValidationQuery()); } private static void pullTomcatDbcp2DataSourceProperties(String name, DataSource dataSource) { // si tomcat et si dataSource standard, alors on récupère des infos final org.apache.tomcat.dbcp.dbcp2.BasicDataSource tomcatDbcp2DataSource = (org.apache.tomcat.dbcp.dbcp2.BasicDataSource) dataSource; final BasicDataSourcesProperties properties = TOMCAT_BASIC_DATASOURCES_PROPERTIES; // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t, // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow) properties.put(name, MAX_ACTIVE_PROPERTY_NAME, tomcatDbcp2DataSource.getMaxTotal()); properties.put(name, "poolPreparedStatements", tomcatDbcp2DataSource.isPoolPreparedStatements()); properties.put(name, "defaultCatalog", tomcatDbcp2DataSource.getDefaultCatalog()); properties.put(name, "defaultAutoCommit", tomcatDbcp2DataSource.getDefaultAutoCommit()); properties.put(name, "defaultReadOnly", tomcatDbcp2DataSource.getDefaultReadOnly()); properties.put(name, "defaultTransactionIsolation", tomcatDbcp2DataSource.getDefaultTransactionIsolation()); properties.put(name, "driverClassName", tomcatDbcp2DataSource.getDriverClassName()); properties.put(name, "initialSize", tomcatDbcp2DataSource.getInitialSize()); properties.put(name, "maxIdle", tomcatDbcp2DataSource.getMaxIdle()); properties.put(name, "maxOpenPreparedStatements", tomcatDbcp2DataSource.getMaxOpenPreparedStatements()); properties.put(name, "maxWait", tomcatDbcp2DataSource.getMaxWaitMillis()); properties.put(name, "minEvictableIdleTimeMillis", tomcatDbcp2DataSource.getMinEvictableIdleTimeMillis()); properties.put(name, "minIdle", tomcatDbcp2DataSource.getMinIdle()); properties.put(name, "numTestsPerEvictionRun", tomcatDbcp2DataSource.getNumTestsPerEvictionRun()); properties.put(name, "testOnBorrow", tomcatDbcp2DataSource.getTestOnBorrow()); properties.put(name, "testOnReturn", tomcatDbcp2DataSource.getTestOnReturn()); properties.put(name, "testWhileIdle", tomcatDbcp2DataSource.getTestWhileIdle()); properties.put(name, "timeBetweenEvictionRunsMillis", tomcatDbcp2DataSource.getTimeBetweenEvictionRunsMillis()); properties.put(name, "validationQuery", tomcatDbcp2DataSource.getValidationQuery()); } private static void pullCommonsDbcp2DataSourceProperties(String name, DataSource dataSource) { // si dbcp et si dataSource standard, alors on récupère des infos final org.apache.commons.dbcp2.BasicDataSource dbcp2DataSource = (org.apache.commons.dbcp2.BasicDataSource) dataSource; final BasicDataSourcesProperties properties = DBCP_BASIC_DATASOURCES_PROPERTIES; // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t, // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow) properties.put(name, MAX_ACTIVE_PROPERTY_NAME, dbcp2DataSource.getMaxTotal()); properties.put(name, "poolPreparedStatements", dbcp2DataSource.isPoolPreparedStatements()); properties.put(name, "defaultCatalog", dbcp2DataSource.getDefaultCatalog()); properties.put(name, "defaultAutoCommit", dbcp2DataSource.getDefaultAutoCommit()); properties.put(name, "defaultReadOnly", dbcp2DataSource.getDefaultReadOnly()); properties.put(name, "defaultTransactionIsolation", dbcp2DataSource.getDefaultTransactionIsolation()); properties.put(name, "driverClassName", dbcp2DataSource.getDriverClassName()); properties.put(name, "initialSize", dbcp2DataSource.getInitialSize()); properties.put(name, "maxIdle", dbcp2DataSource.getMaxIdle()); properties.put(name, "maxOpenPreparedStatements", dbcp2DataSource.getMaxOpenPreparedStatements()); properties.put(name, "maxWait", dbcp2DataSource.getMaxWaitMillis()); properties.put(name, "minEvictableIdleTimeMillis", dbcp2DataSource.getMinEvictableIdleTimeMillis()); properties.put(name, "minIdle", dbcp2DataSource.getMinIdle()); properties.put(name, "numTestsPerEvictionRun", dbcp2DataSource.getNumTestsPerEvictionRun()); properties.put(name, "testOnBorrow", dbcp2DataSource.getTestOnBorrow()); properties.put(name, "testOnReturn", dbcp2DataSource.getTestOnReturn()); properties.put(name, "testWhileIdle", dbcp2DataSource.getTestWhileIdle()); properties.put(name, "timeBetweenEvictionRunsMillis", dbcp2DataSource.getTimeBetweenEvictionRunsMillis()); properties.put(name, "validationQuery", dbcp2DataSource.getValidationQuery()); } private static void pullTomcatJdbcDataSourceProperties(String name, DataSource dataSource) { // si tomcat-jdbc, alors on récupère des infos final org.apache.tomcat.jdbc.pool.DataSource jdbcDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource; final BasicDataSourcesProperties properties = TOMCAT_JDBC_DATASOURCES_PROPERTIES; // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t, // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow) properties.put(name, MAX_ACTIVE_PROPERTY_NAME, jdbcDataSource.getMaxActive()); properties.put(name, "defaultCatalog", jdbcDataSource.getDefaultCatalog()); properties.put(name, "defaultAutoCommit", jdbcDataSource.getDefaultAutoCommit()); properties.put(name, "defaultReadOnly", jdbcDataSource.getDefaultReadOnly()); properties.put(name, "defaultTransactionIsolation", jdbcDataSource.getDefaultTransactionIsolation()); properties.put(name, "driverClassName", jdbcDataSource.getDriverClassName()); properties.put(name, "connectionProperties", jdbcDataSource.getConnectionProperties()); properties.put(name, "initSQL", jdbcDataSource.getInitSQL()); properties.put(name, "initialSize", jdbcDataSource.getInitialSize()); properties.put(name, "maxIdle", jdbcDataSource.getMaxIdle()); properties.put(name, "maxWait", jdbcDataSource.getMaxWait()); properties.put(name, "maxAge", jdbcDataSource.getMaxAge()); properties.put(name, "faireQueue", jdbcDataSource.isFairQueue()); properties.put(name, "jmxEnabled", jdbcDataSource.isJmxEnabled()); properties.put(name, "minEvictableIdleTimeMillis", jdbcDataSource.getMinEvictableIdleTimeMillis()); properties.put(name, "minIdle", jdbcDataSource.getMinIdle()); properties.put(name, "numTestsPerEvictionRun", jdbcDataSource.getNumTestsPerEvictionRun()); properties.put(name, "testOnBorrow", jdbcDataSource.isTestOnBorrow()); properties.put(name, "testOnConnect", jdbcDataSource.isTestOnConnect()); properties.put(name, "testOnReturn", jdbcDataSource.isTestOnReturn()); properties.put(name, "testWhileIdle", jdbcDataSource.isTestWhileIdle()); properties.put(name, "timeBetweenEvictionRunsMillis", jdbcDataSource.getTimeBetweenEvictionRunsMillis()); properties.put(name, "validationInterval", jdbcDataSource.getValidationInterval()); properties.put(name, "validationQuery", jdbcDataSource.getValidationQuery()); properties.put(name, "validatorClassName", jdbcDataSource.getValidatorClassName()); } @SuppressWarnings("all") private static Object changeContextWritable(ServletContext servletContext, Object lock) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException, NamingException { // cette méthode ne peut pas être utilisée avec un simple JdbcDriver assert servletContext != null; final String serverInfo = servletContext.getServerInfo(); if (serverInfo.contains("Tomcat") || serverInfo.contains("vFabric") || serverInfo.contains("SpringSource tc Runtime")) { // on n'exécute cela que si c'est tomcat final Field field = Class.forName("org.apache.naming.ContextAccessController") .getDeclaredField("readOnlyContexts"); setFieldAccessible(field); @SuppressWarnings("unchecked") final Hashtable<String, Object> readOnlyContexts = (Hashtable<String, Object>) field .get(null); // la clé dans cette Hashtable est normalement // "/Catalina/" + hostName + Parameters.getContextPath(servletContext) ; // hostName vaut en général "localhost" (ou autre selon le Host dans server.xml) // et contextPath vaut "/myapp" par exemple ; // la valeur est un securityToken if (lock == null) { // on utilise clear et non remove au cas où le host ne soit pas localhost dans server.xml // (cf issue 105) final Hashtable<String, Object> clone = new Hashtable<String, Object>( readOnlyContexts); readOnlyContexts.clear(); return clone; } // on remet le contexte not writable comme avant @SuppressWarnings("unchecked") final Hashtable<String, Object> myLock = (Hashtable<String, Object>) lock; readOnlyContexts.putAll(myLock); return null; } else if (serverInfo.contains("jetty")) { // on n'exécute cela que si c'est jetty final Context jdbcContext = (Context) new InitialContext().lookup("java:comp"); final Field field = getAccessibleField(jdbcContext, "_env"); @SuppressWarnings("unchecked") final Hashtable<Object, Object> env = (Hashtable<Object, Object>) field .get(jdbcContext); if (lock == null) { // on rend le contexte writable Object result = env.remove("org.mortbay.jndi.lock"); if (result == null) { result = env.remove("org.eclipse.jndi.lock"); } return result; } // on remet le contexte not writable comme avant env.put("org.mortbay.jndi.lock", lock); env.put("org.eclipse.jndi.lock", lock); return null; } return null; } static Object getFieldValue(Object object, String fieldName) throws IllegalAccessException { return getAccessibleField(object, fieldName).get(object); } static void setFieldValue(Object object, String fieldName, Object value) throws IllegalAccessException { getAccessibleField(object, fieldName).set(object, value); } private static Field getAccessibleField(Object object, String fieldName) { assert fieldName != null; Class<?> classe = object.getClass(); Field result = null; do { for (final Field field : classe.getDeclaredFields()) { if (fieldName.equals(field.getName())) { result = field; break; } } classe = classe.getSuperclass(); } while (result == null && classe != null); assert result != null; setFieldAccessible(result); return result; } private static void setFieldAccessible(final Field field) { AccessController.doPrivileged(new PrivilegedAction<Object>() { // pour findbugs /** {@inheritDoc} */ @Override public Object run() { field.setAccessible(true); return null; } }); } static void clearProxyCache() { PROXY_CACHE.clear(); } @SuppressWarnings("unchecked") static <T> T createProxy(T object, InvocationHandler invocationHandler, List<Class<?>> interfaces) { final Class<? extends Object> objectClass = object.getClass(); // ce handler désencapsule les InvocationTargetException des 3 proxy // avant : return (T) Proxy.newProxyInstance(objectClass.getClassLoader(), getObjectInterfaces(objectClass, interfaces), ih); // maintenant (optimisé) : Constructor<?> constructor = PROXY_CACHE.get(objectClass); if (constructor == null) { final Class<?>[] interfacesArray = getObjectInterfaces(objectClass, interfaces); constructor = getProxyConstructor(objectClass, interfacesArray); // si interfaces est non null, la classe n'est en théorie pas suffisante comme clé, // mais ce n'est pas un cas courant if (interfaces == null) { PROXY_CACHE.put(objectClass, constructor); } } try { return (T) constructor.newInstance(new Object[] { invocationHandler }); } catch (final Exception e) { throw new IllegalStateException(e); } } private static Constructor<?> getProxyConstructor(Class<? extends Object> objectClass, Class<?>[] interfacesArray) { final ClassLoader classLoader = objectClass.getClassLoader(); try { final Constructor<?> constructor = Proxy.getProxyClass(classLoader, interfacesArray) .getConstructor(new Class[] { InvocationHandler.class }); // issue 475: workaround for a j.l.r.Proxy change in Java 8 - proxy class for non-public interface is non-public now. // Ref: https://netbeans.org/bugzilla/show_bug.cgi?id=229191 // and http://hg.netbeans.org/jet-main/rev/3238e03c676f constructor.setAccessible(true); return constructor; } catch (final NoSuchMethodException e) { throw new IllegalStateException(e); } } private static Class<?>[] getObjectInterfaces(Class<?> objectClass, List<Class<?>> interfaces) { // Rq: object.getClass().getInterfaces() ne suffit pas pour Connection dans Tomcat // car la connection est une instance de PoolGuardConnectionWrapper // et connection.getClass().getInterfaces() est vide dans ce cas final List<Class<?>> myInterfaces; if (interfaces == null) { myInterfaces = new ArrayList<Class<?>>(Arrays.asList(objectClass.getInterfaces())); Class<?> classe = objectClass.getSuperclass(); while (classe != null) { final Class<?>[] classInterfaces = classe.getInterfaces(); if (classInterfaces.length > 0) { final List<Class<?>> superInterfaces = Arrays.asList(classInterfaces); // removeAll d'abord car il ne faut pas de doublon dans la liste myInterfaces.removeAll(superInterfaces); myInterfaces.addAll(superInterfaces); } classe = classe.getSuperclass(); } // on ignore l'interface javax.naming.Referenceable car sinon le rebind sous jetty appelle // referenceable.getReference() et devient inutile myInterfaces.remove(Referenceable.class); } else { myInterfaces = interfaces; } return myInterfaces.toArray(new Class<?>[myInterfaces.size()]); } }