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.Plugin;
6   import com.atlassian.plugin.PluginState;
7   import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
8   import com.atlassian.plugin.impl.UnloadablePlugin;
9   import com.atlassian.plugin.osgi.AnotherInterface;
10  import com.atlassian.plugin.osgi.BasicWaitCondition;
11  import com.atlassian.plugin.osgi.BooleanFlag;
12  import com.atlassian.plugin.osgi.Callable2;
13  import com.atlassian.plugin.osgi.Callable3;
14  import com.atlassian.plugin.osgi.DefaultBooleanFlag;
15  import com.atlassian.plugin.osgi.DummyModuleDescriptor;
16  import com.atlassian.plugin.osgi.HostClassUsingHostComponentConstructor;
17  import com.atlassian.plugin.osgi.HostClassUsingHostComponentSetter;
18  import com.atlassian.plugin.osgi.ObjectModuleDescriptor;
19  import com.atlassian.plugin.osgi.PluginInContainerTestBase;
20  import com.atlassian.plugin.osgi.SomeInterface;
21  import com.atlassian.plugin.osgi.StubServletModuleDescriptor;
22  import com.atlassian.plugin.osgi.external.SingleModuleDescriptorFactory;
23  import com.atlassian.plugin.osgi.factory.OsgiPlugin;
24  import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
25  import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
26  import com.atlassian.plugin.servlet.DefaultServletModuleManager;
27  import com.atlassian.plugin.servlet.ServletModuleManager;
28  import com.atlassian.plugin.servlet.descriptors.ServletModuleDescriptor;
29  import com.atlassian.plugin.test.PluginJarBuilder;
30  import com.atlassian.plugin.util.PluginUtils;
31  import com.atlassian.plugin.util.WaitUntil;
32  import com.google.common.collect.ImmutableMap;
33  import org.junit.Rule;
34  import org.junit.Test;
35  import org.junit.contrib.java.lang.system.RestoreSystemProperties;
36  import org.osgi.framework.Bundle;
37  import org.osgi.framework.Constants;
38  
39  import javax.servlet.ServletConfig;
40  import javax.servlet.ServletContext;
41  import javax.servlet.http.HttpServlet;
42  import java.io.File;
43  import java.util.Collections;
44  import java.util.concurrent.TimeUnit;
45  import java.util.concurrent.locks.Lock;
46  import java.util.concurrent.locks.ReentrantLock;
47  
48  import static com.atlassian.plugin.PluginState.DISABLED;
49  import static org.hamcrest.Matchers.empty;
50  import static org.hamcrest.Matchers.instanceOf;
51  import static org.hamcrest.Matchers.lessThan;
52  import static org.hamcrest.Matchers.notNullValue;
53  import static org.hamcrest.core.Is.is;
54  import static org.junit.Assert.assertEquals;
55  import static org.junit.Assert.assertFalse;
56  import static org.junit.Assert.assertNotNull;
57  import static org.junit.Assert.assertThat;
58  import static org.junit.Assert.assertTrue;
59  import static org.junit.Assert.fail;
60  import static org.mockito.Mockito.mock;
61  import static org.mockito.Mockito.when;
62  
63  public final class TestPluginInstall extends PluginInContainerTestBase {
64  
65      @Rule
66      public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
67  
68      private static final String SPRING_CONFIG_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
69              + "<beans xmlns=\"http://www.springframework.org/schema/beans\"\n"
70              + "       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
71              + "       xmlns:osgi=\"http://www.eclipse.org/gemini/blueprint/schema/blueprint\"\n"
72              + "       xsi:schemaLocation=\""
73              + "           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\n"
74              + "           http://www.eclipse.org/gemini/blueprint/schema/blueprint http://www.eclipse.org/gemini/blueprint/schema/blueprint/gemini-blueprint.xsd\"\n"
75              + ">\n";
76      private static final String SPRING_CONFIG_FOOTER = "</beans>\n";
77  
78      private static void assertExists(File f) {
79          assertTrue("File should exist: " + f, f.exists());
80      }
81  
82      private static void assertDoesNotExist(File f) {
83          assertFalse("File should not exist: " + f, f.exists());
84      }
85  
86      @Test
87      public void testUpgradeOfBundledPlugin() throws Exception {
88          final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(hostContainer);
89          factory.addModuleDescriptor("object", ObjectModuleDescriptor.class);
90  
91          final File pluginJar = new PluginJarBuilder("testUpgradeOfBundledPlugin")
92                  .addFormattedResource("atlassian-plugin.xml",
93                          "<atlassian-plugin name='Test' key='test.bundled.plugin' pluginsVersion='2'>",
94                          "    <plugin-info>",
95                          "        <version>1.0</version>",
96                          "    </plugin-info>",
97                          "    <object key='obj' class='my.Foo'/>",
98                          "</atlassian-plugin>")
99                  .addFormattedJava("my.Foo",
100                         "package my;",
101                         "public class Foo {}")
102                 .build();
103         initBundlingPluginManager(factory, pluginJar);
104         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
105         assertEquals("Test", pluginAccessor.getPlugin("test.bundled.plugin").getName());
106         assertEquals("my.Foo", pluginAccessor.getPlugin("test.bundled.plugin").getModuleDescriptor("obj").getModule().getClass().getName());
107 
108         final File pluginJar2 = new PluginJarBuilder("testUpgradeOfBundledPlugin")
109                 .addFormattedResource("atlassian-plugin.xml",
110                         "<atlassian-plugin name='Test' key='test.bundled.plugin' pluginsVersion='2'>",
111                         "    <plugin-info>",
112                         "        <version>1.0</version>",
113                         "    </plugin-info>",
114                         "    <object key='obj' class='my.Bar'/>",
115                         "</atlassian-plugin>")
116                 .addFormattedJava("my.Bar",
117                         "package my;",
118                         "public class Bar {}")
119                 .build();
120 
121         pluginController.installPlugins(new JarPluginArtifact(pluginJar2));
122 
123         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
124         assertEquals("Test", pluginAccessor.getPlugin("test.bundled.plugin").getName());
125         assertEquals("my.Bar", pluginAccessor.getPlugin("test.bundled.plugin").getModuleDescriptor("obj").getModule().getClass().getName());
126 
127     }
128 
129     @Test
130     public void testUpgradeOfBadPlugin() throws Exception {
131         new PluginJarBuilder("testUpgradeOfBundledPlugin-old")
132                 .addFormattedResource("atlassian-plugin.xml",
133                         "<atlassian-plugin name='Test' key='test.bundled.plugin' pluginsVersion='2'>",
134                         "    <plugin-info>",
135                         "        <version>1.0</version>",
136                         "    </plugin-info>",
137                         "    <component key='obj' class='my.Foo'/>",
138                         "</atlassian-plugin>")
139                 .addFormattedJava("my.Foo",
140                         "package my;",
141                         "public class Foo {",
142                         "  public Foo() { throw new RuntimeException('bad plugin');}",
143                         "}")
144                 .build(pluginsDir);
145         new PluginJarBuilder("testUpgradeOfBundledPlugin-new")
146                 .addFormattedResource("atlassian-plugin.xml",
147                         "<atlassian-plugin name='Test' key='test.bundled.plugin' pluginsVersion='2'>",
148                         "    <plugin-info>",
149                         "        <version>2.0</version>",
150                         "    </plugin-info>",
151                         "    <component key='obj' class='my.Foo'/>",
152                         "</atlassian-plugin>")
153                 .addFormattedJava("my.Foo",
154                         "package my;",
155                         "public class Foo {",
156                         "  public Foo() {}",
157                         "}")
158                 .build(pluginsDir);
159         initPluginManager();
160         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
161         assertEquals("Test", pluginAccessor.getPlugin("test.bundled.plugin").getName());
162         assertEquals("2.0", pluginAccessor.getPlugin("test.bundled.plugin").getPluginInformation().getVersion());
163     }
164 
165     @Test
166     public void testUpgradeWithNewComponentImports() throws Exception {
167         final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
168         factory.addModuleDescriptor("dummy", DummyModuleDescriptor.class);
169         initPluginManager(new HostComponentProvider() {
170             public void provide(final ComponentRegistrar registrar) {
171                 registrar.register(SomeInterface.class).forInstance(new SomeInterface() {
172                 });
173                 registrar.register(AnotherInterface.class).forInstance(new AnotherInterface() {
174                 });
175             }
176         }, factory);
177 
178         final File pluginJar = new PluginJarBuilder("first")
179                 .addFormattedResource("atlassian-plugin.xml",
180                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
181                         "    <plugin-info>",
182                         "        <version>1.0</version>",
183                         "    </plugin-info>",
184                         "    <component-import key='comp1' interface='com.atlassian.plugin.osgi.SomeInterface' />",
185                         "    <dummy key='dum1'/>", "</atlassian-plugin>")
186                 .build();
187         final File pluginJar2 = new PluginJarBuilder("second")
188                 .addFormattedResource("atlassian-plugin.xml",
189                         "<atlassian-plugin name='Test 2' key='test.plugin' pluginsVersion='2'>",
190                         "    <plugin-info>",
191                         "        <version>1.0</version>",
192                         "    </plugin-info>",
193                         "    <component-import key='comp1' interface='com.atlassian.plugin.osgi.SomeInterface' />",
194                         "    <component-import key='comp2' interface='com.atlassian.plugin.osgi.AnotherInterface' />",
195                         "    <dummy key='dum1'/>",
196                         "    <dummy key='dum2'/>",
197                         "</atlassian-plugin>")
198                 .build();
199 
200         pluginController.installPlugins(new JarPluginArtifact(pluginJar));
201         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
202         assertEquals("Test", pluginAccessor.getPlugin("test.plugin").getName());
203         assertEquals(2, pluginAccessor.getPlugin("test.plugin").getModuleDescriptors().size());
204         pluginController.installPlugins(new JarPluginArtifact(pluginJar2));
205         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
206         assertEquals(4, pluginAccessor.getPlugin("test.plugin").getModuleDescriptors().size());
207         assertEquals("Test 2", pluginAccessor.getPlugin("test.plugin").getName());
208     }
209 
210     @Test
211     public void testInstallWithClassConstructorReferencingHostClassWithHostComponent() throws Exception {
212         final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(hostContainer);
213         factory.addModuleDescriptor("object", ObjectModuleDescriptor.class);
214         initPluginManager(new HostComponentProvider() {
215             public void provide(final ComponentRegistrar registrar) {
216                 registrar.register(SomeInterface.class).forInstance(new SomeInterface() {
217                 });
218             }
219         }, factory);
220 
221         final File pluginJar = new PluginJarBuilder("testUpgradeOfBundledPlugin")
222                 .addFormattedResource("atlassian-plugin.xml",
223                         "<atlassian-plugin name='Test' key='hostClass' pluginsVersion='2'>",
224                         "    <plugin-info>",
225                         "        <version>1.0</version>",
226                         "    </plugin-info>",
227                         "    <object key='hostClass' class='com.atlassian.plugin.osgi.HostClassUsingHostComponentConstructor'/>",
228                         "</atlassian-plugin>")
229                 .build();
230 
231         pluginController.installPlugins(new JarPluginArtifact(pluginJar));
232         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
233         assertEquals("Test", pluginAccessor.getPlugin("hostClass").getName());
234         assertEquals(1, pluginAccessor.getPlugin("hostClass").getModuleDescriptors().size());
235 
236         HostClassUsingHostComponentConstructor module = (HostClassUsingHostComponentConstructor) pluginAccessor.getPlugin("hostClass").getModuleDescriptor("hostClass").getModule();
237         assertNotNull(module);
238     }
239 
240     @Test
241     public void testInstallWithClassSetterReferencingHostClassWithHostComponent() throws Exception {
242         final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(hostContainer);
243         factory.addModuleDescriptor("object", ObjectModuleDescriptor.class);
244 
245         initPluginManager(new HostComponentProvider() {
246             public void provide(final ComponentRegistrar registrar) {
247                 registrar.register(SomeInterface.class).forInstance(new SomeInterface() {
248                 });
249             }
250         }, factory);
251 
252         final File pluginJar = new PluginJarBuilder("testUpgradeOfBundledPlugin")
253                 .addFormattedResource("atlassian-plugin.xml",
254                         "<atlassian-plugin name='Test' key='hostClass' pluginsVersion='2'>",
255                         "    <plugin-info>",
256                         "        <version>1.0</version>",
257                         "    </plugin-info>",
258                         "    <object key='hostClass' class='com.atlassian.plugin.osgi.HostClassUsingHostComponentSetter'/>",
259                         "</atlassian-plugin>")
260                 .build();
261 
262         pluginController.installPlugins(new JarPluginArtifact(pluginJar));
263         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
264         assertEquals("Test", pluginAccessor.getPlugin("hostClass").getName());
265         assertEquals(1, pluginAccessor.getPlugin("hostClass").getModuleDescriptors().size());
266 
267         HostClassUsingHostComponentSetter module = (HostClassUsingHostComponentSetter) pluginAccessor.getPlugin("hostClass").getModuleDescriptor("hostClass").getModule();
268         assertNotNull(module);
269         assertNotNull(module.getSomeInterface());
270     }
271 
272     /* Enable for manual memory leak profiling
273     @Test
274     public void testNoMemoryLeak() throws Exception
275     {
276         DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
277         factory.addModuleDescriptor("dummy", DummyModuleDescriptor.class);
278         for (int x=0; x<100; x++)
279         {
280             pluginEventManager = new DefaultPluginEventManager();
281             initPluginManager(new HostComponentProvider(){
282                 public void provide(ComponentRegistrar registrar)
283                 {
284                     registrar.register(SomeInterface.class).forInstance(new SomeInterface(){});
285                     registrar.register(AnotherInterface.class).forInstance(new AnotherInterface(){});
286                 }
287             }, factory);
288             pluginSystemLifecycle.shutdown();
289 
290         }
291         System.out.println("Gentlement, start your profilers!");
292         System.in.read();
293 
294     }
295     */
296 
297     @Test
298     public void testUpgradeWithNoAutoDisable() throws Exception {
299         DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
300         factory.addModuleDescriptor("dummy", DummyModuleDescriptor.class);
301         initPluginManager(new HostComponentProvider() {
302             public void provide(ComponentRegistrar registrar) {
303                 registrar.register(SomeInterface.class).forInstance(new SomeInterface() {
304                 });
305                 registrar.register(AnotherInterface.class).forInstance(new AnotherInterface() {
306                 });
307             }
308         }, factory);
309 
310         File pluginJar = new PluginJarBuilder("first")
311                 .addFormattedResource("atlassian-plugin.xml",
312                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
313                         "    <plugin-info>",
314                         "        <version>1.0</version>",
315                         "    </plugin-info>",
316                         "    <component-import key='comp1' interface='com.atlassian.plugin.osgi.SomeInterface' />",
317                         "    <dummy key='dum1'/>",
318                         "</atlassian-plugin>")
319                 .build();
320         final File pluginJar2 = new PluginJarBuilder("second")
321                 .addFormattedResource("atlassian-plugin.xml",
322                         "<atlassian-plugin name='Test 2' key='test.plugin' pluginsVersion='2'>",
323                         "    <plugin-info>",
324                         "        <version>1.0</version>",
325                         "    </plugin-info>",
326                         "    <component-import key='comp1' interface='com.atlassian.plugin.osgi.SomeInterface' />",
327                         "    <dummy key='dum1'/>",
328                         "    <dummy key='dum2'/>",
329                         "</atlassian-plugin>")
330                 .build();
331 
332         pluginController.installPlugins(new JarPluginArtifact(pluginJar));
333         assertTrue(pluginAccessor.isPluginEnabled("test.plugin"));
334 
335         final Lock lock = new ReentrantLock();
336         Thread upgradeThread = new Thread() {
337             public void run() {
338                 lock.lock();
339                 pluginController.installPlugins(new JarPluginArtifact(pluginJar2));
340                 lock.unlock();
341             }
342         };
343 
344         Thread isEnabledThread = new Thread() {
345             public void run() {
346                 try {
347                     while (!lock.tryLock(10, TimeUnit.SECONDS))
348                         pluginAccessor.isPluginEnabled("test.plugin");
349                 } catch (InterruptedException e) {
350                     fail();
351                 }
352             }
353         };
354         upgradeThread.start();
355         isEnabledThread.start();
356 
357         upgradeThread.join(10000);
358 
359         assertTrue(pluginAccessor.isPluginEnabled("test.plugin"));
360     }
361 
362     @Test
363     public void testUpgradeTestingForCachedXml() throws Exception {
364         final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
365         factory.addModuleDescriptor("dummy", DummyModuleDescriptor.class);
366         initPluginManager(new HostComponentProvider() {
367             public void provide(final ComponentRegistrar registrar) {
368                 registrar.register(SomeInterface.class).forInstance(new SomeInterface() {
369                 });
370                 registrar.register(AnotherInterface.class).forInstance(new AnotherInterface() {
371                 });
372             }
373         }, factory);
374 
375         final File pluginJar = new PluginJarBuilder("first").addFormattedResource("atlassian-plugin.xml",
376                 "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>", "    <plugin-info>", "        <version>1.0</version>",
377                 "    </plugin-info>", "    <component key='comp1' interface='com.atlassian.plugin.osgi.SomeInterface' class='my.ServiceImpl' />",
378                 "</atlassian-plugin>").addFormattedJava("my.ServiceImpl", "package my;",
379                 "public class ServiceImpl implements com.atlassian.plugin.osgi.SomeInterface {}").build();
380         final File pluginJar2 = new PluginJarBuilder("second").addFormattedResource("atlassian-plugin.xml",
381                 "<atlassian-plugin name='Test 2' key='test.plugin' pluginsVersion='2'>", "    <plugin-info>", "        <version>1.0</version>",
382                 "    </plugin-info>", "</atlassian-plugin>").build();
383 
384         pluginController.installPlugins(new JarPluginArtifact(pluginJar));
385         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
386         assertEquals("Test", pluginAccessor.getPlugin("test.plugin").getName());
387         pluginController.installPlugins(new JarPluginArtifact(pluginJar2));
388         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
389         assertEquals("Test 2", pluginAccessor.getPlugin("test.plugin").getName());
390     }
391 
392     @Test
393     public void testPluginDependentOnPackageImport() throws Exception {
394         HostComponentProvider prov = new HostComponentProvider() {
395             public void provide(final ComponentRegistrar registrar) {
396                 registrar.register(ServletConfig.class).forInstance(new HttpServlet() {
397                 });
398             }
399         };
400         File servletJar = new PluginJarBuilder("first")
401                 .addFormattedResource("META-INF/MANIFEST.MF",
402                         "Export-Package: javax.servlet.http;version='4.0.0',javax.servlet;version='4.0.0'",
403                         "Import-Package: javax.servlet.http;version='4.0.0',javax.servlet;version='4.0.0'",
404                         "Bundle-SymbolicName: first",
405                         "Bundle-Version: 4.0.0",
406                         "Manifest-Version: 1.0",
407                         "")
408                 .addFormattedJava("javax.servlet.Servlet",
409                         "package javax.servlet;",
410                         "public interface Servlet {}")
411                 .addFormattedJava("javax.servlet.http.HttpServlet",
412                         "package javax.servlet.http;",
413                         "public abstract class HttpServlet implements javax.servlet.Servlet{}")
414                 .build();
415 
416         File pluginJar = new PluginJarBuilder("asecond")
417                 .addFormattedResource("atlassian-plugin.xml",
418                         "<atlassian-plugin name='Test' key='second' pluginsVersion='2'>",
419                         "    <plugin-info>",
420                         "        <version>1.0</version>",
421                         "        <bundle-instructions><Import-Package>javax.servlet.http;version='3.0',javax.servlet;version='3.0',*</Import-Package></bundle-instructions>",
422                         "    </plugin-info>",
423                         "</atlassian-plugin>")
424                 .addFormattedJava("second.MyImpl",
425                         "package second;",
426                         "public class MyImpl {",
427                         "    public MyImpl(javax.servlet.ServletConfig config) {",
428                         "    }",
429                         "}")
430                 .build();
431 
432         initPluginManager(prov);
433         pluginController.installPlugins(new JarPluginArtifact(servletJar));
434         pluginController.installPlugins(new JarPluginArtifact(pluginJar));
435         WaitUntil.invoke(new BasicWaitCondition() {
436             public boolean isFinished() {
437                 return pluginAccessor.getEnabledPlugins().size() == 2;
438             }
439         });
440 
441         assertEquals(2, pluginAccessor.getEnabledPlugins().size());
442         assertNotNull(pluginAccessor.getPlugin("first-4.0.0"));
443         assertNotNull(pluginAccessor.getPlugin("second"));
444     }
445 
446     @Test
447     public void testPluginWithHostComponentUsingOldPackageImport() throws Exception {
448         final PluginJarBuilder firstBuilder = new PluginJarBuilder("oldpkgfirst");
449         firstBuilder
450                 .addFormattedResource("atlassian-plugin.xml",
451                         "<atlassian-plugin name='Test' key='first' pluginsVersion='2'>",
452                         "    <plugin-info>",
453                         "        <version>1.0</version>",
454                         "        <bundle-instructions>",
455                         "           <Export-Package>first</Export-Package>",
456                         "        </bundle-instructions>",
457                         "    </plugin-info>",
458                         "    <servlet key='foo' class='second.MyServlet'>",
459                         "       <url-pattern>/foo</url-pattern>",
460                         "    </servlet>",
461                         "</atlassian-plugin>")
462                 .addFormattedJava("first.MyInterface",
463                         "package first;",
464                         "public interface MyInterface {}")
465                 .build(pluginsDir);
466 
467         new PluginJarBuilder("oldpkgsecond", firstBuilder.getClassLoader())
468                 .addPluginInformation("second", "Some name", "1.0")
469                 .addFormattedJava("second.MyImpl",
470                         "package second;",
471                         "public class MyImpl implements first.MyInterface {}")
472                 .build(pluginsDir);
473 
474         initPluginManager();
475 
476         assertEquals(2, pluginAccessor.getEnabledPlugins().size());
477         assertNotNull(pluginAccessor.getPlugin("first"));
478         assertNotNull(pluginAccessor.getPlugin("second"));
479     }
480 
481     @Test
482     public void testPluginWithServletDependentOnPackageImport() throws Exception {
483         final PluginJarBuilder firstBuilder = new PluginJarBuilder("first");
484         firstBuilder
485                 .addPluginInformation("first", "Some name", "1.0")
486                 .addFormattedJava("first.MyInterface",
487                         "package first;",
488                         "public interface MyInterface {}")
489                 .addFormattedResource("META-INF/MANIFEST.MF",
490                         "Manifest-Version: 1.0",
491                         "Bundle-SymbolicName: first",
492                         "Bundle-Version: 1.0",
493                         "Export-Package: first",
494                         "")
495                 .build(pluginsDir);
496 
497         new PluginJarBuilder("asecond", firstBuilder.getClassLoader())
498                 .addFormattedResource("atlassian-plugin.xml",
499                         "<atlassian-plugin name='Test' key='asecond' pluginsVersion='2'>",
500                         "    <plugin-info>",
501                         "        <version>1.0</version>",
502                         "    </plugin-info>",
503                         "    <servlet key='foo' class='second.MyServlet'>",
504                         "       <url-pattern>/foo</url-pattern>",
505                         "    </servlet>",
506                         "</atlassian-plugin>")
507                 .addFormattedJava("second.MyServlet",
508                         "package second;",
509                         "public class MyServlet extends javax.servlet.http.HttpServlet implements first.MyInterface {}")
510                 .build(pluginsDir);
511 
512         initPluginManager(null, new SingleModuleDescriptorFactory(new DefaultHostContainer(), "servlet", StubServletModuleDescriptor.class));
513 
514         assertEquals(2, pluginAccessor.getEnabledPlugins().size());
515         assertTrue(pluginAccessor.getPlugin("first").getPluginState() == PluginState.ENABLED);
516         assertNotNull(pluginAccessor.getPlugin("asecond").getPluginState() == PluginState.ENABLED);
517     }
518 
519     @Test
520     public void testPluginWithServletRefreshedAfterOtherPluginUpgraded() throws Exception {
521         final PluginJarBuilder firstBuilder = new PluginJarBuilder("first");
522         firstBuilder
523                 .addPluginInformation("first", "Some name", "1.0")
524                 .addFormattedJava("first.MyInterface",
525                         "package first;",
526                         "public interface MyInterface {}")
527                 .addFormattedResource("META-INF/MANIFEST.MF",
528                         "Manifest-Version: 1.0",
529                         "Bundle-SymbolicName: first",
530                         "Bundle-Version: 1.0",
531                         "Export-Package: first",
532                         "")
533                 .build(pluginsDir);
534 
535         new PluginJarBuilder("asecond", firstBuilder.getClassLoader())
536                 .addFormattedResource("atlassian-plugin.xml",
537                         "<atlassian-plugin name='Test' key='asecond' pluginsVersion='2'>",
538                         "    <plugin-info>",
539                         "        <version>1.0</version>",
540                         "    </plugin-info>",
541                         "    <servlet key='foo' class='second.MyServlet'>",
542                         "       <url-pattern>/foo</url-pattern>",
543                         "    </servlet>",
544                         "</atlassian-plugin>")
545                 .addFormattedJava("second.MyServlet",
546                         "package second;",
547                         "import com.atlassian.plugin.osgi.Callable2;",
548                         "public class MyServlet extends javax.servlet.http.HttpServlet implements first.MyInterface {",
549                         "   private Callable2 callable;",
550                         "   public MyServlet(Callable2 cal) { this.callable = cal; }",
551                         "   public String getServletInfo() {",
552                         "       try {return callable.call() + ' bob';} catch (Exception ex) { throw new RuntimeException(ex);}",
553                         "   }",
554                         "}")
555                 .build(pluginsDir);
556 
557         HostComponentProvider prov = new HostComponentProvider() {
558 
559             public void provide(ComponentRegistrar registrar) {
560                 registrar.register(Callable2.class).forInstance(new Callable2() {
561                     public String call() {
562                         return "hi";
563                     }
564                 });
565 
566             }
567         };
568 
569         ServletContext ctx = mock(ServletContext.class);
570         when(ctx.getInitParameterNames()).thenReturn(Collections.<String>enumeration(Collections.<String>emptyList()));
571         ServletConfig servletConfig = mock(ServletConfig.class);
572         when(servletConfig.getServletContext()).thenReturn(ctx);
573 
574         ServletModuleManager mgr = new DefaultServletModuleManager(pluginEventManager);
575         hostContainer = createHostContainer(Collections.<Class<?>, Object>singletonMap(ServletModuleManager.class, mgr));
576         initPluginManager(prov, new SingleModuleDescriptorFactory(
577                 hostContainer,
578                 "servlet",
579                 ServletModuleDescriptor.class));
580 
581         assertEquals(2, pluginAccessor.getEnabledPlugins().size());
582         assertTrue(pluginAccessor.getPlugin("first").getPluginState() == PluginState.ENABLED);
583         assertNotNull(pluginAccessor.getPlugin("asecond").getPluginState() == PluginState.ENABLED);
584         assertEquals("hi bob", mgr.getServlet("/foo", servletConfig).getServletInfo());
585 
586 
587         final File updatedJar = new PluginJarBuilder("first-updated")
588                 .addPluginInformation("foo", "Some name", "1.0")
589                 .addFormattedJava("first.MyInterface",
590                         "package first;",
591                         "public interface MyInterface {}")
592                 .addFormattedResource("META-INF/MANIFEST.MF",
593                         "Manifest-Version: 1.0",
594                         "Bundle-SymbolicName: foo",
595                         "Bundle-Version: 1.0",
596                         "Export-Package: first",
597                         "")
598                 .build();
599         pluginController.installPlugins(new JarPluginArtifact(updatedJar));
600 
601         WaitUntil.invoke(new BasicWaitCondition() {
602             public boolean isFinished() {
603                 return pluginAccessor.isPluginEnabled("asecond");
604             }
605 
606         });
607 
608         assertEquals("hi bob", mgr.getServlet("/foo", servletConfig).getServletInfo());
609     }
610 
611     // PLUG-760
612     @Test
613     public void testModuleDescriptorWithNamespace() throws Exception {
614         new PluginJarBuilder("asecond")
615                 .addFormattedResource("atlassian-plugin.xml",
616                         "<atlassian-plugin name='Test' key='asecond' pluginsVersion='2'",
617                         "    xmlns='http://www.atlassian.com/schema/plugins'",
618                         "    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'",
619                         "    xsi:schemaLocation='http://www.atlassian.com/schema/plugins http://schema.atlassian.com/refapp/refapp-2.9.xsd'>",
620                         "    <plugin-info>",
621                         "        <version>1.0</version>",
622                         "    </plugin-info>",
623                         "    <servlet key='foo' class='second.MyServlet'>",
624                         "       <url-pattern>/foo</url-pattern>",
625                         "    </servlet>",
626                         "</atlassian-plugin>")
627                 .addFormattedJava("second.MyServlet",
628                         "package second;",
629                         "public class MyServlet extends javax.servlet.http.HttpServlet {}")
630                 .build(pluginsDir);
631 
632         ServletContext ctx = mock(ServletContext.class);
633         when(ctx.getInitParameterNames()).thenReturn(Collections.<String>enumeration(Collections.<String>emptyList()));
634         ServletConfig servletConfig = mock(ServletConfig.class);
635         when(servletConfig.getServletContext()).thenReturn(ctx);
636 
637         ServletModuleManager mgr = new DefaultServletModuleManager(pluginEventManager);
638         hostContainer = createHostContainer(Collections.<Class<?>, Object>singletonMap(ServletModuleManager.class, mgr));
639         initPluginManager(null, new SingleModuleDescriptorFactory(
640                 hostContainer,
641                 "servlet",
642                 ServletModuleDescriptor.class));
643 
644         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
645         assertNotNull(pluginAccessor.getPlugin("asecond").getPluginState() == PluginState.ENABLED);
646     }
647 
648     @Test
649     public void testLotsOfHostComponents() throws Exception {
650         new PluginJarBuilder("first")
651                 .addFormattedResource("atlassian-plugin.xml",
652                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
653                         "    <plugin-info>",
654                         "        <version>1.0</version>",
655                         "    </plugin-info>",
656                         "    <dummy key='dum1'/>",
657                         "</atlassian-plugin>")
658                 .build(pluginsDir);
659         new PluginJarBuilder("second")
660                 .addFormattedResource("atlassian-plugin.xml",
661                         "<atlassian-plugin name='Test 2' key='test.plugin2' pluginsVersion='2'>",
662                         "    <plugin-info>",
663                         "        <version>1.0</version>",
664                         "    </plugin-info>",
665                         "    <dummy key='dum1'/>",
666                         "    <dummy key='dum2'/>",
667                         "</atlassian-plugin>")
668                 .build(pluginsDir);
669 
670         final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
671         factory.addModuleDescriptor("dummy", DummyModuleDescriptor.class);
672         initPluginManager(new HostComponentProvider() {
673             public void provide(final ComponentRegistrar registrar) {
674                 for (int x = 0; x < 100; x++) {
675                     registrar.register(SomeInterface.class).forInstance(new SomeInterface() {
676                     }).withName("some" + x);
677                     registrar.register(AnotherInterface.class).forInstance(new AnotherInterface() {
678                     }).withName("another" + x);
679                 }
680             }
681         }, factory);
682 
683         assertEquals(2, pluginAccessor.getEnabledPlugins().size());
684     }
685 
686     @Test
687     public void testInstallWithManifestNoSpringContextAndComponents() throws Exception {
688         final BooleanFlag flag = new DefaultBooleanFlag(false);
689         new PluginJarBuilder("first")
690                 .addFormattedResource("atlassian-plugin.xml",
691                         "<atlassian-plugin name='Test' key='first' pluginsVersion='2'>",
692                         "    <plugin-info>",
693                         "        <version>1.0</version>",
694                         "    </plugin-info>",
695                         "    <component key='foo' class='first.MyClass' interface='first.MyInterface' public='true'/>",
696                         "</atlassian-plugin>")
697                 .addFormattedJava("first.MyInterface",
698                         "package first;",
699                         "public interface MyInterface {}")
700                 .addFormattedJava("first.MyClass",
701                         "package first;",
702                         "public class MyClass implements MyInterface{",
703                         "  public MyClass(com.atlassian.plugin.osgi.BooleanFlag bool) { bool.set(true); }",
704                         "}")
705                 .addFormattedResource("META-INF/MANIFEST.MF",
706                         "Manifest-Version: 1.0",
707                         "Bundle-SymbolicName: foo",
708                         "Bundle-Version: 1.0",
709                         "Export-Package: first",
710                         "")
711                 .build(pluginsDir);
712 
713         initPluginManager(registrar -> registrar.register(BooleanFlag.class)
714                 .forInstance(flag)
715                 .withName("bob"));
716 
717         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
718         assertNotNull(pluginAccessor.getPlugin("first"));
719         assertTrue(flag.get());
720     }
721 
722     @Test
723     public void testInstallWithManifestNoDescriptorWithSpringXml() throws Exception {
724         new PluginJarBuilder("nodesc")
725                 .manifest(new ImmutableMap.Builder<String, String>()
726                         .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "nodesc")
727                         .put(Constants.BUNDLE_VERSION, "1.0")
728                         .build())
729                 .addResource("META-INF/spring/foo.xml", SPRING_CONFIG_HEADER + SPRING_CONFIG_FOOTER)
730                 .build(pluginsDir);
731 
732         initPluginManager();
733 
734         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
735         Plugin plugin = pluginAccessor.getPlugin("nodesc");
736         assertNotNull(plugin);
737         assertEquals("1.0", plugin.getPluginInformation().getVersion());
738         assertEquals("nodesc", plugin.getKey());
739         assertEquals(0, plugin.getModuleDescriptors().size());
740     }
741 
742     @Test
743     public void testInstallWithManifestNoDescriptorWithSpringHeader() throws Exception {
744         new PluginJarBuilder("nodesc")
745                 .manifest(new ImmutableMap.Builder<String, String>()
746                         .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "nodesc")
747                         .put(Constants.BUNDLE_VERSION, "1.0")
748                         .put("Spring-Context", "*;timeout:=60")
749                         .build())
750                 .build(pluginsDir);
751 
752         initPluginManager();
753 
754         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
755         Plugin plugin = pluginAccessor.getPlugin("nodesc");
756         assertNotNull(plugin);
757         assertEquals("1.0", plugin.getPluginInformation().getVersion());
758         assertEquals("nodesc", plugin.getKey());
759         assertEquals(0, plugin.getModuleDescriptors().size());
760     }
761 
762     @Test
763     public void testInstallWithSpringContainerFail() throws Exception {
764         System.setProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT, "10");
765 
766         new PluginJarBuilder("fail-spring-container")
767                 .manifest(new ImmutableMap.Builder<String, String>()
768                         .put(OsgiPlugin.ATLASSIAN_PLUGIN_KEY, "fail-spring-container")
769                         .put(Constants.BUNDLE_VERSION, "1.0")
770                         .put("Spring-Context", "*")
771                         .build())
772                 .addResource("META-INF/spring/fail.xml",
773                         SPRING_CONFIG_HEADER + "<osgi:reference id=\"notFound\" interface=\"some.class.NotFound\"/>" + SPRING_CONFIG_FOOTER)
774                 .build(pluginsDir);
775 
776         final long beforeStarting = System.currentTimeMillis();
777         initPluginManager();
778         final long startupDuration = System.currentTimeMillis() - beforeStarting;
779 
780         assertThat(pluginAccessor.getEnabledPlugins(), empty());
781 
782         final Plugin plugin = pluginAccessor.getPlugin("fail-spring-container");
783         assertThat(plugin, notNullValue());
784         assertThat(plugin, instanceOf(OsgiPlugin.class));
785 
786         final OsgiPlugin osgiPlugin = (OsgiPlugin) plugin;
787         assertThat(osgiPlugin.getPluginState(), is(DISABLED));
788         assertThat(osgiPlugin.getBundle().getState(), is(Bundle.RESOLVED));
789 
790         assertThat("fail-spring-container failed due to plugin system timeout rather than immediately via PluginContainerFailedEvent", startupDuration, lessThan(10000L));
791     }
792 
793     // TODO: turn this back on PLUG-682
794 //    @Test
795     public void testInstallWithComponentBeanNameConflictedWithHostComponent() throws Exception {
796         new PluginJarBuilder("first")
797                 .addFormattedResource("atlassian-plugin.xml",
798                         "<atlassian-plugin name='Test' key='first' pluginsVersion='2'>",
799                         "    <plugin-info>",
800                         "        <version>1.0</version>",
801                         "    </plugin-info>",
802                         "    <component key='host_component1' class='first.MyClass' interface='first.MyInterface'/>",
803                         "</atlassian-plugin>")
804                 .addFormattedJava("com.atlassian.plugin.osgi.SomeInterface",
805                         "package com.atlassian.plugin.osgi;",
806                         "public interface SomeInterface {}")
807                 .addFormattedJava("first.MyClass",
808                         "package first;",
809                         "import com.atlassian.plugin.osgi.SomeInterface;",
810                         "public class MyClass implements SomeInterface{",
811                         "}")
812                 .build(pluginsDir);
813 
814         initPluginManager(registrar -> registrar.register(SomeInterface.class)
815                 .forInstance(new SomeInterface() {})
816                 .withName("host_component1"));
817 
818         // there is a name conflict therefore this plugin should not have been enabled.
819         assertEquals(0, pluginAccessor.getEnabledPlugins().size());
820         assertTrue(pluginAccessor.getPlugins().toArray()[0] instanceof UnloadablePlugin);
821 
822         // the error message has to mention the problematic component name otherwise users won't be able to figure out.
823         assertTrue(pluginAccessor.getPlugins().toArray(new UnloadablePlugin[1])[0].getErrorText().contains("host_component1"));
824     }
825 
826     @Test
827     public void testInstallWithStrangePath() throws Exception {
828         File strangeDir = new File(tmpDir, "20%time");
829         strangeDir.mkdir();
830         File oldTmp = tmpDir;
831         try {
832             tmpDir = strangeDir;
833             cacheDir = new File(tmpDir, "felix-cache");
834             cacheDir.mkdir();
835             pluginsDir = new File(tmpDir, "plugins");
836             pluginsDir.mkdir();
837 
838             new PluginJarBuilder("strangePath")
839                     .addFormattedResource("atlassian-plugin.xml",
840                             "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
841                             "    <plugin-info>",
842                             "        <version>1.0</version>",
843                             "    </plugin-info>",
844                             "</atlassian-plugin>")
845                     .build(pluginsDir);
846 
847             final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
848             factory.addModuleDescriptor("dummy", DummyModuleDescriptor.class);
849             initPluginManager(new HostComponentProvider() {
850                 public void provide(final ComponentRegistrar registrar) {
851                 }
852             }, factory);
853 
854             assertEquals(1, pluginAccessor.getEnabledPlugins().size());
855         } finally {
856             tmpDir = oldTmp;
857         }
858     }
859 
860     @Test
861     public void testInstallWithUnsatisifedDependency() throws Exception {
862         File plugin = new PluginJarBuilder("unsatisifiedDependency")
863                 .addFormattedResource("atlassian-plugin.xml",
864                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
865                         "    <plugin-info>",
866                         "        <version>1.0</version>",
867                         "    </plugin-info>",
868                         "    <component-import key='foo' interface='java.util.concurrent.Callable' />",
869                         "</atlassian-plugin>")
870                 .build(pluginsDir);
871 
872         long start = System.currentTimeMillis();
873         // Set dev mode temporarily
874         System.setProperty("atlassian.dev.mode", "true");
875         System.setProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT, "7");
876 
877         initPluginManager();
878 
879         assertTrue(start + (60 * 1000) > System.currentTimeMillis());
880     }
881 
882     @Test
883     public void testInstallSimplePluginNoSpring() throws Exception {
884         File jar = new PluginJarBuilder("strangePath")
885                 .addPluginInformation("no-spring", "foo", "1.0")
886                 .build();
887 
888         initPluginManager();
889         pluginController.installPlugins(new JarPluginArtifact(jar));
890 
891         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
892     }
893 
894     @Test
895     public void testInstallSimplePluginWithNoManifest() throws Exception {
896         File jar = new PluginJarBuilder("strangePath")
897                 .addPluginInformation("no-spring", "foo", "1.0")
898                 .buildWithNoManifest();
899 
900         initPluginManager();
901         pluginController.installPlugins(new JarPluginArtifact(jar));
902 
903         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
904     }
905 
906     @Test
907     public void testInstallPluginTakesTooLong() throws Exception {
908         System.setProperty(PluginUtils.ATLASSIAN_PLUGINS_ENABLE_WAIT, "3");
909         final PluginJarBuilder builder = new PluginJarBuilder("first")
910                 .addFormattedResource("atlassian-plugin.xml",
911                         "<atlassian-plugin name='Test' key='test.plugin' pluginsVersion='2'>",
912                         "    <plugin-info>",
913                         "        <version>1.0</version>",
914                         "    </plugin-info>",
915                         "    <component key='svc' class='my.ServiceImpl' public='true'>",
916                         "    <interface>my.Service</interface>",
917                         "    </component>",
918                         "</atlassian-plugin>")
919                 .addFormattedJava("my.Service",
920                         "package my;",
921                         "public interface Service {",
922                         "    public Object call() throws Exception;",
923                         "}")
924                 .addFormattedJava("my.ServiceImpl",
925                         "package my;",
926                         "public class ServiceImpl implements Service {",
927                         "public ServiceImpl(){",
928                         "try{",
929                         "Thread.sleep(10000);",
930                         "}catch(Exception e){}",
931                         "}",
932                         "    public Object call() throws Exception { ",
933                         "   return 'hi';}",
934                         "}");
935         final File jar = builder.build();
936         initPluginManager();
937 
938         pluginController.installPlugins(new JarPluginArtifact(jar));
939 
940         assertEquals(0, pluginAccessor.getEnabledPlugins().size());
941     }
942 
943     @Test
944     public void testInstallPluginVersion3() throws Exception {
945         initPluginManager();
946 
947         final File v3plugin = new PluginJarBuilder("v3-plugin")
948                 .addFormattedResource("atlassian-plugin.xml",
949                         "<atlassian-plugin name='V3 Plugin' key='v3-plugin' pluginsVersion='3'>",
950                         "    <plugin-info>",
951                         "        <version>1.0</version>",
952                         "        <permissions>",
953                         "            <permission>execute_java</permission>",
954                         "        </permissions>",
955                         "    </plugin-info>",
956                         "</atlassian-plugin>")
957                 .addFormattedResource("META-INF/MANIFEST.MF",
958                         "Bundle-SymbolicName: v3-plugin",
959                         // need the key because of com.atlassian.plugin.osgi.factory.OsgiPluginUninstalledHelper#install,
960                         // and the comparison of keys, using com.atlassian.plugin.osgi.util.OsgiHeaderUtil#getPluginKey !
961                         OsgiPlugin.ATLASSIAN_PLUGIN_KEY + ": v3-plugin",
962                         "Bundle-Version: 1.0.0",
963                         "Manifest-Version: 1.0",
964                         "Spring-Context: *;timeout:=60", // we still need a container, add this to trigger spring.
965                         "")
966                 .build();
967 
968         pluginController.installPlugins(new JarPluginArtifact(v3plugin));
969 
970         assertEquals(1, pluginAccessor.getEnabledPlugins().size());
971         assertNotNull(pluginAccessor.getEnabledPlugin("v3-plugin"));
972     }
973 
974     public static class Callable3Aware {
975         private final Callable3 callable;
976 
977         public Callable3Aware(Callable3 callable) {
978             this.callable = callable;
979         }
980 
981         public String call() throws Exception {
982             return callable.call();
983         }
984     }
985 }