View Javadoc
1   package com.atlassian.plugin.osgi.spring;
2   
3   import com.atlassian.plugin.event.PluginEventListener;
4   import com.atlassian.plugin.event.PluginEventManager;
5   import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
6   import com.google.common.base.Throwables;
7   import org.osgi.framework.Bundle;
8   import org.osgi.framework.BundleContext;
9   import org.osgi.framework.BundleEvent;
10  import org.osgi.framework.BundleListener;
11  import org.osgi.framework.wiring.BundleWiring;
12  import org.springframework.beans.CachedIntrospectionResults;
13  import org.springframework.beans.factory.DisposableBean;
14  import org.springframework.beans.factory.InitializingBean;
15  
16  import javax.annotation.Nonnull;
17  import javax.annotation.Nullable;
18  import java.lang.reflect.Field;
19  import java.util.Map;
20  
21  /**
22   * This class ensures that all classes in OSGi bundles can be cached by
23   * {@link org.springframework.beans.CachedIntrospectionResults}.
24   *
25   * @since 3.2.17
26   */
27  public class MarkBundleClassesCacheableListener implements BundleListener, InitializingBean, DisposableBean {
28      private final BundleContext bundleContext;
29      private final PluginEventManager pluginEventManager;
30  
31      private final Object lock = new Object();
32      private boolean active = Boolean.getBoolean("atlassian.enable.spring.strong.cache.bean.metadata");
33  
34      public MarkBundleClassesCacheableListener(BundleContext bundleContext, PluginEventManager pluginEventManager) {
35          this.bundleContext = bundleContext;
36          this.pluginEventManager = pluginEventManager;
37      }
38  
39      @Override
40      public void afterPropertiesSet() {
41          bundleContext.addBundleListener(this);
42          pluginEventManager.register(this);
43          for (Bundle bundle : bundleContext.getBundles()) {
44              if (bundle.getState() == Bundle.ACTIVE) {
45                  maybeAcceptClassLoader(bundle);
46              }
47          }
48      }
49  
50      @Override
51      public void destroy() {
52          synchronized (lock) {
53              for (Bundle bundle : bundleContext.getBundles()) {
54                  if ((bundle.getState() & (Bundle.ACTIVE | Bundle.STOPPING)) != 0) {
55                      maybeClearClassLoader(bundle);
56                  }
57              }
58              active = false;
59          }
60          // Any events arriving at this stage are ignored as active=false
61          pluginEventManager.unregister(this);
62          bundleContext.removeBundleListener(this);
63      }
64  
65      @Override
66      public void bundleChanged(@Nonnull BundleEvent event) {
67          switch (event.getType()) {
68              case BundleEvent.STARTED: {
69                  maybeAcceptClassLoader(event.getBundle());
70                  break;
71              }
72              case BundleEvent.STOPPED: {
73                  maybeClearClassLoader(event.getBundle());
74                  break;
75              }
76              default:
77                  break;
78          }
79      }
80  
81      private void maybeAcceptClassLoader(@Nonnull Bundle bundle) {
82          if (bundle.getBundleId() == 0) {
83              return; // No need to add system bundle
84          }
85  
86          synchronized (lock) {
87              ClassLoader bundleClassLoader = getBundleClassLoader(bundle);
88              if (bundleClassLoader != null) {
89                  CachedIntrospectionResults.acceptClassLoader(bundleClassLoader);
90              }
91          }
92  
93      }
94  
95      private void maybeClearClassLoader(@Nonnull Bundle bundle) {
96          synchronized (lock) {
97              ClassLoader bundleClassLoader = getBundleClassLoader(bundle);
98              if (bundleClassLoader != null) {
99                  CachedIntrospectionResults.clearClassLoader(bundleClassLoader);
100             }
101         }
102     }
103 
104     @Nullable
105     private ClassLoader getBundleClassLoader(@Nonnull Bundle bundle) {
106         if (active) {
107             final BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
108             if (bundleWiring != null) {
109                 return bundleWiring.getClassLoader();
110             }
111         }
112         return null;
113     }
114 
115     @PluginEventListener
116     public void onPluginEnabled(PluginFrameworkStartedEvent event) {
117         if (Boolean.getBoolean("atlassian.enable.spring.strong.cache.bean.metadata.flush")) {
118             try {
119                 final Field classCacheField = CachedIntrospectionResults.class.getDeclaredField("strongClassCache");
120                 classCacheField.setAccessible(true);
121                 Map classCache = (Map) classCacheField.get(null);
122 
123                 // Flush class cache after plugin framework has started in
124                 // order to free up entries only needed during start-up
125                 classCache.clear();
126             } catch (Exception e) {
127                 Throwables.propagate(e);
128             }
129         }
130     }
131 }