View Javadoc
1   package it.com.atlassian.plugin.osgi;
2   
3   import com.atlassian.plugin.osgi.AbstractWaitCondition;
4   import com.atlassian.plugin.osgi.PluginInContainerTestBase;
5   import com.atlassian.plugin.osgi.StaticHolder;
6   import com.atlassian.plugin.test.PluginJarBuilder;
7   import com.atlassian.plugin.util.WaitUntil;
8   import com.google.common.base.Function;
9   import com.google.common.collect.Lists;
10  import my.AcceptedClassLoadersRetriever;
11  import my.StrongClassCacheRetriever;
12  import org.hamcrest.FeatureMatcher;
13  import org.hamcrest.Matcher;
14  import org.junit.Test;
15  import org.osgi.framework.BundleReference;
16  
17  import java.io.IOException;
18  import java.util.Arrays;
19  import java.util.Map;
20  import java.util.Set;
21  import java.util.concurrent.Callable;
22  
23  import static java.util.concurrent.TimeUnit.MILLISECONDS;
24  import static org.hamcrest.CoreMatchers.equalTo;
25  import static org.hamcrest.CoreMatchers.hasItems;
26  import static org.hamcrest.CoreMatchers.not;
27  import static org.hamcrest.MatcherAssert.assertThat;
28  import static org.hamcrest.Matchers.empty;
29  
30  public class TestMarkBundleClassesCacheableListener extends PluginInContainerTestBase {
31      @Test
32      public void testBundleClassesAreCacheableInSpring() throws Exception {
33          withSystemProperty("atlassian.enable.spring.strong.cache.bean.metadata", "true", new Callable<Void>() {
34              @Override
35              public Void call() throws Exception {
36                  preparePluginWithClass(AcceptedClassLoadersRetriever.class);
37  
38                  initPluginManager();
39                  final Set<ClassLoader> acceptedClassLoaders = StaticHolder.get();
40  
41                  assertThat(acceptedClassLoaders,
42                          hasModuleClassLoadersWithKeys("test.plugin", "com.atlassian.plugin.osgi.bridge"));
43  
44                  pluginController.disablePlugin("test.plugin");
45                  assertThatSoon(acceptedClassLoaders,
46                          not(hasModuleClassLoadersWithKeys("test.plugin")));
47  
48  
49                  pluginController.enablePlugins("test.plugin");
50                  assertThatSoon(acceptedClassLoaders,
51                          hasModuleClassLoadersWithKeys("test.plugin"));
52  
53                  pluginSystemLifecycle.shutdown();
54                  assertThat(acceptedClassLoaders, empty());
55  
56                  return null;
57              }
58          });
59      }
60  
61      @Test
62      public void testBundleClassesAreNotCacheableByDefaultInSpring() throws Exception {
63          preparePluginWithClass(AcceptedClassLoadersRetriever.class);
64  
65          initPluginManager();
66          final Set<ClassLoader> acceptedClassLoaders = StaticHolder.get();
67  
68          assertThat(acceptedClassLoaders, not(hasModuleClassLoadersWithKeys("test.plugin")));
69  
70          pluginController.disablePlugin("test.plugin");
71          pluginController.enablePlugins("test.plugin");
72          assertThatSoon(acceptedClassLoaders, not(hasModuleClassLoadersWithKeys("test.plugin")));
73  
74          pluginSystemLifecycle.shutdown();
75          assertThat(acceptedClassLoaders, empty());
76      }
77  
78      @Test
79      public void testBundleClassesCacheIsFlushed() throws Exception {
80          withSystemProperty("atlassian.enable.spring.strong.cache.bean.metadata.flush", "true", new Callable<Void>() {
81              @Override
82              public Void call() throws Exception {
83                  preparePluginWithClass(StrongClassCacheRetriever.class);
84  
85                  initPluginManager();
86  
87                  final Map<?, ?> classCache = StaticHolder.get();
88  
89                  assertThat(classCache.entrySet(), empty());
90  
91                  return null;
92              }
93          });
94      }
95  
96      @Test
97      public void testBundleClassesCacheIsNotFlushedByDefault() throws Exception {
98          preparePluginWithClass(StrongClassCacheRetriever.class);
99  
100         initPluginManager();
101 
102         final Map<?, ?> classCache = StaticHolder.get();
103 
104         assertThat(classCache.entrySet(), not(empty()));
105     }
106 
107     private void preparePluginWithClass(Class clazz) throws IOException {
108         new PluginJarBuilder("testClassLoaderCachedChecker")
109                 .addFormattedResource("atlassian-plugin.xml",
110                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
111                         "    <plugin-info>",
112                         "        <version>1.0</version>",
113                         "    </plugin-info>",
114                         "    <component key='obj' class='" + clazz.getName() + "'/>",
115                         "</atlassian-plugin>")
116                 .addClass(clazz)
117                 .build(pluginsDir);
118     }
119 
120     private Matcher<Iterable<ClassLoader>> hasModuleClassLoadersWithKeys(String... keys) {
121         Matcher<ClassLoader>[] matchers = Lists.transform(Arrays.asList(keys), new Function<String, Matcher<ClassLoader>>() {
122             @Override
123             public Matcher<ClassLoader> apply(String key) {
124                 return classLoaderWithBundleName(key);
125             }
126         }).toArray(new Matcher[keys.length]);
127         return hasItems(matchers);
128     }
129 
130     private Matcher<ClassLoader> classLoaderWithBundleName(final String bundleName) {
131         return new FeatureMatcher<ClassLoader, String>(equalTo(bundleName), "a class loader for bundle", "bundle of a class loader") {
132             @Override
133             protected String featureValueOf(ClassLoader actual) {
134                 if (actual instanceof BundleReference) {
135                     BundleReference moduleClassLoader = (BundleReference) actual;
136                     return moduleClassLoader.getBundle().getSymbolicName();
137                 }
138                 return null;
139             }
140         };
141     }
142 
143     private <T> T withSystemProperty(String key, String value, Callable<T> action) throws Exception {
144         String originalValue = System.setProperty(key, value);
145         try {
146             return action.call();
147         } finally {
148             if (originalValue != null) {
149                 System.setProperty(key, originalValue);
150             } else {
151                 System.clearProperty(key);
152             }
153         }
154     }
155 
156     private static <T> void assertThatSoon(final T actual, final Matcher<? super T> matcher) {
157         // The concurrency of these tests is difficult to be precise about, because the tracking of the accepted class loaders is
158         // done via BundleEvent tracking, and there's not an easy way to "happen after" that.
159         WaitUntil.invoke(new AbstractWaitCondition() {
160             @Override
161             public boolean isFinished() {
162                 // Thread safety here is because the underlying CachedIntrospectionResults uses Collections.synchronizedSet, and
163                 // since matcher iterates, we need to synchronize explicitly to avoid ConcurrentModificationException.
164                 synchronized (actual) {
165                     return matcher.matches(actual);
166                 }
167             }
168         }, 500, MILLISECONDS, 50);
169         assertThat(actual, matcher);
170     }
171 }