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