1
18 package net.bull.javamelody;
19
20 import java.io.IOException;
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.Method;
23 import java.util.Arrays;
24 import java.util.EventListener;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Map.Entry;
28
29 import javax.servlet.DispatcherType;
30 import javax.servlet.FilterRegistration;
31 import javax.servlet.ServletContext;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34 import javax.sql.DataSource;
35
36 import org.aopalliance.intercept.MethodInvocation;
37 import org.springframework.aop.Pointcut;
38 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
39 import org.springframework.aop.support.Pointcuts;
40 import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
41 import org.springframework.beans.factory.annotation.Value;
42 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
43 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
44 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
45 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
46 import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
47 import org.springframework.boot.context.properties.EnableConfigurationProperties;
48 import org.springframework.boot.web.servlet.FilterRegistrationBean;
49 import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
50 import org.springframework.context.annotation.Bean;
51 import org.springframework.context.annotation.Configuration;
52 import org.springframework.scheduling.annotation.Async;
53 import org.springframework.scheduling.annotation.Scheduled;
54 import org.springframework.scheduling.annotation.Schedules;
55 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
56 import org.springframework.stereotype.Controller;
57 import org.springframework.stereotype.Service;
58 import org.springframework.web.bind.annotation.RequestMapping;
59 import org.springframework.web.bind.annotation.RestController;
60 import org.springframework.web.client.RestTemplate;
61
62
83 @Configuration
84 @EnableConfigurationProperties(JavaMelodyConfigurationProperties.class)
85 @ConditionalOnWebApplication
86 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "enabled", matchIfMissing = true)
87 public class JavaMelodyAutoConfiguration {
88
91 public static final String REGISTRATION_BEAN_NAME = "javamelody-registration";
92
93 private final MonitoredWithAnnotationPointcut monitoredWithSpringAnnotationPointcut = new MonitoredWithAnnotationPointcut();
94
95 private final Pointcut asyncAnnotationPointcut = Pointcuts.union(
96 new AnnotationMatchingPointcut(Async.class),
97 new AnnotationMatchingPointcut(null, Async.class));
98
99 private final Pointcut scheduledAnnotationPointcut = Pointcuts.union(
100 new AnnotationMatchingPointcut(null, Scheduled.class),
101 new AnnotationMatchingPointcut(null, Schedules.class));
102
103
108 @Bean
109 public ServletListenerRegistrationBean<EventListener> monitoringSessionListener(
110 ServletContext servletContext) {
111 final ServletListenerRegistrationBean<EventListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(
112 new SessionListener());
113 if (servletContext.getFilterRegistration("javamelody") != null) {
114
115
116 servletListenerRegistrationBean.setEnabled(false);
117 }
118 return servletListenerRegistrationBean;
119 }
120
121
128 @Bean(name = REGISTRATION_BEAN_NAME)
129 @ConditionalOnMissingBean(name = REGISTRATION_BEAN_NAME)
130 public FilterRegistrationBean<MonitoringFilter> monitoringFilter(
131 JavaMelodyConfigurationProperties properties, ServletContext servletContext) {
132 final FilterRegistrationBean<MonitoringFilter> registrationBean = new FilterRegistrationBean<>();
133
134
135 final MonitoringFilter filter;
136 if (properties.isManagementEndpointMonitoringEnabled()) {
137
138 filter = new MonitoringFilter() {
139 @Override
140 protected boolean isAllowed(HttpServletRequest request,
141 HttpServletResponse response) throws IOException {
142 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden access");
143 return false;
144 }
145 };
146 } else {
147 filter = new MonitoringFilter();
148 }
149 filter.setApplicationType("Spring Boot");
150
151
152 registrationBean.setFilter(filter);
153 registrationBean.setAsyncSupported(true);
154 registrationBean.setName("javamelody");
155 registrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
156
157
158 for (final Entry<String, String> parameter : properties.getInitParameters().entrySet()) {
159 registrationBean.addInitParameter(parameter.getKey(), parameter.getValue());
160 }
161
162
163 registrationBean.addUrlPatterns(");
164
165 final FilterRegistration filterRegistration = servletContext
166 .getFilterRegistration("javamelody");
167 if (filterRegistration != null) {
168
169
170 registrationBean.setEnabled(false);
171 for (final Map.Entry<String, String> entry : registrationBean.getInitParameters()
172 .entrySet()) {
173 filterRegistration.setInitParameter(entry.getKey(), entry.getValue());
174 }
175 }
176 return registrationBean;
177 }
178
179 /**
180 * When enabled, management endpoint for /monitoring reports on the management http port instead of the application http port.
181 * @param servletContext ServletContext
182 * @return MonitoringEndpoint
183 */
184 @Bean
185 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "management-endpoint-monitoring-enabled", matchIfMissing = false)
186 public MonitoringEndpoint monitoringEndpoint(final ServletContext servletContext) {
187 return new MonitoringEndpoint(servletContext);
188 }
189
190
194 @Bean
195 @ConditionalOnMissingBean(DefaultAdvisorAutoProxyCreator.class)
196 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "advisor-auto-proxy-creator-enabled", matchIfMissing = false)
197 public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
198 return new DefaultAdvisorAutoProxyCreator();
199 }
200
201
206 @Bean
207 public SpringDataSourceBeanPostProcessor monitoringDataSourceBeanPostProcessor(
208 @Value("${javamelody.excluded-datasources:}") String excludedDatasources) {
209
210
211 final SpringDataSourceBeanPostProcessor processor = new SpringDataSourceBeanPostProcessor();
212 if (excludedDatasources != null && excludedDatasources.trim().length() > 0) {
213 processor.setExcludedDatasources(
214 new HashSet<>(Arrays.asList(excludedDatasources.split(","))));
215 }
216 return processor;
217 }
218
219
223 @Bean
224 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
225 public MonitoringSpringAdvisor monitoringSpringAdvisor() {
226 return new MonitoringSpringAdvisor(monitoredWithSpringAnnotationPointcut);
227 }
228
229
233 @Bean
234 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
235 public MonitoringSpringAdvisor monitoringSpringServiceAdvisor() {
236 return createMonitoringSpringAdvisorWithExclusions(
237 new AnnotationMatchingPointcut(Service.class),
238 monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut,
239 scheduledAnnotationPointcut);
240 }
241
242
246 @Bean
247 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
248 public MonitoringSpringAdvisor monitoringSpringControllerAdvisor() {
249 return createMonitoringSpringAdvisorWithExclusions(
250 new AnnotationMatchingPointcut(Controller.class),
251 monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut,
252 scheduledAnnotationPointcut);
253 }
254
255
259 @Bean
260 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
261 public MonitoringSpringAdvisor monitoringSpringRestControllerAdvisor() {
262 return createMonitoringSpringAdvisorWithExclusions(
263 new AnnotationMatchingPointcut(RestController.class),
264 monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut,
265 scheduledAnnotationPointcut);
266 }
267
268
272 @Bean
273 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
274 public MonitoringSpringAdvisor monitoringSpringAsyncAdvisor() {
275 return createMonitoringSpringAdvisorWithExclusions(asyncAnnotationPointcut,
276 monitoredWithSpringAnnotationPointcut, scheduledAnnotationPointcut);
277 }
278
279
283 @Bean
284 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "scheduled-monitoring-enabled", matchIfMissing = true)
285 @ConditionalOnMissingBean(DefaultAdvisorAutoProxyCreator.class)
286 public MonitoringSpringAdvisor monitoringSpringScheduledAdvisor() {
287 return createMonitoringSpringAdvisorWithExclusions(scheduledAnnotationPointcut,
288 monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut);
289 }
290
291 private MonitoringSpringAdvisor createMonitoringSpringAdvisorWithExclusions(Pointcut pointcut,
292 Pointcut... excludedPointcuts) {
293 final Pointcut myPointcut;
294 if (excludedPointcuts.length == 0) {
295 myPointcut = pointcut;
296 } else {
297 Pointcut excludedPointcut = excludedPointcuts[0];
298 if (excludedPointcuts.length > 1) {
299 for (int i = 1; i < excludedPointcuts.length; i++) {
300 excludedPointcut = Pointcuts.union(excludedPointcut, excludedPointcuts[i]);
301 }
302 }
303 myPointcut = new ExcludingPointcut(pointcut).exclude(excludedPointcut);
304 }
305 return new MonitoringSpringAdvisor(myPointcut);
306 }
307
308
313 @SuppressWarnings("unchecked")
314 @Bean
315 @ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignClient")
316 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
317
318 @ConditionalOnMissingBean(DefaultAdvisorAutoProxyCreator.class)
319 public MonitoringSpringAdvisor monitoringFeignClientAdvisor() throws ClassNotFoundException {
320 final Class<? extends Annotation> feignClientClass = (Class<? extends Annotation>) Class
321 .forName("org.springframework.cloud.openfeign.FeignClient");
322 final MonitoringSpringAdvisor advisor = new MonitoringSpringAdvisor(
323 new AnnotationMatchingPointcut(feignClientClass, true));
324 advisor.setAdvice(new MonitoringSpringInterceptor() {
325 private static final long serialVersionUID = 1L;
326
327 @Override
328 protected String getRequestName(MethodInvocation invocation) {
329 final StringBuilder sb = new StringBuilder();
330 final Method method = invocation.getMethod();
331 final RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
332 if (requestMapping != null) {
333 String[] path = requestMapping.value();
334 if (path.length == 0) {
335 path = requestMapping.path();
336 }
337 if (path.length > 0) {
338 sb.append(path[0]);
339 sb.append(' ');
340 if (requestMapping.method().length > 0) {
341 sb.append(requestMapping.method()[0].name());
342 } else {
343 sb.append("GET");
344 }
345 sb.append('\n');
346 }
347 }
348 final Class<?> declaringClass = method.getDeclaringClass();
349 final String classPart = declaringClass.getSimpleName();
350 final String methodPart = method.getName();
351 sb.append(classPart).append('.').append(methodPart);
352 return sb.toString();
353 }
354 });
355 return advisor;
356 }
357
358
362 @Bean
363 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
364 public SpringRestTemplateBeanPostProcessor monitoringRestTemplateBeanPostProcessor() {
365 return new SpringRestTemplateBeanPostProcessor();
366 }
367
368
372 @Bean
373 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
374 public SpringMongoDbFactoryBeanPostProcessor monitoringMongoDbFactoryBeanPostProcessor() {
375 return new SpringMongoDbFactoryBeanPostProcessor();
376 }
377
378
382 @Bean
383 @ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
384 public SpringElasticsearchOperationsBeanPostProcessor monitoringElasticsearchOperationsBeanPostProcessor() {
385 return new SpringElasticsearchOperationsBeanPostProcessor();
386 }
387
388
392 @ConditionalOnClass(name = {
393 "org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer",
394 "org.springframework.scheduling.quartz.SchedulerFactoryBean",
395 "org.quartz.JobListener" })
396 @Bean
397 @ConditionalOnMissingBean
398 public SchedulerFactoryBeanCustomizer schedulerFactoryBeanCustomizer() {
399 return new SchedulerFactoryBeanCustomizer() {
400 @Override
401 public void customize(SchedulerFactoryBean schedulerFactoryBean) {
402 final JobGlobalListener jobGlobalListener = new JobGlobalListener();
403 schedulerFactoryBean.setGlobalJobListeners(jobGlobalListener);
404 schedulerFactoryBean.setExposeSchedulerInRepository(true);
405 }
406 };
407 }
408
409
412 @Bean
413 public SpringContext javamelodySpringContext() {
414 return new SpringContext();
415 }
416 }
417