View Javadoc

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