View Javadoc

1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.IllegalPluginStateException;
4   import com.atlassian.plugin.PluginArtifact;
5   import com.atlassian.plugin.PluginArtifactBackedPlugin;
6   import com.atlassian.plugin.PluginException;
7   import com.atlassian.plugin.PluginInformation;
8   import com.atlassian.plugin.PluginPermission;
9   import com.atlassian.plugin.PluginState;
10  import com.atlassian.plugin.impl.AbstractPlugin;
11  import com.atlassian.plugin.osgi.container.OsgiContainerException;
12  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
13  import com.atlassian.plugin.osgi.util.BundleClassLoaderAccessor;
14  import com.atlassian.plugin.util.resource.AlternativeDirectoryResourceLoader;
15  import com.google.common.collect.ImmutableSet;
16  import org.osgi.framework.Bundle;
17  import org.osgi.framework.BundleEvent;
18  import org.osgi.framework.BundleException;
19  import org.osgi.framework.Constants;
20  import org.osgi.framework.SynchronousBundleListener;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import java.io.File;
25  import java.io.InputStream;
26  import java.net.URL;
27  import java.util.Date;
28  import java.util.jar.Manifest;
29  
30  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.extractOsgiPluginInformation;
31  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getAttributeWithoutValidation;
32  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getManifest;
33  import static com.google.common.base.Preconditions.checkNotNull;
34  
35  /**
36   * Plugin that wraps an OSGi bundle that has no plugin descriptor.
37   */
38  public final class OsgiBundlePlugin extends AbstractPlugin implements PluginArtifactBackedPlugin
39  {
40      private static final Logger log = LoggerFactory.getLogger(OsgiBundlePlugin.class);
41  
42      private final PluginArtifact pluginArtifact;
43      private final SynchronousBundleListener bundleStartStopListener;
44      private final Date dateLoaded;
45  
46      /** The OSGi container manager, which will be null after installInternal is called. */
47      private OsgiContainerManager osgiContainerManager;
48      /** The OSGi bundle, which will be null until installInternal is called. */
49      private Bundle bundle;
50      /** The ClassLoader for the OSGi bundle, which will be null until installInternal is called. */
51      private ClassLoader bundleClassLoader;
52  
53      private OsgiBundlePlugin(final String pluginKey, final PluginArtifact pluginArtifact)
54      {
55          this.pluginArtifact = checkNotNull(pluginArtifact);
56          this.bundleStartStopListener = new SynchronousBundleListener()
57          {
58              public void bundleChanged(final BundleEvent bundleEvent)
59              {
60                  // If bundle is null, this == will be false. This silently ignores events after we're uninstalled,
61                  // but i am ok with that, because that's the prior behaviour, and we can preserve that for now.
62                  if (bundleEvent.getBundle() == bundle)
63                  {
64                      if (bundleEvent.getType() == BundleEvent.STOPPING)
65                      {
66                          setPluginState(PluginState.DISABLED);
67                      }
68                      else if (bundleEvent.getType() == BundleEvent.STARTED)
69                      {
70                          setPluginState(PluginState.ENABLED);
71                      }
72                  }
73              }
74          };
75          this.dateLoaded = new Date();
76          setPluginsVersion(2);
77          setKey(pluginKey);
78          setSystemPlugin(false);
79      }
80  
81      /**
82       * Create a plugin wrapper for an already installed Bundle.
83       * @param bundle The installed Bundle.
84       * @param pluginKey The plugin key.
85       * @param pluginArtifact The plugin artifact that was installed.
86       * @deprecated since 3.0.23, use {@link #OsgiBundlePlugin(OsgiContainerManager, String, PluginArtifact)}
87       */
88      @Deprecated
89      public OsgiBundlePlugin(final Bundle bundle, final String pluginKey, final PluginArtifact pluginArtifact)
90      {
91          this(pluginKey, pluginArtifact);
92          // Leave container null, as we're already installed
93          this.bundle = checkNotNull(bundle);
94          this.bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
95  
96          // It feels like this should be a call to OsgiHeaderUtil.extractOsgiPluginInformation, but Bundle.getHeaders is the square
97          // peg Dictionary to extractOsgiPluginInformation's round holed Manifest. Rather than switching this code to parse the jar
98          // again instead, I'm leaving it as is but deprecated.
99          final PluginInformation pluginInformation = new PluginInformation();
100         pluginInformation.setDescription((String) bundle.getHeaders().get(Constants.BUNDLE_DESCRIPTION));
101         pluginInformation.setVersion((String) bundle.getHeaders().get(Constants.BUNDLE_VERSION));
102         pluginInformation.setVendorName((String) bundle.getHeaders().get(Constants.BUNDLE_VENDOR));
103         pluginInformation.setPermissions(ImmutableSet.of(PluginPermission.EXECUTE_JAVA));
104         setPluginInformation(pluginInformation);
105 
106         setName((String) bundle.getHeaders().get(Constants.BUNDLE_NAME));
107         setSystemPlugin(false);
108     }
109 
110     /**
111      * Create a plugin wrapper which installs the bundle when the plugin is installed.
112      *
113      * @param osgiContainerManager the container to install into when the plugin is installed.
114      * @param pluginKey The plugin key.
115      * @param pluginArtifact The The plugin artifact to install.
116      */
117     public OsgiBundlePlugin(final OsgiContainerManager osgiContainerManager,
118             final String pluginKey,
119             final PluginArtifact pluginArtifact)
120     {
121         this(pluginKey, pluginArtifact);
122         this.osgiContainerManager = checkNotNull(osgiContainerManager);
123         // Leave bundle and bundleClassLoader null until we are installed.
124 
125         final Manifest manifest = getManifest(pluginArtifact);
126         if (null != manifest)
127         {
128             setName(getAttributeWithoutValidation(manifest, Constants.BUNDLE_NAME));
129             // The next false is because at the OSGi level, Bundle-Version is not required.
130             setPluginInformation(extractOsgiPluginInformation(manifest, false));
131         }
132         // else this will get flagged as a bad jar, because it's not a bundle, later, and we can let this through
133     }
134 
135     @Override
136     public Date getDateLoaded()
137     {
138         return dateLoaded;
139     }
140 
141     @Override
142     public Date getDateInstalled()
143     {
144         long date = getPluginArtifact().toFile().lastModified();
145         if (date == 0)
146         {
147             date = getDateLoaded().getTime();
148         }
149         return new Date(date);
150     }
151 
152     public boolean isUninstallable()
153     {
154         return true;
155     }
156 
157     public boolean isDeleteable()
158     {
159         return true;
160     }
161 
162     public boolean isDynamicallyLoaded()
163     {
164         return true;
165     }
166 
167     public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException
168     {
169         return BundleClassLoaderAccessor.loadClass(getBundleOrFail(), clazz);
170     }
171 
172     public URL getResource(final String name)
173     {
174         return getBundleClassLoaderOrFail().getResource(name);
175     }
176 
177     public InputStream getResourceAsStream(final String name)
178     {
179         return getBundleClassLoaderOrFail().getResourceAsStream(name);
180     }
181 
182     @Override
183     protected void installInternal() throws OsgiContainerException, IllegalPluginStateException
184     {
185         super.installInternal();
186         if (null != osgiContainerManager)
187         {
188             // We're pending installation, so install
189             final File file = pluginArtifact.toFile();
190             final boolean allowReference = PluginArtifact.AllowsReference.Default.allowsReference(pluginArtifact);
191             bundle = OsgiContainerManager.AllowsReferenceInstall.Default.installBundle(osgiContainerManager, file, allowReference);
192             bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
193             osgiContainerManager = null;
194         }
195         else if (null == bundle)
196         {
197             throw new IllegalPluginStateException("Cannot reuse instance for bundle '" + getKey() + "'");
198         }
199         // else this could be a reinstall or not, but we can't tell, so we let it slide
200     }
201 
202     @Override
203     protected void uninstallInternal()
204     {
205         try
206         {
207             if (bundleIsUsable("uninstall"))
208             {
209                 if (bundle.getState() != Bundle.UNINSTALLED)
210                 {
211                     bundle.uninstall();
212                 }
213                 else
214                 {
215                     // A previous uninstall aborted early ?
216                     log.warn("Bundle '{}' already UNINSTALLED, but still held", getKey());
217                 }
218                 bundle = null;
219                 bundleClassLoader = null;
220             }
221         }
222         catch (final BundleException e)
223         {
224             throw new PluginException(e);
225         }
226     }
227 
228     @Override
229     protected PluginState enableInternal()
230     {
231         log.debug("Enabling OSGi bundled plugin '{}'", getKey());
232         try
233         {
234             if (bundleIsUsable("enable"))
235             {
236                 if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null)
237                 {
238                     log.debug("Plugin '{}' bundle is NOT a fragment, starting.", getKey());
239                     bundle.start();
240                     bundle.getBundleContext().addBundleListener(bundleStartStopListener);
241                 }
242                 else
243                 {
244                     log.debug("Plugin '{}' bundle is a fragment, not doing anything.", getKey());
245                 }
246             }
247 
248             return PluginState.ENABLED;
249         }
250         catch (final BundleException e)
251         {
252             throw new PluginException(e);
253         }
254     }
255 
256     @Override
257     protected void disableInternal()
258     {
259         try
260         {
261             if (bundleIsUsable("disable"))
262             {
263                 if (bundle.getState() == Bundle.ACTIVE)
264                 {
265                     bundle.stop();
266                 }
267                 else
268                 {
269                     log.warn("Cannot disable Bundle '{}', not ACTIVE", getKey());
270                 }
271             }
272         }
273         catch (final BundleException e)
274         {
275             throw new PluginException(e);
276         }
277     }
278 
279     public ClassLoader getClassLoader()
280     {
281         return getBundleClassLoaderOrFail();
282     }
283 
284     public PluginArtifact getPluginArtifact()
285     {
286         return pluginArtifact;
287     }
288 
289     private String getInstallationStateExplanation()
290     {
291         return (null != osgiContainerManager) ? "not yet installed" : "already uninstalled";
292     }
293 
294     private boolean bundleIsUsable(final String task)
295     {
296         if (null != bundle)
297         {
298             return true;
299         }
300         else
301         {
302             final String why = getInstallationStateExplanation();
303             log.warn("Cannot {} {} bundle '{}'", new Object[] { task, why, getKey()});
304             return false;
305         }
306     }
307 
308     private <T> T getOrFail(final T what, final String name) throws PluginException
309     {
310         if (null == what)
311         {
312             throw new IllegalPluginStateException("Cannot use " + name + " of " + getInstallationStateExplanation() + " '"
313                     + getKey() + "' from '" + pluginArtifact + "'");
314         }
315         else
316         {
317             return what;
318         }
319     }
320 
321     private Bundle getBundleOrFail() throws PluginException
322     {
323         return getOrFail(bundle, "bundle");
324     }
325 
326     private ClassLoader getBundleClassLoaderOrFail() throws PluginException
327     {
328         return getOrFail(bundleClassLoader, "bundleClassLoader");
329     }
330 }