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