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