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