View Javadoc
1   package it.com.atlassian.plugin.osgi;
2   
3   import com.atlassian.fugue.Pair;
4   import com.atlassian.plugin.DefaultModuleDescriptorFactory;
5   import com.atlassian.plugin.JarPluginArtifact;
6   import com.atlassian.plugin.ModuleDescriptor;
7   import com.atlassian.plugin.PluginArtifact;
8   import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor;
9   import com.atlassian.plugin.event.PluginEventListener;
10  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
11  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
12  import com.atlassian.plugin.osgi.BasicWaitCondition;
13  import com.atlassian.plugin.osgi.DummyModuleDescriptorWithKey;
14  import com.atlassian.plugin.osgi.DummyStateAwareModuleDescriptorWithKey;
15  import com.atlassian.plugin.osgi.EventTrackingModuleDescriptor;
16  import com.atlassian.plugin.osgi.PluginInContainerTestBase;
17  import com.atlassian.plugin.osgi.factory.OsgiPlugin;
18  import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
19  import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
20  import com.atlassian.plugin.test.PluginJarBuilder;
21  import com.atlassian.plugin.util.WaitUntil;
22  import my.FooModule;
23  import my.FooModuleDescriptor;
24  import org.junit.Test;
25  import org.osgi.framework.Bundle;
26  import org.osgi.framework.BundleContext;
27  import org.osgi.framework.ServiceReference;
28  import org.osgi.framework.ServiceRegistration;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.util.Collection;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Set;
36  import java.util.concurrent.atomic.AtomicInteger;
37  
38  import static org.hamcrest.MatcherAssert.assertThat;
39  import static org.hamcrest.Matchers.equalTo;
40  import static org.junit.Assert.assertEquals;
41  import static org.junit.Assert.assertFalse;
42  import static org.junit.Assert.assertNotNull;
43  import static org.junit.Assert.assertTrue;
44  
45  public class TestDynamicPluginModule extends PluginInContainerTestBase {
46      @Test
47      public void testDynamicPluginModule() throws Exception {
48          initPluginManager(new HostComponentProvider() {
49              public void provide(final ComponentRegistrar registrar) {
50              }
51          });
52  
53          final File pluginJar = new PluginJarBuilder("pluginType")
54                  .addFormattedResource("atlassian-plugin.xml",
55                          "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
56                          "    <plugin-info>",
57                          "        <version>1.0</version>",
58                          "    </plugin-info>",
59                          "    <component key='factory' class='foo.MyModuleDescriptorFactory' public='true'>",
60                          "       <interface>com.atlassian.plugin.ModuleDescriptorFactory</interface>",
61                          "    </component>",
62                          "</atlassian-plugin>")
63                  .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
64                  .addFormattedJava("foo.MyModuleDescriptorFactory",
65                          "package foo;",
66                          "public class MyModuleDescriptorFactory extends com.atlassian.plugin.DefaultModuleDescriptorFactory {",
67                          "  public MyModuleDescriptorFactory() {",
68                          "    super();",
69                          "    addModuleDescriptor('foo', MyModuleDescriptor.class);",
70                          "  }",
71                          "}")
72                  .build();
73          final File pluginJar2 = buildDynamicModuleClientJar();
74  
75          pluginController.installPlugin(new JarPluginArtifact(pluginJar));
76          pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
77          final Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin").getModuleDescriptors();
78          assertEquals(1, descriptors.size());
79          final ModuleDescriptor<?> descriptor = descriptors.iterator()
80                  .next();
81          assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
82      }
83  
84      @Test
85      public void testDynamicPluginModuleUsingModuleTypeDescriptorWithReinstall() throws Exception {
86          initPluginManager();
87  
88          final File pluginJar = new PluginJarBuilder("pluginType")
89                  .addFormattedResource("atlassian-plugin.xml",
90                          "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
91                          "    <plugin-info>",
92                          "        <version>1.0</version>",
93                          "    </plugin-info>",
94                          "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
95                          "</atlassian-plugin>")
96                  .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
97                  .build();
98          final File pluginJar2 = buildDynamicModuleClientJar();
99  
100         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
101         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
102         assertTrue(waitForDynamicModuleEnabled());
103 
104         // uninstall the module - the test plugin modules should revert back to Unrecognised
105         pluginController.uninstall(pluginAccessor.getPlugin("test.plugin.module"));
106         WaitUntil.invoke(new BasicWaitCondition() {
107             public boolean isFinished() {
108                 ModuleDescriptor<?> descriptor = pluginAccessor.getPlugin("test.plugin")
109                         .getModuleDescriptors()
110                         .iterator()
111                         .next();
112                 boolean enabled = pluginAccessor.isPluginModuleEnabled(descriptor.getCompleteKey());
113                 return descriptor
114                         .getClass()
115                         .getSimpleName()
116                         .equals("UnrecognisedModuleDescriptor")
117                         && !enabled;
118             }
119         });
120         // reinstall the module - the test plugin modules should be correct again
121         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
122         assertTrue(waitForDynamicModuleEnabled());
123     }
124 
125     @Test
126     public void testDynamicPluginModuleUsingModuleTypeDescriptorWithImmediateReinstall() throws Exception {
127         initPluginManager();
128 
129         final File pluginJar = new PluginJarBuilder("pluginType")
130                 .addFormattedResource("atlassian-plugin.xml",
131                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
132                         "    <plugin-info>",
133                         "        <version>1.0</version>",
134                         "    </plugin-info>",
135                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
136                         "</atlassian-plugin>")
137                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
138                 .build();
139         final File pluginJar2 = buildDynamicModuleClientJar();
140 
141         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
142         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
143         assertTrue(waitForDynamicModuleEnabled());
144 
145         PluginModuleDisabledListener disabledListener = new PluginModuleDisabledListener("dum2");
146         PluginModuleEnabledListener enabledListener = new PluginModuleEnabledListener("dum2");
147         pluginEventManager.register(disabledListener);
148         pluginEventManager.register(enabledListener);
149 
150         // reinstall the module - the test plugin modules should be correct again
151         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
152         assertTrue(waitForDynamicModuleEnabled());
153 
154         assertEquals(1, enabledListener.called);
155         assertEquals(1, disabledListener.called);
156     }
157 
158     @Test
159     public void testDynamicPluginModuleUsingModuleTypeDescriptorWithImmediateReinstallOfBoth() throws Exception {
160         initPluginManager();
161 
162         final File pluginJar = new PluginJarBuilder("pluginType")
163                 .addFormattedResource("atlassian-plugin.xml",
164                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
165                         "    <plugin-info>",
166                         "        <version>1.0</version>",
167                         "    </plugin-info>",
168                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
169                         "</atlassian-plugin>")
170                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
171                 .build();
172         final File pluginJar2 = buildDynamicModuleClientJar();
173 
174         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
175         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
176 
177         assertTrue(waitForDynamicModuleEnabled());
178 
179         PluginModuleDisabledListener disabledListener = new PluginModuleDisabledListener("dum2");
180         PluginModuleEnabledListener enabledListener = new PluginModuleEnabledListener("dum2");
181         pluginEventManager.register(disabledListener);
182         pluginEventManager.register(enabledListener);
183 
184         // reinstall the module - the test plugin modules should be correct again
185         pluginController.installPlugins(new JarPluginArtifact(pluginJar2), new JarPluginArtifact(pluginJar));
186         assertTrue(waitForDynamicModuleEnabled());
187 
188         assertEquals(pluginAccessor.getPluginModule("test.plugin:dum2").getClass(), pluginAccessor.getPlugin("test.plugin.module").<Object>loadClass("foo.MyModuleDescriptor", null));
189 
190         assertEquals(1, enabledListener.called);
191         assertEquals(1, disabledListener.called);
192     }
193 
194     private File buildDynamicModuleClientJar() throws IOException {
195         return new PluginJarBuilder("fooUser")
196                 .addFormattedResource("atlassian-plugin.xml",
197                         "<atlassian-plugin name='Test 2' key='test.plugin' pluginsVersion='2'>",
198                         "    <plugin-info>",
199                         "        <version>1.0</version>",
200                         "    </plugin-info>",
201                         "    <foo key='dum2'/>",
202                         "</atlassian-plugin>")
203                 .build();
204     }
205 
206     private boolean waitForDynamicModuleEnabled() {
207         return WaitUntil.invoke(new BasicWaitCondition() {
208             public boolean isFinished() {
209                 return pluginAccessor.getPlugin("test.plugin").getModuleDescriptors().iterator().next().getClass().getSimpleName().equals("MyModuleDescriptor");
210             }
211         });
212     }
213 
214     @Test
215     public void testUpgradeOfBundledPluginWithDynamicModule() throws Exception {
216         final File pluginJar = new PluginJarBuilder("pluginType")
217                 .addFormattedResource("atlassian-plugin.xml",
218                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
219                         "    <plugin-info>",
220                         "        <version>1.0</version>",
221                         "    </plugin-info>",
222                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
223                         "</atlassian-plugin>")
224                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
225                 .build();
226 
227         final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(hostContainer);
228         initBundlingPluginManager(factory, pluginJar);
229         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
230 
231         final File pluginClientOld = buildDynamicModuleClientJar();
232         final File pluginClientNew = new PluginJarBuilder("fooUser")
233                 .addFormattedResource("atlassian-plugin.xml",
234                         "<atlassian-plugin name='Test 2' key='test.plugin' pluginsVersion='2'>",
235                         "    <plugin-info>",
236                         "        <version>2.0</version>",
237                         "    </plugin-info>",
238                         "    <foo key='dum2'/>",
239                         "</atlassian-plugin>")
240                 .build();
241         pluginController.installPlugins(new JarPluginArtifact(pluginClientOld), new JarPluginArtifact(pluginClientNew));
242 
243         assertTrue(waitForDynamicModuleEnabled());
244 
245         assertEquals(2, pluginAccessor.getEnabledPlugins().size());
246         assertEquals("2.0", pluginAccessor.getPlugin("test.plugin").getPluginInformation().getVersion());
247     }
248 
249     @Test
250     public void testDynamicPluginModuleNotLinkToAllPlugins() throws Exception {
251         new PluginJarBuilder("pluginType")
252                 .addFormattedResource("atlassian-plugin.xml",
253                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
254                         "    <plugin-info>",
255                         "        <version>1.0</version>",
256                         "    </plugin-info>",
257                         "    <module-type key='foo' class='foo.MyModuleDescriptor'/>",
258                         "</atlassian-plugin>")
259                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
260                 .build(pluginsDir);
261         new PluginJarBuilder("fooUser")
262                 .addFormattedResource("atlassian-plugin.xml",
263                         "<atlassian-plugin name='Test 2' key='test.plugin' pluginsVersion='2'>",
264                         "    <plugin-info>",
265                         "        <version>1.0</version>",
266                         "    </plugin-info>",
267                         "    <foo key='dum2'/>",
268                         "</atlassian-plugin>")
269                 .build(pluginsDir);
270         new PluginJarBuilder("foootherUser")
271                 .addPluginInformation("unusing.plugin", "Unusing plugin", "1.0")
272                 .build(pluginsDir);
273 
274         initPluginManager(new HostComponentProvider() {
275             public void provide(final ComponentRegistrar registrar) {
276             }
277         });
278 
279         assertEquals("MyModuleDescriptor", pluginAccessor.getPlugin("test.plugin").getModuleDescriptor("dum2").getClass().getSimpleName());
280         Set<String> deps = findDependentBundles(((OsgiPlugin) pluginAccessor.getPlugin("test.plugin.module")).getBundle());
281         assertTrue(deps.contains("test.plugin"));
282         assertFalse(deps.contains("unusing.plugin"));
283     }
284 
285     private Set<String> findDependentBundles(Bundle bundle) {
286         Set<String> deps = new HashSet<String>();
287         final ServiceReference[] registeredServices = bundle.getRegisteredServices();
288         if (registeredServices == null) {
289             return deps;
290         }
291 
292         for (final ServiceReference serviceReference : registeredServices) {
293             final Bundle[] usingBundles = serviceReference.getUsingBundles();
294             if (usingBundles == null) {
295                 continue;
296             }
297             for (final Bundle usingBundle : usingBundles) {
298                 deps.add(usingBundle.getSymbolicName());
299             }
300         }
301         return deps;
302     }
303 
304     @Test
305     public void testDynamicPluginModuleUsingModuleTypeDescriptor() throws Exception {
306         initPluginManager(new HostComponentProvider() {
307             public void provide(final ComponentRegistrar registrar) {
308             }
309         });
310 
311         final File pluginJar = new PluginJarBuilder("pluginType")
312                 .addFormattedResource("atlassian-plugin.xml",
313                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
314                         "    <plugin-info>",
315                         "        <version>1.0</version>",
316                         "    </plugin-info>",
317                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
318                         "</atlassian-plugin>")
319                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
320                 .build();
321         final File pluginJar2 = buildDynamicModuleClientJar();
322 
323         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
324         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
325         WaitUntil.invoke(new BasicWaitCondition() {
326             public boolean isFinished() {
327                 return pluginAccessor.getPlugin("test.plugin")
328                         .getModuleDescriptor("dum2")
329                         .getClass()
330                         .getSimpleName()
331                         .equals("MyModuleDescriptor");
332             }
333         });
334         final Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
335                 .getModuleDescriptors();
336         assertEquals(1, descriptors.size());
337         final ModuleDescriptor<?> descriptor = descriptors.iterator()
338                 .next();
339         assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
340     }
341 
342     @Test
343     public void testDynamicPluginModuleWithClientAndHostEnabledSimultaneouslyCheckEvents() throws Exception {
344         initPluginManager();
345 
346         final File pluginJar = new PluginJarBuilder("pluginType")
347                 .addFormattedResource("atlassian-plugin.xml",
348                         "<atlassian-plugin name='Test' key='host' pluginsVersion='2'>",
349                         "    <plugin-info>",
350                         "        <version>1.0</version>",
351                         "    </plugin-info>",
352                         "    <component key='foo' class='foo.MyModuleDescriptorFactory' public='true'>",
353                         "       <interface>com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory</interface>",
354                         "    </component>",
355                         "</atlassian-plugin>")
356                 .addFormattedJava("foo.MyModuleDescriptorFactory",
357                         "package foo;",
358                         "public class MyModuleDescriptorFactory extends com.atlassian.plugin.DefaultModuleDescriptorFactory ",
359                         "                                       implements com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory{",
360                         "  public MyModuleDescriptorFactory() throws Exception{",
361                         "    super();",
362                         "    Thread.sleep(500);",
363                         "    System.out.println('starting descriptor factory');",
364                         "    addModuleDescriptor('foo', com.atlassian.plugin.osgi.EventTrackingModuleDescriptor.class);",
365                         "  }",
366                         "  public Iterable getModuleDescriptorKeys() {",
367                         "    return java.util.Collections.singleton('foo');",
368                         "  }",
369                         "  public java.util.Set getModuleDescriptorClasses() {",
370                         "    return java.util.Collections.singleton(com.atlassian.plugin.osgi.EventTrackingModuleDescriptor.class);",
371                         "  }",
372                         "}")
373                 .build();
374         final File pluginJar2 = new PluginJarBuilder("fooUser")
375                 .addFormattedResource("atlassian-plugin.xml",
376                         "<atlassian-plugin name='Test 2' key='client' pluginsVersion='2'>",
377                         "    <plugin-info>",
378                         "        <version>1.0</version>",
379                         "    </plugin-info>",
380                         "    <foo key='dum2'/>",
381                         "</atlassian-plugin>")
382                 .build();
383 
384         pluginController.installPlugins(new JarPluginArtifact(pluginJar), new JarPluginArtifact(pluginJar2));
385 
386         WaitUntil.invoke(new BasicWaitCondition() {
387             public boolean isFinished() {
388                 return pluginAccessor.getPlugin("client").getModuleDescriptor("dum2").getClass().getSimpleName().equals("EventTrackingModuleDescriptor");
389             }
390         });
391         EventTrackingModuleDescriptor desc = (EventTrackingModuleDescriptor) pluginAccessor.getPlugin("client").getModuleDescriptor("dum2");
392         assertEquals(1, desc.getEnabledCount());
393     }
394 
395     @Test
396     public void testDynamicPluginModuleUsingModuleTypeDescriptorAndComponentInjection() throws Exception {
397         initPluginManager(new HostComponentProvider() {
398             public void provide(final ComponentRegistrar registrar) {
399             }
400         });
401 
402         final File pluginJar = new PluginJarBuilder("pluginType")
403                 .addFormattedResource("atlassian-plugin.xml",
404                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
405                         "    <plugin-info>",
406                         "        <version>1.0</version>",
407                         "    </plugin-info>",
408                         "    <component key='comp' class='foo.MyComponent' />",
409                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
410                         "</atlassian-plugin>")
411                 .addFormattedJava("foo.MyComponent", "package foo;", "public class MyComponent {", "}")
412                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
413                 .build();
414         final File pluginJar2 = buildDynamicModuleClientJar();
415 
416         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
417         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
418         assertTrue(waitForDynamicModuleEnabled());
419         final Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
420                 .getModuleDescriptors();
421         assertEquals(1, descriptors.size());
422         final ModuleDescriptor<?> descriptor = descriptors.iterator()
423                 .next();
424         assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
425     }
426 
427     @Test
428     public void testDynamicPluginModuleUsingModuleTypeDescriptorAfterTheFact() throws Exception {
429         initPluginManager(new HostComponentProvider() {
430             public void provide(final ComponentRegistrar registrar) {
431             }
432         });
433 
434         final File pluginJar = new PluginJarBuilder("pluginType")
435                 .addFormattedResource("atlassian-plugin.xml",
436                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
437                         "    <plugin-info>",
438                         "        <version>1.0</version>",
439                         "    </plugin-info>",
440                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
441                         "</atlassian-plugin>")
442                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
443                 .build();
444         final File pluginJar2 = buildDynamicModuleClientJar();
445 
446         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
447         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
448         waitForDynamicModuleEnabled();
449 
450         Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
451                 .getModuleDescriptors();
452         assertEquals(1, descriptors.size());
453         ModuleDescriptor<?> descriptor = descriptors.iterator()
454                 .next();
455         assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
456 
457         pluginController.uninstall(pluginAccessor.getPlugin("test.plugin.module"));
458         WaitUntil.invoke(new BasicWaitCondition() {
459             public boolean isFinished() {
460                 return pluginAccessor.getPlugin("test.plugin")
461                         .getModuleDescriptors()
462                         .iterator()
463                         .next()
464                         .getClass()
465                         .getSimpleName()
466                         .equals("UnrecognisedModuleDescriptor");
467             }
468         });
469         descriptors = pluginAccessor.getPlugin("test.plugin")
470                 .getModuleDescriptors();
471         assertEquals(1, descriptors.size());
472         descriptor = descriptors.iterator()
473                 .next();
474         assertEquals("UnrecognisedModuleDescriptor", descriptor.getClass().getSimpleName());
475 
476         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
477         descriptors = pluginAccessor.getPlugin("test.plugin")
478                 .getModuleDescriptors();
479         assertEquals(1, descriptors.size());
480         descriptor = descriptors.iterator()
481                 .next();
482         assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
483     }
484 
485     @Test
486     public void testDynamicPluginModuleUsingModuleTypeDescriptorAfterTheFactWithException() throws Exception {
487         initPluginManager(new HostComponentProvider() {
488             public void provide(final ComponentRegistrar registrar) {
489             }
490         });
491 
492         final File pluginJar = new PluginJarBuilder("pluginType")
493                 .addFormattedResource("atlassian-plugin.xml",
494                         "<atlassian-plugin name='Test' key='test.plugin.module' pluginsVersion='2'>",
495                         "    <plugin-info>",
496                         "        <version>1.0</version>",
497                         "    </plugin-info>",
498                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
499                         "</atlassian-plugin>")
500                 .addFormattedJava("foo.MyModuleDescriptor",
501                         "package foo;",
502                         "import com.atlassian.plugin.module.ModuleFactory;",
503                         "public class MyModuleDescriptor extends com.atlassian.plugin.descriptors.AbstractModuleDescriptor {",
504                         "  public MyModuleDescriptor() {",
505                         "    super(ModuleFactory.LEGACY_MODULE_FACTORY);",
506                         "    throw new RuntimeException('error loading module');",
507                         "  }",
508                         "  public Object getModule(){return null;}",
509                         "}")
510                 .build();
511         final File pluginJar2 = buildDynamicModuleClientJar();
512 
513         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
514         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
515         assertTrue(WaitUntil.invoke(new BasicWaitCondition() {
516             public boolean isFinished() {
517                 UnrecognisedModuleDescriptor des = (UnrecognisedModuleDescriptor) pluginAccessor.getPlugin("test.plugin").getModuleDescriptor("dum2");
518                 return des.getErrorText().contains("error loading module");
519             }
520         }));
521     }
522 
523     @Test
524     public void testDynamicPluginModuleUsingModuleTypeDescriptorInSamePlugin() throws Exception {
525         initPluginManager(new HostComponentProvider() {
526             public void provide(final ComponentRegistrar registrar) {
527             }
528         });
529 
530         final File pluginJar = new PluginJarBuilder("pluginType")
531                 .addFormattedResource("atlassian-plugin.xml",
532                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
533                         "    <plugin-info>",
534                         "        <version>1.0</version>",
535                         "    </plugin-info>",
536                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
537                         "    <foo key='dum2' />",
538                         "</atlassian-plugin>")
539                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
540                 .build();
541 
542         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
543         WaitUntil.invoke(new BasicWaitCondition() {
544             public boolean isFinished() {
545                 return pluginAccessor.getPlugin("test.plugin")
546                         .getModuleDescriptor("dum2")
547                         .getClass()
548                         .getSimpleName()
549                         .equals("MyModuleDescriptor");
550             }
551         });
552         final Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
553                 .getModuleDescriptors();
554         assertEquals(2, descriptors.size());
555         final ModuleDescriptor<?> descriptor = pluginAccessor.getPlugin("test.plugin")
556                 .getModuleDescriptor("dum2");
557         assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
558     }
559 
560     @Test
561     public void testDynamicPluginModuleUsingModuleTypeDescriptorInSamePluginWithRestart() throws Exception {
562         initPluginManager();
563 
564         final File pluginJar = new PluginJarBuilder("pluginType")
565                 .addFormattedResource("atlassian-plugin.xml",
566                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
567                         "    <plugin-info>",
568                         "        <version>1.0</version>",
569                         "    </plugin-info>",
570                         "    <module-type key='foo' class='foo.MyModuleDescriptor' />",
571                         "    <foo key='dum2' />",
572                         "</atlassian-plugin>")
573                 .addFormattedJava(getMyModuleDescriptorClass().left(), getMyModuleDescriptorClass().right())
574                 .build();
575 
576         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
577         WaitUntil.invoke(new BasicWaitCondition() {
578             public boolean isFinished() {
579                 return pluginAccessor.getPlugin("test.plugin")
580                         .getModuleDescriptor("dum2")
581                         .getClass()
582                         .getSimpleName()
583                         .equals("MyModuleDescriptor");
584             }
585         });
586         Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
587                 .getModuleDescriptors();
588         assertEquals(2, descriptors.size());
589         ModuleDescriptor<?> descriptor = pluginAccessor.getPlugin("test.plugin")
590                 .getModuleDescriptor("dum2");
591         assertEquals("MyModuleDescriptor", descriptor.getClass().getSimpleName());
592 
593         PluginModuleDisabledListener disabledListener = new PluginModuleDisabledListener("dum2");
594         PluginModuleEnabledListener enabledListener = new PluginModuleEnabledListener("dum2");
595         pluginEventManager.register(disabledListener);
596         pluginEventManager.register(enabledListener);
597 
598         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
599         WaitUntil.invoke(new BasicWaitCondition() {
600             public boolean isFinished() {
601                 return pluginAccessor.getPlugin("test.plugin")
602                         .getModuleDescriptor("dum2")
603                         .getClass()
604                         .getSimpleName()
605                         .equals("MyModuleDescriptor");
606             }
607         });
608         descriptors = pluginAccessor.getPlugin("test.plugin")
609                 .getModuleDescriptors();
610         assertEquals(2, descriptors.size());
611         ModuleDescriptor<?> newdescriptor = pluginAccessor.getPlugin("test.plugin")
612                 .getModuleDescriptor("dum2");
613         assertEquals("MyModuleDescriptor", newdescriptor.getClass().getSimpleName());
614         assertTrue(descriptor.getClass() != newdescriptor.getClass());
615         assertEquals(1, disabledListener.called);
616         assertEquals(1, enabledListener.called);
617     }
618 
619     @Test
620     public void testDynamicModuleDescriptor() throws Exception {
621         initPluginManager(null);
622 
623         final File pluginJar = new PluginJarBuilder("pluginType").addPluginInformation("test.plugin", "foo", "1.0")
624                 .build();
625 
626         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
627         final BundleContext ctx = ((OsgiPlugin) pluginAccessor.getPlugin("test.plugin")).getBundle()
628                 .getBundleContext();
629         final ServiceRegistration reg = ctx.registerService(ModuleDescriptor.class.getName(), new DummyModuleDescriptorWithKey(), null);
630 
631         final Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
632                 .getModuleDescriptors();
633         assertEquals(1, descriptors.size());
634         final ModuleDescriptor<?> descriptor = descriptors.iterator()
635                 .next();
636         assertEquals("DummyModuleDescriptorWithKey", descriptor.getClass().getSimpleName());
637         List<DummyModuleDescriptorWithKey> list = pluginAccessor.getEnabledModuleDescriptorsByClass(DummyModuleDescriptorWithKey.class);
638         assertEquals(1, list.size());
639         reg.unregister();
640         list = pluginAccessor.getEnabledModuleDescriptorsByClass(DummyModuleDescriptorWithKey.class);
641         assertEquals(0, list.size());
642     }
643 
644     @Test
645     public void testStateAwareDynamicModuleDescriptor() throws Exception {
646         initPluginManager(null);
647 
648         final File pluginJar = new PluginJarBuilder("pluginType").addPluginInformation("test.plugin", "foo", "1.0")
649                 .build();
650 
651         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
652         final BundleContext ctx = ((OsgiPlugin) pluginAccessor.getPlugin("test.plugin")).getBundle()
653                 .getBundleContext();
654 
655         AtomicInteger timesEnabled = new AtomicInteger();
656         AtomicInteger timesDisabled = new AtomicInteger();
657         final ServiceRegistration reg = ctx.registerService(ModuleDescriptor.class.getName(),
658                 new DummyStateAwareModuleDescriptorWithKey(timesEnabled, timesDisabled), null);
659 
660         assertThat(timesEnabled.get(), equalTo(Integer.valueOf(1)));
661         reg.unregister();
662         assertThat(timesDisabled.get(), equalTo(Integer.valueOf(1)));
663     }
664 
665     @Test
666     public void testDynamicModuleDescriptorIsolatedToPlugin() throws Exception {
667         initPluginManager(null);
668 
669         final File pluginJar = new PluginJarBuilder("pluginType").addPluginInformation("test.plugin", "foo", "1.0")
670                 .build();
671 
672         pluginController.installPlugin(new JarPluginArtifact(pluginJar));
673         final BundleContext ctx = ((OsgiPlugin) pluginAccessor.getPlugin("test.plugin")).getBundle()
674                 .getBundleContext();
675         ctx.registerService(ModuleDescriptor.class.getName(), new DummyModuleDescriptorWithKey(), null);
676 
677         final File pluginJar2 = new PluginJarBuilder("pluginType").addPluginInformation("test.plugin2", "foo", "1.0")
678                 .build();
679         pluginController.installPlugin(new JarPluginArtifact(pluginJar2));
680         final BundleContext ctx2 = ((OsgiPlugin) pluginAccessor.getPlugin("test.plugin2")).getBundle()
681                 .getBundleContext();
682         final ServiceRegistration reg2 = ctx2.registerService(ModuleDescriptor.class.getName(), new DummyModuleDescriptorWithKey(), null);
683 
684         Collection<ModuleDescriptor<?>> descriptors = pluginAccessor.getPlugin("test.plugin")
685                 .getModuleDescriptors();
686         assertEquals(1, descriptors.size());
687         final ModuleDescriptor<?> descriptor = descriptors.iterator()
688                 .next();
689         assertEquals("DummyModuleDescriptorWithKey", descriptor.getClass().getSimpleName());
690         List<DummyModuleDescriptorWithKey> list = pluginAccessor.getEnabledModuleDescriptorsByClass(DummyModuleDescriptorWithKey.class);
691         assertEquals(2, list.size());
692         reg2.unregister();
693         list = pluginAccessor.getEnabledModuleDescriptorsByClass(DummyModuleDescriptorWithKey.class);
694         assertEquals(1, list.size());
695         descriptors = pluginAccessor.getPlugin("test.plugin")
696                 .getModuleDescriptors();
697         assertEquals(1, descriptors.size());
698     }
699 
700     @Test
701     public void testInstallUninstallInstallWithModuleTypePlugin() throws Exception {
702         final PluginArtifact moduleTypeProviderArtifact = new JarPluginArtifact(new PluginJarBuilder()
703                 .addFormattedResource(
704                         "atlassian-plugin.xml",
705                         "<atlassian-plugin name='Foo Module Type Provider' key='test.fooModuleTypeProvider' pluginsVersion='2'>",
706                         "    <plugin-info>",
707                         "        <version>1.0</version>",
708                         "        <bundle-instructions>",
709                         "            <Export-Package>my</Export-Package>",
710                         "        </bundle-instructions>",
711                         "    </plugin-info>",
712                         "    <module-type key='foo-module' class='my.FooModuleDescriptor'/>",
713                         "</atlassian-plugin>")
714                 .addClass(FooModule.class)
715                 .addClass(FooModuleDescriptor.class)
716                 .build());
717         final PluginArtifact moduleTypeImplementerArtifact = new JarPluginArtifact(new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
718                 "<atlassian-plugin name='Foo Module Type Implementer' key='test.fooModuleTypeImplementer' pluginsVersion='2'>",
719                 "    <plugin-info>",
720                 "        <version>1.0</version>",
721                 "        <bundle-instructions>",
722                 "            <Import-Package>my</Import-Package>",
723                 "        </bundle-instructions>",
724                 "    </plugin-info>",
725                 "    <foo-module key='myFooModule' class='my.impl.FooModuleImpl'/>",
726                 "</atlassian-plugin>")
727                 .addFormattedJava(
728                         "my.impl.FooModuleImpl",
729                         "package my.impl;",
730                         "",
731                         "import my.FooModule;",
732                         "",
733                         "public class FooModuleImpl implements FooModule {",
734                         "}")
735                 .build());
736 
737         initPluginManager();
738         pluginController.installPlugin(moduleTypeProviderArtifact);
739         pluginController.installPlugin(moduleTypeImplementerArtifact);
740 
741         final long foo1InitialisationTime = assertFooImplEnabledAndGetInitialisationTime();
742 
743         pluginController.installPlugin(moduleTypeProviderArtifact);
744 
745         final long foo2InitialisationTime = assertFooImplEnabledAndGetInitialisationTime();
746 
747         assertTrue("FooModuleImpl implements old version of FooModule", foo2InitialisationTime > foo1InitialisationTime);
748     }
749 
750     private long assertFooImplEnabledAndGetInitialisationTime() throws IllegalAccessException, NoSuchFieldException {
751         assertTrue(pluginAccessor.isPluginModuleEnabled("test.fooModuleTypeProvider:foo-module"));
752         assertTrue(pluginAccessor.isPluginModuleEnabled("test.fooModuleTypeImplementer:myFooModule"));
753         final ModuleDescriptor<?> fooDescriptor = pluginAccessor.getEnabledPluginModule("test.fooModuleTypeImplementer:myFooModule");
754         assertNotNull(fooDescriptor);
755         final Object foo = fooDescriptor.getModule();
756         assertNotNull(foo);
757         final Class<? extends Object> fooClass = foo.getClass();
758         assertTrue(fooClass.getName().equals("my.impl.FooModuleImpl"));
759         return fooClass.getField("INITIALISATION_TIME").getLong(foo);
760     }
761 
762     private static Pair<String, String[]> getMyModuleDescriptorClass() {
763         return Pair.pair(
764                 "foo.MyModuleDescriptor",
765                 new String[]{
766                         "package foo;",
767                         "import com.atlassian.plugin.module.ModuleFactory;",
768                         "public class MyModuleDescriptor extends com.atlassian.plugin.descriptors.AbstractModuleDescriptor {",
769                         "  public MyModuleDescriptor(){ super(ModuleFactory.LEGACY_MODULE_FACTORY); }",
770                         "  public Object getModule(){return null;}",
771                         "}"
772                 });
773     }
774 
775     public static class PluginModuleEnabledListener {
776         public volatile int called;
777         private final String key;
778 
779         public PluginModuleEnabledListener(String key) {
780             this.key = key;
781         }
782 
783         @PluginEventListener
784         public void onEnable(PluginModuleEnabledEvent event) {
785             if (event.getModule().getKey().equals(key)) {
786                 called++;
787             }
788         }
789     }
790 
791     public static class PluginModuleDisabledListener {
792         public volatile int called;
793         private final String key;
794 
795         public PluginModuleDisabledListener(String key) {
796             this.key = key;
797         }
798 
799         @PluginEventListener
800         public void onDisable(PluginModuleDisabledEvent event) {
801             if (event.getModule().getKey().equals(key)) {
802                 called++;
803             }
804         }
805     }
806 }