View Javadoc
1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.ModuleDescriptorFactory;
5   import com.atlassian.plugin.Plugin;
6   import com.atlassian.plugin.PluginAccessor;
7   import com.atlassian.plugin.PluginArtifact;
8   import com.atlassian.plugin.PluginParseException;
9   import com.atlassian.plugin.factories.AbstractPluginFactory;
10  import com.atlassian.plugin.impl.UnloadablePlugin;
11  import com.atlassian.plugin.osgi.container.OsgiContainerManager;
12  import com.atlassian.plugin.parsers.DescriptorParser;
13  import com.atlassian.plugin.parsers.XmlDescriptorParserFactory;
14  import com.google.common.collect.ImmutableSet;
15  import org.dom4j.Element;
16  import org.osgi.framework.Constants;
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.util.function.Predicate;
23  import java.util.jar.Attributes;
24  import java.util.jar.Manifest;
25  
26  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getManifest;
27  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getPluginKey;
28  import static com.atlassian.plugin.parsers.XmlDescriptorParserUtils.addModule;
29  import static com.google.common.base.Preconditions.checkNotNull;
30  
31  /**
32   * Plugin deployer that deploys OSGi bundles that don't contain a (spring) context instantiated by the
33   * plugin system
34   */
35  public final class OsgiBundleFactory extends AbstractPluginFactory {
36  
37      private static final Logger log = LoggerFactory.getLogger(OsgiBundleFactory.class);
38  
39      private static final Predicate<Integer> IS_PLUGIN_2_OR_HIGHER = input -> input != null && input >= Plugin.VERSION_2;
40  
41      /*
42       * OSGi container to install plugin to 
43       */
44      private final OsgiContainerManager osgiContainerManager;
45  
46      /*
47       * Atlassian plugin descriptor filename 
48       */
49      private final String pluginDescriptorFileName;
50  
51      /*
52       * Build to create plugin specific ModuleFactories needs to parse descriptors (atlassian-plugin.xml)
53       */
54      private final OsgiChainedModuleDescriptorFactoryCreator osgiChainedModuleDescriptorFactoryCreator;
55  
56      public OsgiBundleFactory(final OsgiContainerManager osgi) {
57          this(PluginAccessor.Descriptor.FILENAME, osgi);
58      }
59  
60      public OsgiBundleFactory(final String pluginDescriptorFileName, final OsgiContainerManager osgi) {
61          super(new XmlDescriptorParserFactory(), ImmutableSet.of());
62          this.pluginDescriptorFileName = checkNotNull(pluginDescriptorFileName);
63          this.osgiContainerManager = checkNotNull(osgi, "The osgi container is required");
64  
65          this.osgiChainedModuleDescriptorFactoryCreator = new OsgiChainedModuleDescriptorFactoryCreator(osgiContainerManager::getServiceTracker);
66      }
67  
68      @Override
69      protected InputStream getDescriptorInputStream(PluginArtifact pluginArtifact) {
70          return pluginArtifact.getResourceAsStream(pluginDescriptorFileName);
71      }
72  
73      @Override
74      protected Predicate<Integer> isValidPluginsVersion() {
75          return IS_PLUGIN_2_OR_HIGHER;
76      }
77  
78      /**
79       * Determines if this deployer can handle this artifact by looking for the plugin descriptor
80       * <p>
81       * OsgiBundlePlugin wraps usual OSGi Bundle into Atlassian Plugin. Factory could create plugin for any
82       * bundle that is not Spring powered even for those has not atlassian-plugin.xml. Note that some parts
83       * of plugin descriptor, such as &lt;component&gt; or &lt;component-import&gt;, relies on IoC container
84       * in order to work, so without PluginContainerAccessor service exported it won't work and will throw
85       * an exception
86       *
87       * @param pluginArtifact The artifact to test
88       * @return The plugin key, null if it cannot load the plugin
89       * @throws com.atlassian.plugin.PluginParseException If there are exceptions parsing the plugin configuration
90       */
91      public String canCreate(final PluginArtifact pluginArtifact) throws PluginParseException {
92          checkNotNull(pluginArtifact, "The plugin artifact is required");
93          final boolean isPlugin = hasDescriptor(checkNotNull(pluginArtifact));
94          final boolean hasSpring = pluginArtifact.containsSpringContext();
95          final boolean isTransformless = getPluginKeyFromManifest(checkNotNull(pluginArtifact)) != null;
96          final Manifest mf = getManifest(pluginArtifact);
97  
98          String key = null;
99          if (!isTransformless && !isPlugin && mf != null) {
100             final Attributes attrs = mf.getMainAttributes();
101 
102             // Only a proper OSGi bundle could be loaded, otherwise it is a simple JAR which should be
103             // loaded as P1 (xml classpath) plugin
104             if (attrs.containsKey(new Attributes.Name(Constants.BUNDLE_SYMBOLICNAME))) {
105                 key = getPluginKey(mf);
106             }
107         }
108 
109         if (key == null && (isTransformless && !hasSpring)) {
110             key = isPlugin ? getPluginKeyFromDescriptor(checkNotNull(pluginArtifact)) : getPluginKeyFromManifest(pluginArtifact);
111         }
112 
113         return key;
114     }
115 
116     /**
117      * Create a plugin from the given artifact.
118      *
119      * @param pluginArtifact          the plugin artifact containing the plugin.
120      * @param moduleDescriptorFactory The factory for plugin modules.
121      * @return The instantiated and populated plugin, or an {@link UnloadablePlugin} if the plugin cannot be loaded.
122      * @since 2.2.0
123      */
124     public Plugin create(final PluginArtifact pluginArtifact, final ModuleDescriptorFactory moduleDescriptorFactory) {
125         checkNotNull(pluginArtifact, "The plugin artifact is required");
126         checkNotNull(moduleDescriptorFactory, "The module descriptor factory is required");
127 
128         // Check if plugin could be created and fail fast if not
129         final String pluginKey = canCreate(pluginArtifact);
130         if (null == pluginKey) {
131             log.warn("Unable to load plugin from '{}'", pluginArtifact);
132             return new UnloadablePlugin("PluginArtifact has no manifest or is not a bundle: '" + pluginArtifact + "'");
133         }
134 
135         // If plugin could be loaded then it is:
136         // 1. pure OSGi bundle, no atlassian descriptor, no module factory needs
137         // 2. atlassian plugin - needs for a module descriptor
138         Plugin plugin;
139         try (InputStream pluginDescriptor = pluginArtifact.getResourceAsStream(pluginDescriptorFileName)) {
140             plugin = new OsgiBundlePlugin(osgiContainerManager, pluginKey, pluginArtifact);
141             if (pluginDescriptor != null) {
142                 // Plugin specific module factory to allow plugin internal resources usage
143                 ModuleDescriptorFactory combinedFactory = osgiChainedModuleDescriptorFactoryCreator.create(
144                         pluginArtifact::doesResourceExist, moduleDescriptorFactory);
145 
146 
147                 // Parse descriptor & configure plugin
148                 DescriptorParser parser = descriptorParserFactory.getInstance(pluginDescriptor, applications);
149                 plugin = parser.configurePlugin(combinedFactory, plugin);
150             }
151         } catch (IOException ex) {
152             log.error("Unable to load plugin: {}", pluginArtifact.toFile(), ex);
153             plugin = new UnloadablePlugin("Unable to load plugin: " + ex.getMessage());
154         }
155 
156         return plugin;
157     }
158 
159     @Override
160     public ModuleDescriptor<?> createModule(final Plugin plugin, final Element module, final ModuleDescriptorFactory moduleDescriptorFactory) {
161         if (plugin instanceof OsgiBundlePlugin) {
162             final ModuleDescriptorFactory combinedFactory = osgiChainedModuleDescriptorFactoryCreator.create(name -> {
163                 // This returns true to indicate that the listable module descriptor class is present in this plugin.
164                 // Those module descriptors are skipped when first installing the plugin, however no need for us to skip
165                 // here, as the plugin has been loaded/installed.
166                 return false;
167             }, moduleDescriptorFactory);
168 
169             return addModule(combinedFactory, plugin, module);
170         } else {
171             return null;
172         }
173     }
174 
175     private String getPluginKeyFromManifest(final PluginArtifact pluginArtifact) {
176         final Manifest mf = getManifest(pluginArtifact);
177         if (mf != null) {
178             final String key = mf.getMainAttributes().getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY);
179 
180             final String version = mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
181             if (key != null) {
182                 if (version != null) {
183                     return key;
184                 } else {
185                     log.warn("Found plugin key '" + key + "' in the manifest but no bundle version, so it can't be loaded as an OsgiPlugin");
186                 }
187             }
188         }
189         return null;
190     }
191 }