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.InvocationHandler;
21 import java.lang.reflect.Method;
22 import java.util.Set;
23
24 import javax.sql.DataSource;
25
26 import org.springframework.beans.factory.config.BeanPostProcessor;
27 import org.springframework.core.PriorityOrdered;
28 import org.springframework.jndi.JndiObjectFactoryBean;
29
30 import net.bull.javamelody.internal.common.LOG;
31 import net.bull.javamelody.internal.common.Parameters;
32
33 /**
34  * Post-processor Spring pour une éventuelle {@link DataSource} défini dans le fichier xml Spring.
35  * @author Emeric Vernat
36  */

37 public class SpringDataSourceBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
38     private Set<String> excludedDatasources;
39     // l'interface PriorityOrdered place la priorité assez haute dans le contexte Spring
40     // quelle que soit la valeur de order
41     private int order = LOWEST_PRECEDENCE;
42
43     private final Class<?> delegatingDataSourceClass = getDelegatingDataSourceClass();
44
45     /**
46      * Définit les noms des datasources Spring exclues.
47      * @param excludedDatasources Set
48      */

49     public void setExcludedDatasources(Set<String> excludedDatasources) {
50         this.excludedDatasources = excludedDatasources;
51
52         // exemple:
53         //    <bean id="springDataSourceBeanPostProcessor" class="net.bull.javamelody.SpringDataSourceBeanPostProcessor">
54         //        <property name="excludedDatasources">
55         //            <set>
56         //                <value>excludedDataSourceName</value>
57         //            </set>
58         //        </property>
59         //     </bean>
60     }
61
62     /** {@inheritDoc} */
63     @Override
64     public int getOrder() {
65         return order;
66     }
67
68     /**
69      * Définit la priorité dans le contexte Spring.
70      * @param order int
71      */

72     public void setOrder(int order) {
73         this.order = order;
74     }
75
76     /** {@inheritDoc} */
77     @Override
78     public Object postProcessBeforeInitialization(Object bean, String beanName) {
79         return bean;
80     }
81
82     private boolean isExcludedDataSource(String beanName) {
83         if (excludedDatasources != null && excludedDatasources.contains(beanName)) {
84             LOG.debug("Spring datasource excluded: " + beanName);
85             return true;
86         }
87         return false;
88     }
89
90     /** {@inheritDoc} */
91     @Override
92     public Object postProcessAfterInitialization(Object bean, String beanName) {
93         if (bean instanceof DataSource) {
94             // on ne teste isExcludedDataSource que si on est sur une datasource
95             if (isExcludedDataSource(beanName) || Parameters.isNoDatabase()
96                     || isDelegatingDataSourceAndAlreadyProxied(bean, beanName)) {
97                 return bean;
98             }
99
100             final DataSource dataSource = (DataSource) bean;
101             JdbcWrapper.registerSpringDataSource(beanName, dataSource);
102             final DataSource result = JdbcWrapper.SINGLETON.createDataSourceProxy(beanName,
103                     dataSource);
104             LOG.debug("Spring datasource wrapped: " + beanName);
105             return result;
106         } else if (bean instanceof JndiObjectFactoryBean) {
107             // ou sur un JndiObjectFactoryBean
108             if (isExcludedDataSource(beanName) || Parameters.isNoDatabase()) {
109                 return bean;
110             }
111
112             // fix issue 20
113             final Object result = createProxy(bean, beanName);
114             LOG.debug("Spring JNDI factory wrapped: " + beanName);
115             return result;
116         }
117
118         // I tried here in the post-processor to fix "quartz jobs which are scheduled with spring
119         // are not displayed in javamelody, except if there is the following property for
120         // SchedulerFactoryBean in spring xml:
121         // <property name="exposeSchedulerInRepository" value="true" /> ",
122
123         // but I had some problem with Spring creating the scheduler
124         // twice and so registering the scheduler in SchedulerRepository with the same name
125         // as the one registered below (and Quartz wants not)
126         //        else if (bean != null
127         //                && "org.springframework.scheduling.quartz.SchedulerFactoryBean".equals(bean
128         //                        .getClass().getName())) {
129         //            try {
130         //                // Remarque: on ajoute nous même le scheduler de Spring dans le SchedulerRepository
131         //                // de Quartz, car l'appel ici de schedulerFactoryBean.setExposeSchedulerInRepository(true)
132         //                // est trop tard et ne fonctionnerait pas
133         //                final Method method = bean.getClass().getMethod("getScheduler", (Class<?>[]) null);
134         //                final Scheduler scheduler = (Scheduler) method.invoke(bean, (Object[]) null);
135         //
136         //                final SchedulerRepository schedulerRepository = SchedulerRepository.getInstance();
137         //                synchronized (schedulerRepository) {
138         //                    if (schedulerRepository.lookup(scheduler.getSchedulerName()) == null) {
139         //                        schedulerRepository.bind(scheduler);
140         //                        scheduler.addGlobalJobListener(new JobGlobalListener());
141         //                    }
142         //                }
143         //            } catch (final NoSuchMethodException e) {
144         //                // si la méthode n'existe pas (avant spring 2.5.6), alors cela marche sans rien faire
145         //                return bean;
146         //            } catch (final InvocationTargetException e) {
147         //                // tant pis
148         //                return bean;
149         //            } catch (final IllegalAccessException e) {
150         //                // tant pis
151         //                return bean;
152         //            } catch (SchedulerException e) {
153         //                // tant pis
154         //                return bean;
155         //            }
156         //        }
157
158         return bean;
159     }
160
161     private Object createProxy(final Object bean, final String beanName) {
162         final InvocationHandler invocationHandler = new InvocationHandler() {
163             /** {@inheritDoc} */
164             @Override
165             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
166                 Object result = method.invoke(bean, args);
167                 if (result instanceof DataSource) {
168                     result = JdbcWrapper.SINGLETON.createDataSourceProxy(beanName,
169                             (DataSource) result);
170                 }
171                 return result;
172             }
173         };
174         return JdbcWrapper.createProxy(bean, invocationHandler);
175     }
176
177     private boolean isDelegatingDataSourceAndAlreadyProxied(Object bean, String beanName) {
178         // bean instanceof DelegatingDataSource ?
179         // use reflection in case spring-jdbc is not available
180         if (delegatingDataSourceClass != null && delegatingDataSourceClass.isInstance(bean)) {
181             final DataSource targetDataSource;
182             try {
183                 targetDataSource = (DataSource) delegatingDataSourceClass
184                         .getMethod("getTargetDataSource").invoke(bean);
185             } catch (final Exception e) {
186                 // call to ((DelegatingDataSource) bean).getTargetDataSource() is not supposed to fail
187                 throw new IllegalStateException(e);
188             }
189             if (JdbcWrapper.isProxyAlready(targetDataSource)) {
190                 LOG.debug("Spring delegating datasource excluded: " + beanName);
191                 return true;
192             }
193         }
194         return false;
195     }
196
197     private static Class<?> getDelegatingDataSourceClass() {
198         try {
199             return Class.forName("org.springframework.jdbc.datasource.DelegatingDataSource");
200         } catch (final ClassNotFoundException e) {
201             return null;
202         }
203     }
204 }
205