View Javadoc

1   package com.atlassian.plugin.classloader;
2   
3   import junit.framework.TestCase;
4   import com.mockobjects.dynamic.Mock;
5   import com.mockobjects.dynamic.C;
6   import com.atlassian.plugin.PluginAccessor;
7   import com.atlassian.plugin.Plugin;
8   import com.atlassian.plugin.MockPluginAccessor;
9   import com.atlassian.plugin.MockPlugin;
10  
11  import java.net.URL;
12  import java.net.MalformedURLException;
13  import java.util.Collections;
14  import java.util.Collection;
15  import java.util.LinkedList;
16  
17  /**
18   *
19   */
20  public class TestPluginsClassLoader extends TestCase
21  {
22      private static final String TEST_RESOURCE = "log4j.properties";
23  
24      private PluginsClassLoader pluginsClassLoader;
25      private Mock mockPluginAccessor;
26      private Mock mockPlugin;
27      private static final String PLUGIN_KEY = "aPluginKey";
28      private static final String TEST_CLASS = "java.lang.String";
29  
30      protected void setUp() throws Exception
31      {
32          mockPluginAccessor = new Mock(PluginAccessor.class);
33          pluginsClassLoader = new PluginsClassLoader((PluginAccessor) mockPluginAccessor.proxy());
34  
35          mockPlugin = new Mock(Plugin.class);
36      }
37  
38      protected void tearDown() throws Exception
39      {
40          mockPluginAccessor.verify();
41          mockPlugin.verify();
42  
43          mockPluginAccessor = null;
44          pluginsClassLoader = null;
45      }
46  
47      public void testFindResourceWhenIndexed()
48      {
49          final StubClassLoader stubClassLoader = new StubClassLoader();
50          loadPluginResource(stubClassLoader);
51          assertTrue(stubClassLoader.getFindResourceNames().contains(TEST_RESOURCE));
52          stubClassLoader.clear();
53  
54          mockPlugin.expectAndReturn("getKey", PLUGIN_KEY);
55          mockPluginAccessor.matchAndReturn("isPluginEnabled", C.args(C.eq(PLUGIN_KEY)), Boolean.TRUE);
56          mockPlugin.expectAndReturn("getClassLoader", stubClassLoader);
57          pluginsClassLoader.findResource(TEST_RESOURCE);
58  
59          assertTrue(stubClassLoader.getFindResourceNames().contains(TEST_RESOURCE));
60      }
61  
62      public void testFindResourceWhenNotIndexed()
63      {
64          final StubClassLoader stubClassLoader = new StubClassLoader();
65          loadPluginResource(stubClassLoader);
66  
67          assertTrue(stubClassLoader.getFindResourceNames().contains(TEST_RESOURCE));
68      }
69  
70      public void testFindResourceWhenIndexedAndPluginDisabled()
71      {
72          final StubClassLoader stubClassLoader = new StubClassLoader();
73          loadPluginResource(stubClassLoader);
74          assertTrue(stubClassLoader.getFindResourceNames().contains(TEST_RESOURCE));
75          stubClassLoader.clear();
76  
77          mockPluginAccessor.expectAndReturn("getEnabledPlugins", Collections.emptyList());
78          mockPlugin.expectAndReturn("getKey", PLUGIN_KEY);
79          mockPluginAccessor.matchAndReturn("isPluginEnabled", C.args(C.eq(PLUGIN_KEY)), Boolean.FALSE);
80          pluginsClassLoader.findResource(TEST_RESOURCE);
81  
82          assertFalse(stubClassLoader.getFindResourceNames().contains(TEST_RESOURCE));
83      }
84  
85      public void testFindClassWhenIndexed() throws Exception
86      {
87          final StubClassLoader stubClassLoader = new StubClassLoader();
88          loadPluginClass(stubClassLoader);
89          assertTrue(stubClassLoader.getFindClassNames().contains(TEST_CLASS));
90          stubClassLoader.clear();
91  
92          mockPlugin.expectAndReturn("getKey", PLUGIN_KEY);
93          mockPluginAccessor.matchAndReturn("isPluginEnabled", C.args(C.eq(PLUGIN_KEY)), Boolean.TRUE);
94          mockPlugin.expectAndReturn("getClassLoader", stubClassLoader);
95          pluginsClassLoader.findClass(TEST_CLASS);
96  
97          assertTrue(stubClassLoader.getFindClassNames().contains(TEST_CLASS));
98      }
99  
100     public void testFindClassWhenNotIndexed() throws Exception
101     {
102         final StubClassLoader stubClassLoader = new StubClassLoader();
103         loadPluginClass(stubClassLoader);
104 
105         assertTrue(stubClassLoader.getFindClassNames().contains(TEST_CLASS));
106     }
107 
108     public void testFindClassWhenIndexedAndPluginDisabled() throws Exception
109     {
110         final StubClassLoader stubClassLoader = new StubClassLoader();
111         loadPluginClass(stubClassLoader);
112         assertTrue(stubClassLoader.getFindClassNames().contains(TEST_CLASS));
113         stubClassLoader.clear();
114 
115         mockPluginAccessor.expectAndReturn("getEnabledPlugins", Collections.emptyList());
116         mockPlugin.expectAndReturn("getKey", PLUGIN_KEY);
117         mockPluginAccessor.matchAndReturn("isPluginEnabled", C.args(C.eq(PLUGIN_KEY)), Boolean.FALSE);
118         try
119         {
120             pluginsClassLoader.findClass(TEST_CLASS);
121             fail("Plugin is disabled so its ClassLoader should throw ClassNotFoundException");
122         }
123         catch (ClassNotFoundException e)
124         {
125             // good
126         }
127     }
128 
129     public void testGetPluginForClass() throws Exception
130     {
131         final MockPluginAccessor mockPluginAccessor = new MockPluginAccessor();
132         PluginsClassLoader pluginsClassLoader = new PluginsClassLoader(mockPluginAccessor);
133         // Set up plugin A
134         MockClassLoader mockClassLoaderA = new MockClassLoader();
135         mockClassLoaderA.register("com.acme.Ant", String.class);
136         mockClassLoaderA.register("com.acme.Clash", String.class);
137         MockPlugin pluginA = new MockPlugin("A", mockClassLoaderA);
138         mockPluginAccessor.addPlugin(pluginA);
139         // Set up plugin B
140         MockClassLoader mockClassLoaderB = new MockClassLoader();
141         mockClassLoaderB.register("com.acme.Bat", String.class);
142         mockClassLoaderB.register("com.acme.Clash", String.class);
143         MockPlugin pluginB = new MockPlugin("B", mockClassLoaderB);
144         mockPluginAccessor.addPlugin(pluginB);
145 
146         // With both plugins disabled, we should get Clash from no-one
147         assertEquals(null, pluginsClassLoader.getPluginForClass("com.acme.Ant"));
148         assertEquals(null, pluginsClassLoader.getPluginForClass("com.acme.Bat"));
149         assertEquals(null, pluginsClassLoader.getPluginForClass("com.acme.Clash"));
150         assertEquals(null, pluginsClassLoader.getPluginForClass("java.lang.String"));
151 
152         // Enable PluginB and it should give us Bat and Clash from pluginB
153         pluginB.enable();
154         pluginsClassLoader.notifyPluginOrModuleEnabled();
155         assertEquals(null, pluginsClassLoader.getPluginForClass("com.acme.Ant"));
156         assertEquals(pluginB, pluginsClassLoader.getPluginForClass("com.acme.Bat"));
157         assertEquals(pluginB, pluginsClassLoader.getPluginForClass("com.acme.Clash"));
158         assertEquals(null, pluginsClassLoader.getPluginForClass("java.lang.String"));
159 
160         // Enable PluginA and it should give us Clash from pluginB (because it is cached).
161         pluginA.enable();
162         pluginsClassLoader.notifyPluginOrModuleEnabled();
163         assertEquals(pluginA, pluginsClassLoader.getPluginForClass("com.acme.Ant"));
164         assertEquals(pluginB, pluginsClassLoader.getPluginForClass("com.acme.Bat"));
165         assertEquals(pluginB, pluginsClassLoader.getPluginForClass("com.acme.Clash"));
166         assertEquals(null, pluginsClassLoader.getPluginForClass("java.lang.String"));
167 
168         // flush the cache and we get Clash from plugin A instead (because it is earlier in the list).
169         pluginsClassLoader.notifyUninstallPlugin(pluginB);
170         assertEquals(pluginA, pluginsClassLoader.getPluginForClass("com.acme.Ant"));
171         assertEquals(pluginB, pluginsClassLoader.getPluginForClass("com.acme.Bat"));
172         assertEquals(pluginA, pluginsClassLoader.getPluginForClass("com.acme.Clash"));
173         assertEquals(null, pluginsClassLoader.getPluginForClass("java.lang.String"));
174     }
175 
176     private void loadPluginResource(ClassLoader stubClassLoader)
177     {
178         mockPluginAccessor.expectAndReturn("getEnabledPlugins", Collections.singleton(mockPlugin.proxy()));
179         mockPlugin.expectAndReturn("getClassLoader", stubClassLoader);
180         pluginsClassLoader.findResource(TEST_RESOURCE);
181     }
182 
183     private void loadPluginClass(ClassLoader stubClassLoader) throws ClassNotFoundException
184     {
185         mockPluginAccessor.expectAndReturn("getEnabledPlugins", Collections.singleton(mockPlugin.proxy()));
186         mockPlugin.expectAndReturn("getClassLoader", stubClassLoader);
187         pluginsClassLoader.findClass(TEST_CLASS);
188     }
189 
190     private static final class StubClassLoader extends AbstractClassLoader
191     {
192         private final Collection<String> findResourceNames = new LinkedList<String>();
193 
194         public Collection getFindClassNames()
195         {
196             return findClassNames;
197         }
198 
199         private final Collection<String> findClassNames = new LinkedList<String>();
200 
201         public StubClassLoader()
202         {
203             super(null); // no parent classloader needed for tests
204         }
205 
206         protected URL findResource(String name)
207         {
208             findResourceNames.add(name);
209             try
210             {
211                 return new URL("file://"+name);
212             }
213             catch (MalformedURLException e)
214             {
215                 // ignore
216                 return null;
217             }
218         }
219 
220         /**
221          * override the default behavior to bypass the system class loader
222          * for tests
223          */
224         public Class loadClass(String name) throws ClassNotFoundException
225         {
226             return findClass(name);
227         }
228 
229         protected Class findClass(String className) throws ClassNotFoundException
230         {
231             findClassNames.add(className);
232             return String.class;
233         }
234 
235         public Collection getFindResourceNames()
236         {
237             return findResourceNames;
238         }
239 
240         public void clear() {
241             findResourceNames.clear();
242             findClassNames.clear();
243         }
244     }
245 }