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