View Javadoc

1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.Application;
4   import com.atlassian.plugin.JarPluginArtifact;
5   import com.atlassian.plugin.ModuleDescriptorFactory;
6   import com.atlassian.plugin.Plugin;
7   import com.atlassian.plugin.PluginArtifact;
8   import com.atlassian.plugin.PluginInformation;
9   import com.atlassian.plugin.PluginParseException;
10  import com.atlassian.plugin.PluginPermission;
11  import com.atlassian.plugin.event.PluginEventManager;
12  import com.atlassian.plugin.factories.AbstractPluginFactory;
13  import com.atlassian.plugin.impl.UnloadablePlugin;
14  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
15  import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
16  import com.atlassian.plugin.osgi.factory.transform.DefaultPluginTransformer;
17  import com.atlassian.plugin.osgi.factory.transform.PluginTransformationException;
18  import com.atlassian.plugin.osgi.factory.transform.PluginTransformer;
19  import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
20  import com.atlassian.plugin.parsers.DescriptorParser;
21  import com.google.common.base.Predicate;
22  import com.google.common.collect.ImmutableSet;
23  import com.google.common.collect.Ranges;
24  import org.apache.commons.io.IOUtils;
25  import org.osgi.framework.Constants;
26  import org.osgi.util.tracker.ServiceTracker;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.util.Set;
34  import java.util.jar.Manifest;
35  
36  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getAttributeWithoutValidation;
37  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getNonEmptyAttribute;
38  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getValidatedAttribute;
39  import static com.google.common.base.Preconditions.checkNotNull;
40  
41  /**
42   * Plugin loader that starts an OSGi container and loads plugins into it, wrapped as OSGi bundles.  Supports
43   * <ul>
44   *  <li>Dynamic loading of module descriptors via OSGi services</li>
45   *  <li>Delayed enabling until the plugin container is active</li>
46   *  <li>XML or Jar manifest configuration</li>
47   * </ul>
48   */
49  public final class OsgiPluginFactory extends AbstractPluginFactory
50  {
51      private static final Logger log = LoggerFactory.getLogger(OsgiPluginFactory.class);
52  
53      public interface PluginTransformerFactory
54      {
55          PluginTransformer newPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applicationKeys, String pluginDescriptorPath, OsgiContainerManager osgi);
56      }
57  
58      public static class DefaultPluginTransformerFactory implements PluginTransformerFactory
59      {
60          public PluginTransformer newPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applicationKeys, String pluginDescriptorPath, OsgiContainerManager osgi)
61          {
62              return new DefaultPluginTransformer(cache, systemExports, applicationKeys, pluginDescriptorPath, osgi);
63          }
64      }
65  
66      private final OsgiContainerManager osgi;
67      private final String pluginDescriptorFileName;
68      private final PluginEventManager pluginEventManager;
69      private final Set<Application> applications;
70      private final OsgiPersistentCache persistentCache;
71      private final PluginTransformerFactory pluginTransformerFactory;
72  
73      private volatile PluginTransformer pluginTransformer;
74  
75      private final OsgiChainedModuleDescriptorFactoryCreator osgiChainedModuleDescriptorFactoryCreator;
76  
77      /**
78       * Default constructor
79       */
80      public OsgiPluginFactory(String pluginDescriptorFileName, Set<Application> applications, OsgiPersistentCache persistentCache, final OsgiContainerManager osgi, PluginEventManager pluginEventManager)
81      {
82          this(pluginDescriptorFileName, applications, persistentCache, osgi, pluginEventManager, new DefaultPluginTransformerFactory());
83      }
84  
85      /**
86       * Constructor for implementations that want to override the DefaultPluginTransformer with a custom implementation
87       */
88      public OsgiPluginFactory(String pluginDescriptorFileName, Set<Application> applications, OsgiPersistentCache persistentCache, final OsgiContainerManager osgi, PluginEventManager pluginEventManager, PluginTransformerFactory pluginTransformerFactory)
89      {
90          super(new OsgiPluginXmlDescriptorParserFactory(), applications);
91          this.pluginDescriptorFileName = checkNotNull(pluginDescriptorFileName, "Plugin descriptor is required");
92          this.osgi = checkNotNull(osgi, "The OSGi container is required");
93          this.applications = checkNotNull(applications, "Applications is required!");
94          this.persistentCache = checkNotNull(persistentCache, "The osgi persistent cache is required");
95          this.pluginEventManager = checkNotNull(pluginEventManager, "The plugin event manager is required");
96          this.pluginTransformerFactory = checkNotNull(pluginTransformerFactory, "The plugin transformer factory is required");
97          this.osgiChainedModuleDescriptorFactoryCreator = new OsgiChainedModuleDescriptorFactoryCreator(new OsgiChainedModuleDescriptorFactoryCreator.ServiceTrackerFactory()
98          {
99              public ServiceTracker create(String className)
100             {
101                 return osgi.getServiceTracker(className);
102             }
103         });
104     }
105 
106     private PluginTransformer getPluginTransformer()
107     {
108         if (pluginTransformer == null)
109         {
110             String exportString = (String) osgi.getBundles()[0].getHeaders()
111                     .get(Constants.EXPORT_PACKAGE);
112             SystemExports exports = new SystemExports(exportString);
113             pluginTransformer = pluginTransformerFactory.newPluginTransformer(persistentCache, exports, applications, pluginDescriptorFileName, osgi);
114         }
115         return pluginTransformer;
116     }
117 
118     public String canCreate(PluginArtifact pluginArtifact) throws PluginParseException
119     {
120         if (hasDescriptor(checkNotNull(pluginArtifact)))
121         {
122             return getPluginKeyFromDescriptor(checkNotNull(pluginArtifact));
123         }
124         else // no descriptor, pure OSGi bundle
125         {
126             return getPluginKeyFromManifest(pluginArtifact);
127         }
128     }
129 
130     @Override
131     protected InputStream getDescriptorInputStream(PluginArtifact pluginArtifact)
132     {
133         return pluginArtifact.getResourceAsStream(pluginDescriptorFileName);
134     }
135 
136     @Override
137     protected Predicate<Integer> isValidPluginsVersion()
138     {
139         return Ranges.singleton(Plugin.VERSION_2);
140     }
141 
142     /**
143      * @param pluginArtifact The plugin artifact
144      * @return The plugin key if a manifest is present and contains {@link OsgiPlugin#ATLASSIAN_PLUGIN_KEY} and
145      * {@link Constants#BUNDLE_VERSION}
146      */
147     private String getPluginKeyFromManifest(PluginArtifact pluginArtifact)
148     {
149         final Manifest mf = getManifest(pluginArtifact);
150         if (mf != null)
151         {
152             final String key = mf.getMainAttributes().getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY);
153             final String version = mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
154             if (key != null)
155             {
156                 if (version != null)
157                 {
158                     return key;
159                 }
160                 else
161                 {
162                     log.warn("Found plugin key '" + key + "' in the manifest but no bundle version, so it can't be loaded as an OsgiPlugin");
163                 }
164             }
165         }
166         return null;
167     }
168 
169     private Manifest getManifest(PluginArtifact pluginArtifact)
170     {
171         InputStream descriptorClassStream = pluginArtifact.getResourceAsStream("META-INF/MANIFEST.MF");
172         if (descriptorClassStream != null)
173         {
174             try
175             {
176                 return new Manifest(descriptorClassStream);
177             }
178             catch (IOException e)
179             {
180                 log.error("Cannot read manifest from plugin artifact " + pluginArtifact.getName(), e);
181             }
182             finally
183             {
184                 IOUtils.closeQuietly(descriptorClassStream);
185             }
186         }
187         return null;
188     }
189 
190     /**
191      * Deploys the plugin artifact.  The artifact will only undergo transformation if a plugin descriptor can be found
192      * and the "Atlassian-Plugin-Key" value is not already defined in the manifest.
193      *
194      * @param pluginArtifact          the plugin artifact to deploy
195      * @param moduleDescriptorFactory The factory for plugin modules
196      * @return The instantiated and populated plugin
197      * @throws PluginParseException If the descriptor cannot be parsed
198      * @throws IllegalArgumentException If the plugin descriptor isn't found, and the plugin key and bundle version aren't
199      * specified in the manifest
200      * @since 2.2.0
201      */
202     public Plugin create(PluginArtifact pluginArtifact, ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
203     {
204         checkNotNull(pluginArtifact, "The plugin deployment unit is required");
205         checkNotNull(moduleDescriptorFactory, "The module descriptor factory is required");
206 
207         Plugin plugin = null;
208         InputStream pluginDescriptor = null;
209         try
210         {
211             pluginDescriptor = pluginArtifact.getResourceAsStream(pluginDescriptorFileName);
212             if (pluginDescriptor != null)
213             {
214                 ModuleDescriptorFactory combinedFactory = getChainedModuleDescriptorFactory(moduleDescriptorFactory, pluginArtifact);
215                 DescriptorParser parser = descriptorParserFactory.getInstance(pluginDescriptor, applications);
216 
217                 final PluginArtifact artifactToInstall;
218 
219                 // only transform the artifact if no plugin key is found in the manifest
220                 final String pluginKeyFromManifest = getPluginKeyFromManifest(pluginArtifact);
221                 if (pluginKeyFromManifest == null)
222                 {
223                     log.debug("Plugin key NOT found in manifest at entry {}, undergoing transformation", OsgiPlugin.ATLASSIAN_PLUGIN_KEY);
224                     artifactToInstall = createOsgiPluginJar(pluginArtifact);
225                 }
226                 else
227                 {
228                     log.debug("Plugin key found in manifest at entry {}, skipping transformation for '{}'", OsgiPlugin.ATLASSIAN_PLUGIN_KEY, pluginKeyFromManifest);
229                     artifactToInstall = pluginArtifact;
230                 }
231 
232                 final Plugin osgiPlugin = new OsgiPlugin(parser.getKey(), osgi, artifactToInstall, pluginArtifact, pluginEventManager);
233 
234                 // Temporarily configure plugin until it can be properly installed
235                 plugin = parser.configurePlugin(combinedFactory, osgiPlugin);
236             }
237             else
238             {
239                 Manifest mf = getManifest(pluginArtifact);
240                 plugin = extractOsgiPlugin(pluginArtifact, mf, osgi, pluginEventManager);
241                 plugin.setPluginInformation(extractOsgiPluginInformation(mf));
242             }
243         }
244         catch (PluginTransformationException ex)
245         {
246             return reportUnloadablePlugin(pluginArtifact.toFile(), ex);
247         }
248         finally
249         {
250             IOUtils.closeQuietly(pluginDescriptor);
251         }
252         return plugin;
253     }
254 
255     private static Plugin extractOsgiPlugin(PluginArtifact pluginArtifact, Manifest mf, final OsgiContainerManager osgi, final PluginEventManager pluginEventManager)
256     {
257         Plugin plugin;
258         String pluginKey = getNonEmptyAttribute(mf, OsgiPlugin.ATLASSIAN_PLUGIN_KEY);
259         String bundleName = getAttributeWithoutValidation(mf, Constants.BUNDLE_NAME);
260 
261         plugin = new OsgiPlugin(pluginKey, osgi, pluginArtifact, pluginArtifact, pluginEventManager);
262         plugin.setKey(pluginKey);
263         plugin.setPluginsVersion(2);
264         plugin.setName(bundleName);
265         return plugin;
266     }
267 
268     private PluginInformation extractOsgiPluginInformation(final Manifest manifest) {
269         String pluginVersion = getValidatedAttribute(manifest, Constants.BUNDLE_VERSION);
270         String vendorName = getAttributeWithoutValidation(manifest, Constants.BUNDLE_VENDOR);
271         String bundleDescription = getAttributeWithoutValidation(manifest, Constants.BUNDLE_DESCRIPTION);
272         PluginInformation info = new PluginInformation();
273         info.setVersion(pluginVersion);
274         info.setDescription(bundleDescription);
275         info.setVendorName(vendorName);
276         info.setPermissions(ImmutableSet.of(PluginPermission.EXECUTE_JAVA)); // OSGi plugins require execute Java.
277         return info;
278     }
279 
280 
281     /**
282      * Get a chained module descriptor factory that includes any dynamically available descriptor factories
283      *
284      * @param originalFactory The factory provided by the host application
285      * @param pluginArtifact
286      * @return The composite factory
287      */
288     private ModuleDescriptorFactory getChainedModuleDescriptorFactory(ModuleDescriptorFactory originalFactory, final PluginArtifact pluginArtifact)
289     {
290         return osgiChainedModuleDescriptorFactoryCreator.create(new OsgiChainedModuleDescriptorFactoryCreator.ResourceLocator()
291         {
292             public boolean doesResourceExist(String name)
293             {
294                 return pluginArtifact.doesResourceExist(name);
295             }
296         }, originalFactory);
297     }
298 
299     private PluginArtifact createOsgiPluginJar(PluginArtifact pluginArtifact)
300     {
301         File transformedFile = getPluginTransformer().transform(pluginArtifact, osgi.getHostComponentRegistrations());
302         return new JarPluginArtifact(transformedFile);
303     }
304 
305     private Plugin reportUnloadablePlugin(File file, Exception e)
306     {
307         log.error("Unable to load plugin: " + file, e);
308 
309         UnloadablePlugin plugin = new UnloadablePlugin();
310         plugin.setErrorText("Unable to load plugin: " + e.getMessage());
311         return plugin;
312     }
313 }