1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody;
19
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.InvocationHandler;
23 import java.lang.reflect.Proxy;
24 import java.security.AccessController;
25 import java.security.PrivilegedAction;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.Hashtable;
30 import java.util.LinkedHashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.WeakHashMap;
34
35 import javax.naming.Context;
36 import javax.naming.InitialContext;
37 import javax.naming.NameClassPair;
38 import javax.naming.NamingException;
39 import javax.naming.NoInitialContextException;
40 import javax.naming.Referenceable;
41 import javax.servlet.ServletContext;
42 import javax.sql.DataSource;
43
44 import org.apache.tomcat.dbcp.dbcp.BasicDataSource;
45
46 /**
47  * Classe utilitaire pour {@link JdbcWrapper}.
48  * @author Emeric Vernat
49  */

50 final class JdbcWrapperHelper {
51     private static final String MAX_ACTIVE_PROPERTY_NAME = "maxActive";
52     private static final Map<String, DataSource> SPRING_DATASOURCES = new LinkedHashMap<>();
53     private static final Map<String, DataSource> JNDI_DATASOURCES_BACKUP = new LinkedHashMap<>();
54     private static final Map<String, DataSource> REWRAPPED_DATASOURCES_BACKUP = new LinkedHashMap<>();
55     private static final BasicDataSourcesProperties TOMCAT_BASIC_DATASOURCES_PROPERTIES = new BasicDataSourcesProperties();
56     private static final BasicDataSourcesProperties DBCP_BASIC_DATASOURCES_PROPERTIES = new BasicDataSourcesProperties();
57     private static final BasicDataSourcesProperties TOMCAT_JDBC_DATASOURCES_PROPERTIES = new BasicDataSourcesProperties();
58
59     private static final Map<Class<?>, Constructor<?>> PROXY_CACHE = Collections
60             .synchronizedMap(new WeakHashMap<Class<?>, Constructor<?>>());
61
62     /**
63      * Propriétés des BasicDataSources si elles viennent de Tomcat-DBCP ou de DBCP seul.
64      * @author Emeric Vernat
65      */

66     private static class BasicDataSourcesProperties {
67         private final Map<String, Map<String, Object>> properties = new LinkedHashMap<>();
68
69         BasicDataSourcesProperties() {
70             super();
71         }
72
73         boolean isEmpty() {
74             return properties.isEmpty();
75         }
76
77         int getMaxActive() {
78             int result = 0;
79             for (final Map<String, Object> dataSourceProperties : properties.values()) {
80                 final Integer maxActive = (Integer) dataSourceProperties
81                         .get(MAX_ACTIVE_PROPERTY_NAME);
82                 if (maxActive == null) {
83                     return -1;
84                 }
85                 result += maxActive;
86             }
87             return result;
88         }
89
90         Map<String, Map<String, Object>> getDataSourcesProperties() {
91             final Map<String, Map<String, Object>> result = new LinkedHashMap<>();
92             for (final Map.Entry<String, Map<String, Object>> entry : properties.entrySet()) {
93                 result.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue()));
94             }
95             return Collections.unmodifiableMap(result);
96         }
97
98         void put(String dataSourceName, String key, Object value) {
99             Map<String, Object> dataSourceProperties = properties.get(dataSourceName);
100             if (dataSourceProperties == null) {
101                 dataSourceProperties = new LinkedHashMap<>();
102                 properties.put(dataSourceName, dataSourceProperties);
103             }
104             dataSourceProperties.put(key, value);
105         }
106     }
107
108     private JdbcWrapperHelper() {
109         super();
110     }
111
112     static void registerSpringDataSource(String name, DataSource dataSource) {
113         SPRING_DATASOURCES.put(name, dataSource);
114     }
115
116     static void registerRewrappedDataSource(String name, DataSource dataSource) {
117         REWRAPPED_DATASOURCES_BACKUP.put(name, dataSource);
118     }
119
120     static Map<String, DataSource> getRewrappedDataSources() {
121         return REWRAPPED_DATASOURCES_BACKUP;
122     }
123
124     static void rebindDataSource(ServletContext servletContext, String jndiName,
125             DataSource dataSource, DataSource dataSourceProxy) throws Throwable {
126         final Object lock = changeContextWritable(servletContext, null);
127         final InitialContext initialContext = new InitialContext();
128         initialContext.rebind(jndiName, dataSourceProxy);
129         JNDI_DATASOURCES_BACKUP.put(jndiName, dataSource);
130         changeContextWritable(servletContext, lock);
131         initialContext.close();
132     }
133
134     static void rebindInitialDataSources(ServletContext servletContext) throws Throwable {
135         try {
136             final InitialContext initialContext = new InitialContext();
137             for (final Map.Entry<String, DataSource> entry : JNDI_DATASOURCES_BACKUP.entrySet()) {
138                 final String jndiName = entry.getKey();
139                 final DataSource dataSource = entry.getValue();
140                 final Object lock = changeContextWritable(servletContext, null);
141                 initialContext.rebind(jndiName, dataSource);
142                 changeContextWritable(servletContext, lock);
143             }
144             initialContext.close();
145         } finally {
146             JNDI_DATASOURCES_BACKUP.clear();
147         }
148     }
149
150     static Map<String, DataSource> getJndiAndSpringDataSources() throws NamingException {
151         Map<String, DataSource> dataSources;
152         try {
153             dataSources = new LinkedHashMap<>(getJndiDataSources());
154         } catch (final NoInitialContextException e) {
155             dataSources = new LinkedHashMap<>();
156         }
157         dataSources.putAll(SPRING_DATASOURCES);
158         return dataSources;
159     }
160
161     static Map<String, DataSource> getJndiDataSources() throws NamingException {
162         final Map<String, DataSource> dataSources = new LinkedHashMap<>(2);
163         final String datasourcesParameter = Parameter.DATASOURCES.getValue();
164         if (datasourcesParameter == null) {
165             dataSources.putAll(getJndiDataSourcesAt("java:comp/env/jdbc"));
166             // pour jboss sans jboss-env.xml ou sans resource-ref dans web.xml :
167             dataSources.putAll(getJndiDataSourcesAt("java:/jdbc"));
168             // pour JavaEE 6 :
169             // (voir par exemple http://smokeandice.blogspot.com/2009/12/datasourcedefinition-hidden-gem-from.html)
170             dataSources.putAll(getJndiDataSourcesAt("java:global/jdbc"));
171             // pour WebLogic 10 et WebSphere 7, cf issue 68
172             dataSources.putAll(getJndiDataSourcesAt("jdbc"));
173         } else if (!datasourcesParameter.trim().isEmpty()) {
174             final InitialContext initialContext = new InitialContext();
175             for (final String datasource : datasourcesParameter.split(",")) {
176                 final String jndiName = datasource.trim();
177                 // ici, on n'ajoute pas java:/comp/env
178                 // et on suppose qu'il n'en faut pas ou que cela a été ajouté dans le paramétrage
179                 final DataSource dataSource = (DataSource) initialContext.lookup(jndiName);
180                 dataSources.put(jndiName, dataSource);
181             }
182             initialContext.close();
183         }
184         return Collections.unmodifiableMap(dataSources);
185     }
186
187     private static Map<String, DataSource> getJndiDataSourcesAt(String jndiPrefix)
188             throws NamingException {
189         final InitialContext initialContext = new InitialContext();
190         final Map<String, DataSource> dataSources = new LinkedHashMap<>(2);
191         try {
192             for (final NameClassPair nameClassPair : Collections
193                     .list(initialContext.list(jndiPrefix))) {
194                 // note: il ne suffit pas de tester
195                 // (DataSource.class.isAssignableFrom(Class.forName(nameClassPair.getClassName())))
196                 // car nameClassPair.getClassName() vaut "javax.naming.LinkRef" sous jboss 5.1.0.GA
197                 // par exemple, donc on fait le lookup pour voir
198                 final String jndiName;
199                 if (nameClassPair.getName().startsWith("java:")) {
200                     // pour glassfish v3
201                     jndiName = nameClassPair.getName();
202                 } else {
203                     jndiName = jndiPrefix + '/' + nameClassPair.getName();
204                 }
205                 final Object value = initialContext.lookup(jndiName);
206                 if (value instanceof DataSource) {
207                     dataSources.put(jndiName, (DataSource) value);
208                 }
209             }
210         } catch (final NamingException e) {
211             // le préfixe ("comp/env/jdbc""/jdbc" ou "java:global/jdbc", etc) n'existe pas dans jndi,
212             // (dans glassfish 3.0.1, c'est une NamingException et non une NameNotFoundException)
213             return dataSources;
214         }
215         initialContext.close();
216         return dataSources;
217     }
218
219     static int getMaxConnectionCount() {
220         if (!TOMCAT_BASIC_DATASOURCES_PROPERTIES.isEmpty()) {
221             return TOMCAT_BASIC_DATASOURCES_PROPERTIES.getMaxActive();
222         } else if (!DBCP_BASIC_DATASOURCES_PROPERTIES.isEmpty()) {
223             return DBCP_BASIC_DATASOURCES_PROPERTIES.getMaxActive();
224         } else if (!TOMCAT_JDBC_DATASOURCES_PROPERTIES.isEmpty()) {
225             return TOMCAT_JDBC_DATASOURCES_PROPERTIES.getMaxActive();
226         }
227         return -1;
228     }
229
230     static Map<String, Map<String, Object>> getBasicDataSourceProperties() {
231         if (!TOMCAT_BASIC_DATASOURCES_PROPERTIES.isEmpty()) {
232             return TOMCAT_BASIC_DATASOURCES_PROPERTIES.getDataSourcesProperties();
233         } else if (!DBCP_BASIC_DATASOURCES_PROPERTIES.isEmpty()) {
234             return DBCP_BASIC_DATASOURCES_PROPERTIES.getDataSourcesProperties();
235         } else if (!TOMCAT_JDBC_DATASOURCES_PROPERTIES.isEmpty()) {
236             return TOMCAT_JDBC_DATASOURCES_PROPERTIES.getDataSourcesProperties();
237         }
238         return Collections.emptyMap();
239     }
240
241     // CHECKSTYLE:OFF
242     static void pullDataSourceProperties(String name, DataSource dataSource) {
243         // CHECKSTYLE:ON
244         final String dataSourceClassName = dataSource.getClass().getName();
245         if ("org.apache.tomcat.dbcp.dbcp.BasicDataSource".equals(dataSourceClassName)
246                 && dataSource instanceof BasicDataSource) {
247             pullTomcatDbcpDataSourceProperties(name, dataSource);
248         } else if ("org.apache.tomcat.dbcp.dbcp2.BasicDataSource".equals(dataSourceClassName)
249                 && dataSource instanceof org.apache.tomcat.dbcp.dbcp2.BasicDataSource) {
250             pullTomcatDbcp2DataSourceProperties(name, dataSource);
251         } else if ("org.apache.commons.dbcp.BasicDataSource".equals(dataSourceClassName)
252                 && dataSource instanceof org.apache.commons.dbcp.BasicDataSource) {
253             pullCommonsDbcpDataSourceProperties(name, dataSource);
254         } else if ("org.apache.commons.dbcp2.BasicDataSource".equals(dataSourceClassName)
255                 && dataSource instanceof org.apache.commons.dbcp2.BasicDataSource) {
256             pullCommonsDbcp2DataSourceProperties(name, dataSource);
257         } else if ("org.apache.tomcat.jdbc.pool.DataSource".equals(dataSourceClassName)
258                 && dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) {
259             pullTomcatJdbcDataSourceProperties(name, dataSource);
260         }
261     }
262
263     private static void pullTomcatDbcpDataSourceProperties(String name, DataSource dataSource) {
264         // si tomcat et si dataSource standard, alors on récupère des infos
265         final BasicDataSource tomcatDbcpDataSource = (BasicDataSource) dataSource;
266         final BasicDataSourcesProperties properties = TOMCAT_BASIC_DATASOURCES_PROPERTIES;
267         // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t,
268         // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t
269
270         // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow)
271         properties.put(name, MAX_ACTIVE_PROPERTY_NAME, tomcatDbcpDataSource.getMaxActive());
272         properties.put(name, "poolPreparedStatements",
273                 tomcatDbcpDataSource.isPoolPreparedStatements());
274
275         properties.put(name, "defaultCatalog", tomcatDbcpDataSource.getDefaultCatalog());
276         properties.put(name, "defaultAutoCommit", tomcatDbcpDataSource.getDefaultAutoCommit());
277         properties.put(name, "defaultReadOnly", tomcatDbcpDataSource.getDefaultReadOnly());
278         properties.put(name, "defaultTransactionIsolation",
279                 tomcatDbcpDataSource.getDefaultTransactionIsolation());
280         properties.put(name, "driverClassName", tomcatDbcpDataSource.getDriverClassName());
281         properties.put(name, "initialSize", tomcatDbcpDataSource.getInitialSize());
282         properties.put(name, "maxIdle", tomcatDbcpDataSource.getMaxIdle());
283         properties.put(name, "maxOpenPreparedStatements",
284                 tomcatDbcpDataSource.getMaxOpenPreparedStatements());
285         properties.put(name, "maxWait", tomcatDbcpDataSource.getMaxWait());
286         properties.put(name, "minEvictableIdleTimeMillis",
287                 tomcatDbcpDataSource.getMinEvictableIdleTimeMillis());
288         properties.put(name, "minIdle", tomcatDbcpDataSource.getMinIdle());
289         properties.put(name, "numTestsPerEvictionRun",
290                 tomcatDbcpDataSource.getNumTestsPerEvictionRun());
291         properties.put(name, "testOnBorrow", tomcatDbcpDataSource.getTestOnBorrow());
292         properties.put(name, "testOnReturn", tomcatDbcpDataSource.getTestOnReturn());
293         properties.put(name, "testWhileIdle", tomcatDbcpDataSource.getTestWhileIdle());
294         properties.put(name, "timeBetweenEvictionRunsMillis",
295                 tomcatDbcpDataSource.getTimeBetweenEvictionRunsMillis());
296         properties.put(name, "validationQuery", tomcatDbcpDataSource.getValidationQuery());
297     }
298
299     private static void pullCommonsDbcpDataSourceProperties(String name, DataSource dataSource) {
300         // si dbcp et si dataSource standard, alors on récupère des infos
301         final org.apache.commons.dbcp.BasicDataSource dbcpDataSource = (org.apache.commons.dbcp.BasicDataSource) dataSource;
302         final BasicDataSourcesProperties properties = DBCP_BASIC_DATASOURCES_PROPERTIES;
303         // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t,
304         // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t
305
306         // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow)
307         properties.put(name, MAX_ACTIVE_PROPERTY_NAME, dbcpDataSource.getMaxActive());
308         properties.put(name, "poolPreparedStatements", dbcpDataSource.isPoolPreparedStatements());
309
310         properties.put(name, "defaultCatalog", dbcpDataSource.getDefaultCatalog());
311         properties.put(name, "defaultAutoCommit", dbcpDataSource.getDefaultAutoCommit());
312         properties.put(name, "defaultReadOnly", dbcpDataSource.getDefaultReadOnly());
313         properties.put(name, "defaultTransactionIsolation",
314                 dbcpDataSource.getDefaultTransactionIsolation());
315         properties.put(name, "driverClassName", dbcpDataSource.getDriverClassName());
316         properties.put(name, "initialSize", dbcpDataSource.getInitialSize());
317         properties.put(name, "maxIdle", dbcpDataSource.getMaxIdle());
318         properties.put(name, "maxOpenPreparedStatements",
319                 dbcpDataSource.getMaxOpenPreparedStatements());
320         properties.put(name, "maxWait", dbcpDataSource.getMaxWait());
321         properties.put(name, "minEvictableIdleTimeMillis",
322                 dbcpDataSource.getMinEvictableIdleTimeMillis());
323         properties.put(name, "minIdle", dbcpDataSource.getMinIdle());
324         properties.put(name, "numTestsPerEvictionRun", dbcpDataSource.getNumTestsPerEvictionRun());
325         properties.put(name, "testOnBorrow", dbcpDataSource.getTestOnBorrow());
326         properties.put(name, "testOnReturn", dbcpDataSource.getTestOnReturn());
327         properties.put(name, "testWhileIdle", dbcpDataSource.getTestWhileIdle());
328         properties.put(name, "timeBetweenEvictionRunsMillis",
329                 dbcpDataSource.getTimeBetweenEvictionRunsMillis());
330         properties.put(name, "validationQuery", dbcpDataSource.getValidationQuery());
331     }
332
333     private static void pullTomcatDbcp2DataSourceProperties(String name, DataSource dataSource) {
334         // si tomcat et si dataSource standard, alors on récupère des infos
335         final org.apache.tomcat.dbcp.dbcp2.BasicDataSource tomcatDbcp2DataSource = (org.apache.tomcat.dbcp.dbcp2.BasicDataSource) dataSource;
336         final BasicDataSourcesProperties properties = TOMCAT_BASIC_DATASOURCES_PROPERTIES;
337         // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t,
338         // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t
339
340         // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow)
341         properties.put(name, MAX_ACTIVE_PROPERTY_NAME, tomcatDbcp2DataSource.getMaxTotal());
342         properties.put(name, "poolPreparedStatements",
343                 tomcatDbcp2DataSource.isPoolPreparedStatements());
344
345         properties.put(name, "defaultCatalog", tomcatDbcp2DataSource.getDefaultCatalog());
346         properties.put(name, "defaultAutoCommit", tomcatDbcp2DataSource.getDefaultAutoCommit());
347         properties.put(name, "defaultReadOnly", tomcatDbcp2DataSource.getDefaultReadOnly());
348         properties.put(name, "defaultTransactionIsolation",
349                 tomcatDbcp2DataSource.getDefaultTransactionIsolation());
350         properties.put(name, "driverClassName", tomcatDbcp2DataSource.getDriverClassName());
351         properties.put(name, "initialSize", tomcatDbcp2DataSource.getInitialSize());
352         properties.put(name, "maxIdle", tomcatDbcp2DataSource.getMaxIdle());
353         properties.put(name, "maxOpenPreparedStatements",
354                 tomcatDbcp2DataSource.getMaxOpenPreparedStatements());
355         properties.put(name, "maxWait", tomcatDbcp2DataSource.getMaxWaitMillis());
356         properties.put(name, "minEvictableIdleTimeMillis",
357                 tomcatDbcp2DataSource.getMinEvictableIdleTimeMillis());
358         properties.put(name, "minIdle", tomcatDbcp2DataSource.getMinIdle());
359         properties.put(name, "numTestsPerEvictionRun",
360                 tomcatDbcp2DataSource.getNumTestsPerEvictionRun());
361         properties.put(name, "testOnBorrow", tomcatDbcp2DataSource.getTestOnBorrow());
362         properties.put(name, "testOnReturn", tomcatDbcp2DataSource.getTestOnReturn());
363         properties.put(name, "testWhileIdle", tomcatDbcp2DataSource.getTestWhileIdle());
364         properties.put(name, "timeBetweenEvictionRunsMillis",
365                 tomcatDbcp2DataSource.getTimeBetweenEvictionRunsMillis());
366         properties.put(name, "validationQuery", tomcatDbcp2DataSource.getValidationQuery());
367     }
368
369     private static void pullCommonsDbcp2DataSourceProperties(String name, DataSource dataSource) {
370         // si dbcp et si dataSource standard, alors on récupère des infos
371         final org.apache.commons.dbcp2.BasicDataSource dbcp2DataSource = (org.apache.commons.dbcp2.BasicDataSource) dataSource;
372         final BasicDataSourcesProperties properties = DBCP_BASIC_DATASOURCES_PROPERTIES;
373         // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t,
374         // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t
375
376         // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow)
377         properties.put(name, MAX_ACTIVE_PROPERTY_NAME, dbcp2DataSource.getMaxTotal());
378         properties.put(name, "poolPreparedStatements", dbcp2DataSource.isPoolPreparedStatements());
379
380         properties.put(name, "defaultCatalog", dbcp2DataSource.getDefaultCatalog());
381         properties.put(name, "defaultAutoCommit", dbcp2DataSource.getDefaultAutoCommit());
382         properties.put(name, "defaultReadOnly", dbcp2DataSource.getDefaultReadOnly());
383         properties.put(name, "defaultTransactionIsolation",
384                 dbcp2DataSource.getDefaultTransactionIsolation());
385         properties.put(name, "driverClassName", dbcp2DataSource.getDriverClassName());
386         properties.put(name, "initialSize", dbcp2DataSource.getInitialSize());
387         properties.put(name, "maxIdle", dbcp2DataSource.getMaxIdle());
388         properties.put(name, "maxOpenPreparedStatements",
389                 dbcp2DataSource.getMaxOpenPreparedStatements());
390         properties.put(name, "maxWait", dbcp2DataSource.getMaxWaitMillis());
391         properties.put(name, "minEvictableIdleTimeMillis",
392                 dbcp2DataSource.getMinEvictableIdleTimeMillis());
393         properties.put(name, "minIdle", dbcp2DataSource.getMinIdle());
394         properties.put(name, "numTestsPerEvictionRun", dbcp2DataSource.getNumTestsPerEvictionRun());
395         properties.put(name, "testOnBorrow", dbcp2DataSource.getTestOnBorrow());
396         properties.put(name, "testOnReturn", dbcp2DataSource.getTestOnReturn());
397         properties.put(name, "testWhileIdle", dbcp2DataSource.getTestWhileIdle());
398         properties.put(name, "timeBetweenEvictionRunsMillis",
399                 dbcp2DataSource.getTimeBetweenEvictionRunsMillis());
400         properties.put(name, "validationQuery", dbcp2DataSource.getValidationQuery());
401     }
402
403     private static void pullTomcatJdbcDataSourceProperties(String name, DataSource dataSource) {
404         // si tomcat-jdbc, alors on récupère des infos
405         final org.apache.tomcat.jdbc.pool.DataSource jdbcDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
406         final BasicDataSourcesProperties properties = TOMCAT_JDBC_DATASOURCES_PROPERTIES;
407         // basicDataSource.getNumActive() est en théorie égale à USED_CONNECTION_COUNT à un instant t,
408         // numIdle + numActive est le nombre de connexions ouvertes dans la bdd pour ce serveur à un instant t
409
410         // les propriétés généralement importantes en premier (se méfier aussi de testOnBorrow)
411         properties.put(name, MAX_ACTIVE_PROPERTY_NAME, jdbcDataSource.getMaxActive());
412
413         properties.put(name, "defaultCatalog", jdbcDataSource.getDefaultCatalog());
414         properties.put(name, "defaultAutoCommit", jdbcDataSource.getDefaultAutoCommit());
415         properties.put(name, "defaultReadOnly", jdbcDataSource.getDefaultReadOnly());
416         properties.put(name, "defaultTransactionIsolation",
417                 jdbcDataSource.getDefaultTransactionIsolation());
418         properties.put(name, "driverClassName", jdbcDataSource.getDriverClassName());
419         properties.put(name, "connectionProperties", jdbcDataSource.getConnectionProperties());
420         properties.put(name, "initSQL", jdbcDataSource.getInitSQL());
421         properties.put(name, "initialSize", jdbcDataSource.getInitialSize());
422         properties.put(name, "maxIdle", jdbcDataSource.getMaxIdle());
423         properties.put(name, "maxWait", jdbcDataSource.getMaxWait());
424         properties.put(name, "maxAge", jdbcDataSource.getMaxAge());
425         properties.put(name, "faireQueue", jdbcDataSource.isFairQueue());
426         properties.put(name, "jmxEnabled", jdbcDataSource.isJmxEnabled());
427         properties.put(name, "minEvictableIdleTimeMillis",
428                 jdbcDataSource.getMinEvictableIdleTimeMillis());
429         properties.put(name, "minIdle", jdbcDataSource.getMinIdle());
430         properties.put(name, "numTestsPerEvictionRun", jdbcDataSource.getNumTestsPerEvictionRun());
431         properties.put(name, "testOnBorrow", jdbcDataSource.isTestOnBorrow());
432         properties.put(name, "testOnConnect", jdbcDataSource.isTestOnConnect());
433         properties.put(name, "testOnReturn", jdbcDataSource.isTestOnReturn());
434         properties.put(name, "testWhileIdle", jdbcDataSource.isTestWhileIdle());
435         properties.put(name, "timeBetweenEvictionRunsMillis",
436                 jdbcDataSource.getTimeBetweenEvictionRunsMillis());
437         properties.put(name, "validationInterval", jdbcDataSource.getValidationInterval());
438         properties.put(name, "validationQuery", jdbcDataSource.getValidationQuery());
439         properties.put(name, "validatorClassName", jdbcDataSource.getValidatorClassName());
440     }
441
442     @SuppressWarnings("all")
443     private static Object changeContextWritable(ServletContext servletContext, Object lock)
444             throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException,
445             NamingException {
446         // cette méthode ne peut pas être utilisée avec un simple JdbcDriver
447         assert servletContext != null;
448         final String serverInfo = servletContext.getServerInfo();
449         if (serverInfo.contains("jetty")) {
450             // on n'exécute cela que si c'est jetty
451             final Context jdbcContext = (Context) new InitialContext().lookup("java:comp");
452             final Field field = getAccessibleField(jdbcContext, "_env");
453             @SuppressWarnings("unchecked")
454             final Hashtable<Object, Object> env = (Hashtable<Object, Object>) field
455                     .get(jdbcContext);
456             if (lock == null) {
457                 // on rend le contexte writable
458                 Object result = env.remove("org.mortbay.jndi.lock");
459                 if (result == null) {
460                     result = env.remove("org.eclipse.jndi.lock");
461                 }
462                 return result;
463             }
464             // on remet le contexte not writable comme avant
465             env.put("org.mortbay.jndi.lock", lock);
466             env.put("org.eclipse.jndi.lock", lock);
467
468             return null;
469         }
470         // si on arrive là (pas rewrap et pas jetty),
471         // alors on suppose que cela pourrait être Tomcat
472         // (serverInfo.contains("Tomcat") || serverInfo.contains("vFabric") || serverInfo.contains("SpringSource tc Runtime"))
473         // même si serverInfo a été modifié dans ServerInfo.properties
474         try {
475             final Field field = Class.forName("org.apache.naming.ContextAccessController")
476                     .getDeclaredField("readOnlyContexts");
477             setFieldAccessible(field);
478             @SuppressWarnings("unchecked")
479             final Map<String, Object> readOnlyContexts = (Map<String, Object>) field.get(null);
480             // la clé dans cette Hashtable ou depuis 8.0.83/9.0.67 ConcurrentHashMap est normalement
481             // "/Catalina/" + hostName + Parameters.getContextPath(servletContext) ;
482             // hostName vaut en général "localhost" (ou autre selon le Host dans server.xml)
483             // et contextPath vaut "/myapp" par exemple ;
484             // la valeur est un securityToken
485             if (lock == null) {
486                 // on utilise clear et non remove au cas où le host ne soit pas localhost dans server.xml
487                 // (cf issue 105)
488                 final Hashtable<String, Object> clone = new Hashtable<>(readOnlyContexts);
489                 readOnlyContexts.clear();
490                 return clone;
491             }
492             // on remet le contexte not writable comme avant
493             @SuppressWarnings("unchecked")
494             final Hashtable<String, Object> myLock = (Hashtable<String, Object>) lock;
495             readOnlyContexts.putAll(myLock);
496
497             return null;
498         } catch (final Exception e) {
499             // tant pis pour le lock jndi, ce n'était pas Tomcat finalement
500             return null;
501         }
502     }
503
504     static Object getFieldValue(Object object, String fieldName) throws IllegalAccessException {
505         return getAccessibleField(object, fieldName).get(object);
506     }
507
508     static void setFieldValue(Object object, String fieldName, Object value)
509             throws IllegalAccessException {
510         getAccessibleField(object, fieldName).set(object, value);
511     }
512
513     static boolean hasField(Object object, String fieldName) {
514         return getField(object, fieldName) != null;
515     }
516
517     private static Field getAccessibleField(Object object, String fieldName) {
518         final Field result = getField(object, fieldName);
519         assert result != null;
520         setFieldAccessible(result);
521         return result;
522     }
523
524     private static Field getField(Object object, String fieldName) {
525         assert fieldName != null;
526         Class<?> classe = object.getClass();
527         Field result = null;
528         do {
529             for (final Field field : classe.getDeclaredFields()) {
530                 if (fieldName.equals(field.getName())) {
531                     result = field;
532                     break;
533                 }
534             }
535             classe = classe.getSuperclass();
536         } while (result == null && classe != null);
537         return result;
538     }
539
540     private static void setFieldAccessible(final Field field) {
541         AccessController.doPrivileged(new PrivilegedAction<Object>() { // pour findbugs
542             /** {@inheritDoc} */
543             @Override
544             public Object run() {
545                 field.setAccessible(true);
546                 return null;
547             }
548         });
549     }
550
551     static void clearProxyCache() {
552         PROXY_CACHE.clear();
553     }
554
555     @SuppressWarnings("unchecked")
556     static <T> T createProxy(T object, InvocationHandler invocationHandler,
557             List<Class<?>> interfaces) {
558         final Class<?> objectClass = object.getClass();
559         // ce handler désencapsule les InvocationTargetException des 3 proxy
560         // avant : return (T) Proxy.newProxyInstance(objectClass.getClassLoader(), getObjectInterfaces(objectClass, interfaces), ih);
561         // maintenant (optimisé) :
562         Constructor<?> constructor = PROXY_CACHE.get(objectClass);
563         if (constructor == null) {
564             final Class<?>[] interfacesArray = getObjectInterfaces(objectClass, interfaces);
565             constructor = getProxyConstructor(objectClass, interfacesArray);
566             // si interfaces est non null, la classe n'est en théorie pas suffisante comme clé,
567             // mais ce n'est pas un cas courant
568             if (interfaces == null) {
569                 PROXY_CACHE.put(objectClass, constructor);
570             }
571         }
572         try {
573             return (T) constructor.newInstance(new Object[] { invocationHandler });
574         } catch (final Exception e) {
575             throw new IllegalStateException(e);
576         }
577     }
578
579     private static Constructor<?> getProxyConstructor(Class<?> objectClass,
580             Class<?>[] interfacesArray) {
581         final ClassLoader classLoader = objectClass.getClassLoader();
582         try {
583             final Constructor<?> constructor = Proxy.getProxyClass(classLoader, interfacesArray)
584                     .getConstructor(InvocationHandler.class);
585             // issue 475: workaround for a j.l.r.Proxy change in Java 8 - proxy class for non-public interface is non-public now.
586             // Ref: https://netbeans.org/bugzilla/show_bug.cgi?id=229191
587             // and http://hg.netbeans.org/jet-main/rev/3238e03c676f
588             constructor.setAccessible(true);
589             return constructor;
590         } catch (final NoSuchMethodException e) {
591             throw new IllegalStateException(e);
592         }
593     }
594
595     private static Class<?>[] getObjectInterfaces(Class<?> objectClass, List<Class<?>> interfaces) {
596         // Rq: object.getClass().getInterfaces() ne suffit pas pour Connection dans Tomcat
597         // car la connection est une instance de PoolGuardConnectionWrapper
598         // et connection.getClass().getInterfaces() est vide dans ce cas
599         final List<Class<?>> myInterfaces;
600         if (interfaces == null) {
601             myInterfaces = new ArrayList<>(Arrays.asList(objectClass.getInterfaces()));
602             Class<?> classe = objectClass.getSuperclass();
603             while (classe != null) {
604                 final Class<?>[] classInterfaces = classe.getInterfaces();
605                 if (classInterfaces.length > 0) {
606                     final List<Class<?>> superInterfaces = Arrays.asList(classInterfaces);
607                     // removeAll d'abord car il ne faut pas de doublon dans la liste
608                     myInterfaces.removeAll(superInterfaces);
609                     myInterfaces.addAll(superInterfaces);
610                 }
611                 classe = classe.getSuperclass();
612             }
613             // on ignore l'interface javax.naming.Referenceable car sinon le rebind sous jetty appelle
614             // referenceable.getReference() et devient inutile
615             myInterfaces.remove(Referenceable.class);
616         } else {
617             myInterfaces = interfaces;
618         }
619         return myInterfaces.toArray(new Class<?>[0]);
620     }
621 }
622