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