View Javadoc

1   package com.atlassian.plugin.osgi.spring;
2   
3   import java.lang.reflect.Field;
4   import java.lang.reflect.Method;
5   import java.util.Map;
6   
7   import javax.annotation.Nullable;
8   
9   import com.atlassian.plugin.event.PluginEventListener;
10  import com.atlassian.plugin.event.PluginEventManager;
11  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
12  
13  import com.google.common.base.Throwables;
14  
15  import org.osgi.framework.Bundle;
16  import org.osgi.framework.BundleContext;
17  import org.osgi.framework.BundleEvent;
18  import org.osgi.framework.BundleListener;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  import org.springframework.beans.CachedIntrospectionResults;
22  import org.springframework.beans.factory.DisposableBean;
23  import org.springframework.beans.factory.InitializingBean;
24  
25  /**
26   * This class ensures that all classes in OSGi bundles can be cached by
27   * {@link org.springframework.beans.CachedIntrospectionResults}.
28   *
29   * @since 3.2.17
30   */
31  public class MarkBundleClassesCacheableListener implements BundleListener, InitializingBean, DisposableBean
32  {
33      private static final Logger log = LoggerFactory.getLogger(MarkBundleClassesCacheableListener.class);
34  
35      private final BundleContext bundleContext;
36      private final PluginEventManager pluginEventManager;
37  
38      private final Class<?> bundleImplClass;
39      private final Method getModule;
40      private final Class<?> moduleImplClass;
41      private final Method getClassLoader;
42  
43      private final Object lock = new Object();
44      private boolean active;
45  
46      public MarkBundleClassesCacheableListener(BundleContext bundleContext, PluginEventManager pluginEventManager)
47      {
48          this.bundleContext = bundleContext;
49          this.pluginEventManager = pluginEventManager;
50  
51          Class<?> bundleImplClass = null;
52          Method getModule = null;
53          Class<?> moduleImplClass = null;
54          Method getClassLoader = null;
55          try
56          {
57              bundleImplClass = Bundle.class.getClassLoader().loadClass("org.apache.felix.framework.BundleImpl");
58              getModule = bundleImplClass.getDeclaredMethod("getCurrentModule");
59              getModule.setAccessible(true);
60  
61              moduleImplClass = Bundle.class.getClassLoader().loadClass("org.apache.felix.framework.ModuleImpl");
62              getClassLoader = moduleImplClass.getDeclaredMethod("getClassLoader");
63              getClassLoader.setAccessible(true);
64  
65              active = Boolean.getBoolean("atlassian.enable.spring.strong.cache.bean.metadata");
66          }
67          catch (Exception e)
68          {
69              log.warn("Reflection failed. Performance may suffer.", e);
70          }
71          this.bundleImplClass = bundleImplClass;
72          this.getModule = getModule;
73          this.moduleImplClass = moduleImplClass;
74          this.getClassLoader = getClassLoader;
75      }
76  
77      @Override
78      public void afterPropertiesSet()
79      {
80          bundleContext.addBundleListener(this);
81          pluginEventManager.register(this);
82          for (Bundle bundle : bundleContext.getBundles())
83          {
84              if (bundle.getState() == Bundle.ACTIVE)
85              {
86                  maybeAcceptClassLoader(bundle);
87              }
88          }
89      }
90  
91      @Override
92      public void destroy()
93      {
94          synchronized (lock)
95          {
96              for (Bundle bundle : bundleContext.getBundles())
97              {
98                  if (bundle.getState() == Bundle.ACTIVE)
99                  {
100                     maybeClearClassLoader(bundle);
101                 }
102             }
103             active = false;
104         }
105         // Any events arriving at this stage are ignored as active=false
106         pluginEventManager.unregister(this);
107         bundleContext.removeBundleListener(this);
108     }
109 
110     @Override
111     public void bundleChanged(BundleEvent event)
112     {
113         switch (event.getType()) {
114             case BundleEvent.STARTED: {
115                 maybeAcceptClassLoader(event.getBundle());
116                 break;
117             }
118             case BundleEvent.STOPPED: {
119                 maybeClearClassLoader(event.getBundle());
120                 break;
121             }
122             default:
123                 break;
124         }
125     }
126 
127     private void maybeAcceptClassLoader(Bundle bundle)
128     {
129         synchronized (lock)
130         {
131             ClassLoader bundleClassLoader = getBundleClassLoader(bundle);
132             if (bundleClassLoader != null)
133             {
134                 CachedIntrospectionResults.acceptClassLoader(bundleClassLoader);
135             }
136         }
137 
138     }
139 
140     private void maybeClearClassLoader(Bundle bundle)
141     {
142         synchronized (lock)
143         {
144             ClassLoader bundleClassLoader = getBundleClassLoader(bundle);
145             if (bundleClassLoader != null)
146             {
147                 CachedIntrospectionResults.clearClassLoader(bundleClassLoader);
148             }
149         }
150     }
151 
152     @Nullable
153     private ClassLoader getBundleClassLoader(Bundle bundle)
154     {
155         if (active)
156         {
157             try
158             {
159                 // This reflection should be replaced with
160                 // bundle.adapt(BundleWiring.class).getClassLoader() once available
161                 if (bundleImplClass == bundle.getClass())
162                 {
163                     final Object module = getModule.invoke(bundle);
164                     if (moduleImplClass == module.getClass())
165                     {
166                         return (ClassLoader) getClassLoader.invoke(module);
167                     }
168                 }
169             }
170             catch (Exception e)
171             {
172                 log.warn("Failed to retrieve class loader for bundle '{}'", bundle.getSymbolicName(), e);
173             }
174         }
175         return null;
176     }
177 
178     @PluginEventListener
179     public void onPluginEnabled(PluginFrameworkStartedEvent event)
180     {
181         if(Boolean.getBoolean("atlassian.enable.spring.strong.cache.bean.metadata.flush"))
182         {
183             try
184             {
185                 final Field classCacheField = CachedIntrospectionResults.class.getDeclaredField("classCache");
186                 classCacheField.setAccessible(true);
187                 Map classCache = (Map) classCacheField.get(null);
188 
189                 // Flush class cache after plugin framework has started in
190                 // order to free up entries only needed during start-up
191                 classCache.clear();
192             }
193             catch (Exception e)
194             {
195                 Throwables.propagate(e);
196             }
197         }
198     }
199 }