View Javadoc

1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.PluginState;
4   import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
5   import com.atlassian.plugin.descriptors.RequiresRestart;
6   import com.atlassian.plugin.event.PluginEventManager;
7   import com.atlassian.plugin.event.events.PluginContainerRefreshedEvent;
8   import com.atlassian.plugin.module.ModuleFactory;
9   import com.atlassian.plugin.osgi.container.OsgiContainerException;
10  import com.atlassian.plugin.osgi.util.OsgiSystemBundleUtil;
11  
12  import org.junit.After;
13  import org.junit.Before;
14  import org.junit.Test;
15  import org.junit.runner.RunWith;
16  import org.mockito.Mock;
17  import org.mockito.invocation.InvocationOnMock;
18  import org.mockito.runners.MockitoJUnitRunner;
19  import org.mockito.stubbing.Answer;
20  import org.osgi.framework.Bundle;
21  import org.osgi.framework.BundleContext;
22  import org.osgi.framework.BundleException;
23  import org.osgi.framework.Constants;
24  import org.osgi.service.packageadmin.PackageAdmin;
25  
26  import java.util.Dictionary;
27  import java.util.Hashtable;
28  
29  import static org.hamcrest.Matchers.arrayContaining;
30  import static org.junit.Assert.assertEquals;
31  import static org.junit.Assert.assertTrue;
32  import static org.mockito.Matchers.argThat;
33  import static org.mockito.Mockito.doAnswer;
34  import static org.mockito.Mockito.doThrow;
35  import static org.mockito.Mockito.mock;
36  import static org.mockito.Mockito.never;
37  import static org.mockito.Mockito.times;
38  import static org.mockito.Mockito.verify;
39  import static org.mockito.Mockito.when;
40  
41  @RunWith (MockitoJUnitRunner.class)
42  public class TestOsgiPlugin
43  {
44      private static final String PLUGIN_KEY = "plugin-key";
45  
46      @Mock private Bundle bundle;
47      @Mock private BundleContext bundleContext;
48      @Mock private Bundle systemBundle;
49      @Mock private OsgiPluginHelper helper;
50      @Mock private PluginEventManager pluginEventManager;
51      @Mock private PackageAdmin packageAdmin;
52  
53      private OsgiPlugin plugin;
54  
55      @Before
56      public void setUp()
57      {
58          final Dictionary<String, String> bundleHeaders = new Hashtable<String, String>();
59          bundleHeaders.put(Constants.BUNDLE_DESCRIPTION, "desc");
60          bundleHeaders.put(Constants.BUNDLE_VERSION, "1.0");
61          when(bundle.getHeaders()).thenReturn(bundleHeaders);
62          when(bundle.getBundleContext()).thenReturn(bundleContext);
63  
64          when(bundleContext.getBundle(OsgiSystemBundleUtil.SYSTEM_BUNDLE_ID)).thenReturn(systemBundle);
65  
66          final BundleContext systemBundleContext = mock(BundleContext.class);
67          when(systemBundle.getBundleContext()).thenReturn(systemBundleContext);
68  
69          when(helper.getBundle()).thenReturn(bundle);
70  
71          plugin = new OsgiPlugin(pluginEventManager, helper, packageAdmin);
72          plugin.setKey(PLUGIN_KEY);
73      }
74  
75      @After
76      public void tearDown()
77      {
78          bundle = null;
79          plugin = null;
80          bundleContext = null;
81      }
82  
83      @Test
84      public void testEnabled() throws BundleException
85      {
86          _enablePlugin();
87          verify(bundle).start();
88      }
89  
90      private void _enablePlugin()
91      {
92          when(bundle.getState()).thenReturn(Bundle.RESOLVED);
93          plugin.enable();
94      }
95  
96      @Test
97      public void testDisabled() throws BundleException
98      {
99          when(bundle.getState()).thenReturn(Bundle.ACTIVE);
100         plugin.disable();
101         verify(bundle).stop();
102     }
103 
104     @Test
105     public void testDisabledOnNonDynamicPlugin() throws BundleException
106     {
107         plugin.addModuleDescriptor(new StaticModuleDescriptor());
108         plugin.onPluginFrameworkStartedEvent(null);
109         when(bundle.getState()).thenReturn(Bundle.ACTIVE);
110         plugin.disable();
111         verify(bundle, never()).stop();
112     }
113 
114     @Test
115     public void testUninstall() throws BundleException
116     {
117         _enablePlugin();
118         when(bundle.getState()).thenReturn(Bundle.ACTIVE);
119         _assertPluginUninstalled();
120     }
121 
122     @Test
123     public void testUninstallingUninstalledBundle() throws BundleException
124     {
125         _enablePlugin();
126         when(bundle.getState()).thenReturn(Bundle.ACTIVE).thenReturn(Bundle.UNINSTALLED);
127         _assertPluginUninstalled();
128         _assertPluginUninstalled();
129     }
130 
131     private void _assertPluginUninstalled()
132     {
133         plugin.uninstallInternal();
134         assertEquals(plugin.getPluginState(), PluginState.UNINSTALLED);
135     }
136 
137     @Test
138     public void testOnPluginContainerRefresh()
139     {
140         _enablePlugin();
141         assertEquals(PluginState.ENABLING, plugin.getPluginState());
142         final PluginContainerRefreshedEvent event = new PluginContainerRefreshedEvent(new Object(), PLUGIN_KEY);
143         plugin.onPluginContainerRefresh(event);
144         assertEquals(PluginState.ENABLED, plugin.getPluginState());
145     }
146 
147     @Test
148     public void testQuickOnPluginContainerRefresh() throws BundleException, InterruptedException
149     {
150         when(bundle.getState()).thenReturn(Bundle.RESOLVED);
151 
152         final ConcurrentStateEngine states = new ConcurrentStateEngine(
153                 "bundle-starting", "container-created", "bundle-started", "mid-start", "end");
154         when(bundle.getBundleContext()).thenAnswer(new Answer<Object>()
155         {
156             public Object answer(final InvocationOnMock invocation) throws Throwable
157             {
158                 states.tryNextState("bundle-started", "mid-start");
159                 final BundleContext context = mock(BundleContext.class);
160                 when(context.getBundle(OsgiSystemBundleUtil.SYSTEM_BUNDLE_ID)).thenReturn(systemBundle);
161 
162                 return context;
163             }
164         });
165 
166         doAnswer(new Answer<Object>()
167         {
168             public Object answer(final InvocationOnMock invocation) throws Throwable
169             {
170                 states.state("bundle-starting");
171                 final Thread t = new Thread()
172                 {
173                     public void run()
174                     {
175                         final PluginContainerRefreshedEvent event = new PluginContainerRefreshedEvent(
176                                 new Object(), PLUGIN_KEY);
177                         states.tryNextState("bundle-starting", "container-created");
178                         plugin.onPluginContainerRefresh(event);
179                     }
180                 };
181                 t.start();
182                 states.tryNextState("container-created", "bundle-started");
183                 return null;
184             }
185         }).when(bundle).start();
186 
187         plugin.enable();
188 
189 
190         states.tryNextState("mid-start", "end");
191 
192         assertEquals(PluginState.ENABLED, plugin.getPluginState());
193     }
194 
195     @Test
196     public void testOnPluginContainerRefreshNotEnabling()
197     {
198         final PluginContainerRefreshedEvent event = new PluginContainerRefreshedEvent(new Object(), PLUGIN_KEY);
199         when(bundle.getState()).thenReturn(Bundle.ACTIVE);
200         plugin.disable();
201         plugin.onPluginContainerRefresh(event);
202         assertEquals(PluginState.DISABLED, plugin.getPluginState());
203     }
204 
205     @Test
206     public void testUninstallRetryLogic() throws BundleException
207     {
208         try
209         {
210             when(bundle.getState()).thenReturn(Bundle.ACTIVE);
211             plugin.enable();
212             when(bundle.getSymbolicName()).thenReturn("Mock Bundle");
213             doThrow(new BundleException("Mock Bundle Exception")).when(bundle).uninstall();
214             plugin.uninstallInternal();
215         }
216         catch (final Exception e)
217         {
218             assertTrue("Should throw an OsgiContainerException", e instanceof OsgiContainerException);
219         }
220         finally
221         {
222             assertEquals(PluginState.ENABLED, plugin.getPluginState());
223             verify(bundle, times(3)).uninstall();
224         }
225     }
226 
227     @Test
228     public void recoverFromPartialUninstall() throws Exception
229     {
230         // This test checks a code path that is unreachable if (a) only plugin code uninstalls bundles,
231         // and (b) felix satisfies the OSGi postconditions for {@link Bundle#uninstall}. However,
232         // since we're adding code to try to diagnose mysterious behaviour, an off the map test is justified.
233 
234         _enablePlugin();
235         when(bundle.getState()).thenReturn(Bundle.UNINSTALLED);
236         _assertPluginUninstalled();
237         verify(bundle, never()).uninstall();
238         verify(helper).onUninstall();
239         verify(pluginEventManager).unregister(plugin);
240     }
241 
242     @Test
243     public void resolveInvokesBundleResolution()
244     {
245         plugin.resolve();
246         verify(packageAdmin).resolveBundles(argThat(arrayContaining(bundle)));
247     }
248 
249     @RequiresRestart
250     public static class StaticModuleDescriptor extends AbstractModuleDescriptor<Object>
251     {
252         public StaticModuleDescriptor()
253         {
254             super(ModuleFactory.LEGACY_MODULE_FACTORY);
255         }
256 
257         public Object getModule()
258         {
259             return null;
260         }
261     }
262 }