View Javadoc

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