View Javadoc

1   package com.atlassian.plugin.osgi.factory;
2   
3   import java.io.File;
4   import java.io.InputStream;
5   import java.util.Arrays;
6   import java.util.Set;
7   import java.util.jar.Manifest;
8   import javax.annotation.Nullable;
9   
10  import com.atlassian.plugin.Application;
11  import com.atlassian.plugin.JarPluginArtifact;
12  import com.atlassian.plugin.ModuleDescriptorFactory;
13  import com.atlassian.plugin.Plugin;
14  import com.atlassian.plugin.PluginArtifact;
15  import com.atlassian.plugin.PluginParseException;
16  import com.atlassian.plugin.event.PluginEventManager;
17  import com.atlassian.plugin.factories.AbstractPluginFactory;
18  import com.atlassian.plugin.impl.UnloadablePlugin;
19  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
20  import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
21  import com.atlassian.plugin.osgi.factory.transform.DefaultPluginTransformer;
22  import com.atlassian.plugin.osgi.factory.transform.PluginTransformationException;
23  import com.atlassian.plugin.osgi.factory.transform.PluginTransformer;
24  import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
25  import com.atlassian.plugin.parsers.CompositeDescriptorParserFactory;
26  import com.atlassian.plugin.parsers.DescriptorParser;
27  
28  import com.google.common.base.Function;
29  import com.google.common.base.Predicate;
30  import com.google.common.collect.Ranges;
31  
32  import com.google.common.collect.Sets;
33  import org.apache.commons.io.IOUtils;
34  import org.apache.commons.lang.StringUtils;
35  import org.osgi.framework.Constants;
36  import org.osgi.util.tracker.ServiceTracker;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.extractOsgiPluginInformation;
41  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getAttributeWithoutValidation;
42  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getManifest;
43  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getNonEmptyAttribute;
44  import static com.google.common.base.Preconditions.checkNotNull;
45  import static com.google.common.collect.Iterables.transform;
46  
47  /**
48   * Plugin loader that starts an OSGi container and loads plugins into it, wrapped as OSGi bundles.  Supports
49   * <ul>
50   *  <li>Dynamic loading of module descriptors via OSGi services</li>
51   *  <li>Delayed enabling until the plugin container is active</li>
52   *  <li>XML or Jar manifest configuration</li>
53   * </ul>
54   */
55  public final class OsgiPluginFactory extends AbstractPluginFactory
56  {
57      private static final Logger log = LoggerFactory.getLogger(OsgiPluginFactory.class);
58  
59      public interface PluginTransformerFactory
60      {
61          PluginTransformer newPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applicationKeys, String pluginDescriptorPath, OsgiContainerManager osgi);
62      }
63  
64      public static class DefaultPluginTransformerFactory implements PluginTransformerFactory
65      {
66          public PluginTransformer newPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applicationKeys, String pluginDescriptorPath, OsgiContainerManager osgi)
67          {
68              return new DefaultPluginTransformer(cache, systemExports, applicationKeys, pluginDescriptorPath, osgi);
69          }
70      }
71  
72      private final OsgiContainerManager osgi;
73      private final String pluginDescriptorFileName;
74      private final PluginEventManager pluginEventManager;
75      private final Set<Application> applications;
76      private final OsgiPersistentCache persistentCache;
77      private final PluginTransformerFactory pluginTransformerFactory;
78  
79      private volatile PluginTransformer pluginTransformer;
80  
81      private final OsgiChainedModuleDescriptorFactoryCreator osgiChainedModuleDescriptorFactoryCreator;
82  
83      /**
84       * Default constructor
85       */
86      public OsgiPluginFactory(String pluginDescriptorFileName, Set<Application> applications, OsgiPersistentCache persistentCache, final OsgiContainerManager osgi, PluginEventManager pluginEventManager)
87      {
88          this(pluginDescriptorFileName, applications, persistentCache, osgi, pluginEventManager, new DefaultPluginTransformerFactory());
89      }
90  
91      /**
92       * Constructor for implementations that want to override the DefaultPluginTransformer with a custom implementation
93       */
94      public OsgiPluginFactory(String pluginDescriptorFileName, Set<Application> applications, OsgiPersistentCache persistentCache, final OsgiContainerManager osgi, PluginEventManager pluginEventManager, PluginTransformerFactory pluginTransformerFactory)
95      {
96          super(new OsgiPluginXmlDescriptorParserFactory(), applications);
97          this.pluginDescriptorFileName = checkNotNull(pluginDescriptorFileName, "Plugin descriptor is required");
98          this.osgi = checkNotNull(osgi, "The OSGi container is required");
99          this.applications = checkNotNull(applications, "Applications is required!");
100         this.persistentCache = checkNotNull(persistentCache, "The osgi persistent cache is required");
101         this.pluginEventManager = checkNotNull(pluginEventManager, "The plugin event manager is required");
102         this.pluginTransformerFactory = checkNotNull(pluginTransformerFactory, "The plugin transformer factory is required");
103         this.osgiChainedModuleDescriptorFactoryCreator = new OsgiChainedModuleDescriptorFactoryCreator(new OsgiChainedModuleDescriptorFactoryCreator.ServiceTrackerFactory()
104         {
105             public ServiceTracker create(String className)
106             {
107                 return osgi.getServiceTracker(className);
108             }
109         });
110     }
111 
112     private PluginTransformer getPluginTransformer()
113     {
114         if (pluginTransformer == null)
115         {
116             String exportString = (String) osgi.getBundles()[0].getHeaders()
117                     .get(Constants.EXPORT_PACKAGE);
118             SystemExports exports = new SystemExports(exportString);
119             pluginTransformer = pluginTransformerFactory.newPluginTransformer(persistentCache, exports, applications, pluginDescriptorFileName, osgi);
120         }
121         return pluginTransformer;
122     }
123 
124     public String canCreate(PluginArtifact pluginArtifact) throws PluginParseException
125     {
126         if (hasDescriptor(checkNotNull(pluginArtifact)))
127         {
128             return getPluginKeyFromDescriptor(checkNotNull(pluginArtifact));
129         }
130         else // no descriptor, pure OSGi bundle
131         {
132             return getPluginKeyFromManifest(pluginArtifact);
133         }
134     }
135 
136     @Override
137     protected InputStream getDescriptorInputStream(PluginArtifact pluginArtifact)
138     {
139         return pluginArtifact.getResourceAsStream(pluginDescriptorFileName);
140     }
141 
142     @Override
143     protected Predicate<Integer> isValidPluginsVersion()
144     {
145         return Ranges.singleton(Plugin.VERSION_2);
146     }
147 
148     /**
149      * @param pluginArtifact The plugin artifact
150      * @return The plugin key if a manifest is present and contains {@link OsgiPlugin#ATLASSIAN_PLUGIN_KEY} and
151      * {@link Constants#BUNDLE_VERSION}
152      */
153     private String getPluginKeyFromManifest(PluginArtifact pluginArtifact)
154     {
155         final Manifest mf = getManifest(pluginArtifact);
156         if (mf != null)
157         {
158             final String key = mf.getMainAttributes().getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY);
159             final String version = mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
160             if (key != null)
161             {
162                 if (version != null)
163                 {
164                     return key;
165                 }
166                 else
167                 {
168                     log.warn("Found plugin key '" + key + "' in the manifest but no bundle version, so it can't be loaded as an OsgiPlugin");
169                 }
170             }
171         }
172         return null;
173     }
174 
175     /**
176      * @param pluginArtifact The plugin artifact
177      * @return The scan folders  if a manifest is present and contains an {@link OsgiPlugin#ATLASSIAN_PLUGIN_KEY}
178      * @since 3.2.13
179      */
180     private Iterable<String> getScanFoldersFromManifest(PluginArtifact pluginArtifact)
181     {
182         final Set<String> scanFolders = Sets.newHashSet();
183         final Manifest mf = getManifest(pluginArtifact);
184         if (mf != null)
185         {
186             final String sf = mf.getMainAttributes().getValue(OsgiPlugin.ATLASSIAN_SCAN_FOLDERS);
187             if (StringUtils.isNotBlank(sf))
188             {
189                 String[] folders = sf.split(",");
190                 scanFolders.addAll(Arrays.asList(folders));
191             }
192         }
193         return scanFolders;
194     }
195 
196     /**
197      * Deploys the plugin artifact.  The artifact will only undergo transformation if a plugin descriptor can be found
198      * and the "Atlassian-Plugin-Key" value is not already defined in the manifest.
199      *
200      * @param pluginArtifact          the plugin artifact to deploy
201      * @param moduleDescriptorFactory The factory for plugin modules
202      * @return The instantiated and populated plugin
203      * @throws PluginParseException If the descriptor cannot be parsed
204      * @throws IllegalArgumentException If the plugin descriptor isn't found, and the plugin key and bundle version aren't
205      * specified in the manifest
206      * @since 2.2.0
207      */
208     public Plugin create(PluginArtifact pluginArtifact, ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
209     {
210         checkNotNull(pluginArtifact, "The plugin deployment unit is required");
211         checkNotNull(moduleDescriptorFactory, "The module descriptor factory is required");
212 
213         Plugin plugin = null;
214         InputStream pluginDescriptor = null;
215         try
216         {
217             pluginDescriptor = pluginArtifact.getResourceAsStream(pluginDescriptorFileName);
218             if (pluginDescriptor != null)
219             {
220                 ModuleDescriptorFactory combinedFactory = getChainedModuleDescriptorFactory(moduleDescriptorFactory, pluginArtifact);
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                 DescriptorParser parser = createDescriptorParser(artifactToInstall, pluginDescriptor);
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                 final Manifest manifest = getManifest(pluginArtifact);
245                 if (manifest != null)
246                 {
247                     plugin = extractOsgiPlugin(pluginArtifact, manifest, osgi, pluginEventManager);
248                     // The next true is because we require a Bundle-Version in our canCreate
249                     plugin.setPluginInformation(extractOsgiPluginInformation(manifest, true));
250                 }
251                 else
252                 {
253                     log.warn("Unable to load plugin from '{}', no manifest", pluginArtifact);
254 
255                     return new UnloadablePlugin("No manifest in PluginArtifact '" + pluginArtifact + "'");
256                 }
257             }
258         }
259         catch (PluginTransformationException ex)
260         {
261             return reportUnloadablePlugin(pluginArtifact.toFile(), ex);
262         }
263         finally
264         {
265             IOUtils.closeQuietly(pluginDescriptor);
266         }
267         return plugin;
268     }
269 
270     private DescriptorParser createDescriptorParser(final PluginArtifact pluginArtifact, final InputStream pluginDescriptor)
271     {
272         final Set<String> xmlPaths =  Sets.newHashSet();
273         for ( String path : getScanFoldersFromManifest(pluginArtifact))
274         {
275             xmlPaths.addAll(((PluginArtifact.HasExtraModuleDescriptors)pluginArtifact).extraModuleDescriptorFiles(path));
276         }
277 
278         final Iterable<InputStream> sources = transform(xmlPaths
279             , new Function<String, InputStream>()
280             {
281                 final Iterable<InputStream> streams = Sets.newHashSet();
282                 @Override
283                 public InputStream apply(@Nullable String source)
284                 {
285                     return pluginArtifact.getResourceAsStream(source);
286                 }
287             });
288         return ((CompositeDescriptorParserFactory)descriptorParserFactory).getInstance(pluginDescriptor, sources, applications);
289     }
290 
291     private static Plugin extractOsgiPlugin(PluginArtifact pluginArtifact, Manifest mf, final OsgiContainerManager osgi, final PluginEventManager pluginEventManager)
292     {
293         Plugin plugin;
294         String pluginKey = getNonEmptyAttribute(mf, OsgiPlugin.ATLASSIAN_PLUGIN_KEY);
295         String bundleName = getAttributeWithoutValidation(mf, Constants.BUNDLE_NAME);
296 
297         plugin = new OsgiPlugin(pluginKey, osgi, pluginArtifact, pluginArtifact, pluginEventManager);
298         plugin.setKey(pluginKey);
299         plugin.setPluginsVersion(2);
300         plugin.setName(bundleName);
301         return plugin;
302     }
303 
304     /**
305      * Get a chained module descriptor factory that includes any dynamically available descriptor factories
306      *
307      * @param originalFactory The factory provided by the host application
308      * @param pluginArtifact
309      * @return The composite factory
310      */
311     private ModuleDescriptorFactory getChainedModuleDescriptorFactory(ModuleDescriptorFactory originalFactory, final PluginArtifact pluginArtifact)
312     {
313         return osgiChainedModuleDescriptorFactoryCreator.create(new OsgiChainedModuleDescriptorFactoryCreator.ResourceLocator()
314         {
315             public boolean doesResourceExist(String name)
316             {
317                 return pluginArtifact.doesResourceExist(name);
318             }
319         }, originalFactory);
320     }
321 
322     private PluginArtifact createOsgiPluginJar(PluginArtifact pluginArtifact)
323     {
324         File transformedFile = getPluginTransformer().transform(pluginArtifact, osgi.getHostComponentRegistrations());
325         // A transformed jar is always allows reference, because the jar is sitting in a transform cache somewhere.
326         // A PluginTransformer implementation could conceivably break this, but it would be broken to do so.
327         return new JarPluginArtifact(transformedFile, PluginArtifact.AllowsReference.ReferenceMode.PERMIT_REFERENCE);
328     }
329 
330     private Plugin reportUnloadablePlugin(File file, Exception e)
331     {
332         log.error("Unable to load plugin: " + file, e);
333 
334         UnloadablePlugin plugin = new UnloadablePlugin();
335         plugin.setErrorText("Unable to load plugin: " + e.getMessage());
336         return plugin;
337     }
338 }