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