View Javadoc
1   package com.atlassian.plugin.spring.scanner.runtime.impl;
2   
3   import org.osgi.framework.Bundle;
4   import org.osgi.framework.BundleContext;
5   import org.slf4j.Logger;
6   import org.springframework.beans.BeansException;
7   import org.springframework.beans.PropertyValues;
8   import org.springframework.beans.factory.DisposableBean;
9   import org.springframework.beans.factory.InitializingBean;
10  import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
11  import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
12  
13  import java.beans.PropertyDescriptor;
14  import java.io.PrintWriter;
15  import java.io.StringWriter;
16  import java.util.Dictionary;
17  import java.util.Enumeration;
18  
19  import static java.lang.String.format;
20  import static java.util.Objects.requireNonNull;
21  import static org.slf4j.LoggerFactory.getLogger;
22  
23  /**
24   * A BeanPostProcessor that logs information about the bean before/after initialisation,
25   * so that developers can debug their Spring context.
26   */
27  public class DevModeBeanInitialisationLoggerBeanPostProcessor
28          implements InstantiationAwareBeanPostProcessor, InitializingBean, DestructionAwareBeanPostProcessor, DisposableBean {
29  
30      /**
31       * Setting this system property to <code>"true"</code> enables per-bundle WARN logging by this class.
32       * Developers who are not interested in the loading/unloading of a given bundle can set that bundle's
33       * spring scanner logging to ERROR.
34       */
35      public static final String ATLASSIAN_DEV_MODE = "atlassian.dev.mode";
36  
37      private static final String BUNDLE_LOGGER_NAME_FORMAT = "com.atlassian.plugin.spring.scanner.%s";
38  
39      private final Bundle bundle;
40      private final Logger bundleLogger;
41  
42      public DevModeBeanInitialisationLoggerBeanPostProcessor(final BundleContext bundleContext) {
43          this.bundle = requireNonNull(bundleContext.getBundle());
44          this.bundleLogger = getLogger(format(BUNDLE_LOGGER_NAME_FORMAT, bundleContext.getBundle().getSymbolicName()));
45      }
46  
47      @Override
48      public Object postProcessBeforeInitialization(Object bean, String beanName) {
49          return bean;
50      }
51  
52      @Override
53      public Object postProcessAfterInitialization(Object bean, String beanName) {
54          logBeanDetail("AfterInitialisation", bean.getClass(), beanName);
55          return bean;
56      }
57  
58      @Override
59      public Object postProcessBeforeInstantiation(Class beanClass, String beanName) {
60          logBeanDetail("BeforeInstantiation", beanClass, beanName);
61          return null; // do default instantiation
62      }
63  
64      @Override
65      public boolean postProcessAfterInstantiation(Object bean, String beanName) {
66          return true; // don't skip property setting
67      }
68  
69      @Override
70      public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) {
71          return pvs; // pass values to be set through
72      }
73  
74      @Override
75      public void postProcessBeforeDestruction(final Object bean, final String beanName) throws BeansException {
76          logBeanDetail("BeforeDestruction", bean.getClass(), beanName);
77      }
78  
79      private void logBeanDetail(String stage, Class beanClass, String beanName) {
80          bundleLogger.debug("{} [bean={}, type={}]", stage, beanName, beanClass.getName());
81      }
82  
83      @Override
84      public void afterPropertiesSet() {
85          // only log the startup info once, the first time we are hit
86          logInDevMode("Spring context started for bundle: {} id({}) v({}) {}",
87                  bundle.getSymbolicName(), bundle.getBundleId(), bundle.getVersion(), bundle.getLocation());
88          if (bundleLogger.isTraceEnabled()) {
89              final StringWriter sw = new StringWriter();
90              final PrintWriter out = new PrintWriter(sw);
91  
92              out.format("\tBundle Headers :\n");
93              final Dictionary headers = bundle.getHeaders();
94              final Enumeration keys = headers.keys();
95              while (keys.hasMoreElements()) {
96                  final Object key = keys.nextElement();
97                  final Object value = headers.get(key);
98                  out.format("\t\t%s: %s\n", key, value);
99              }
100 
101             bundleLogger.trace(sw.toString());
102         }
103     }
104 
105     @Override
106     public void destroy() {
107         logInDevMode("Spring context destroyed for bundle: {} id({}) v({})",
108                 bundle.getSymbolicName(), bundle.getBundleId(), bundle.getVersion());
109     }
110 
111     private void logInDevMode(final String message, final Object... arguments) {
112         if (isDevMode()) {
113             // This is WARN because in JIRA at least, that's the level of the root logger,
114             // and we want these messages to be shown by default (in dev mode).
115             bundleLogger.warn(message, arguments);
116         }
117     }
118 
119     private static boolean isDevMode() {
120         return Boolean.getBoolean(ATLASSIAN_DEV_MODE);
121     }
122 }