View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import com.atlassian.plugin.*;
4   import com.atlassian.plugin.classloader.PluginClassLoader;
5   import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
6   import com.atlassian.plugin.loaders.classloading.Scanner;
7   import com.atlassian.plugin.parsers.DescriptorParser;
8   import com.atlassian.plugin.parsers.DescriptorParserFactory;
9   import com.atlassian.plugin.parsers.XmlDescriptorParserFactory;
10  import org.apache.commons.logging.Log;
11  import org.apache.commons.logging.LogFactory;
12  import org.apache.commons.io.IOUtils;
13  
14  import java.io.File;
15  import java.io.InputStream;
16  import java.io.IOException;
17  import java.util.ArrayList;
18  import java.util.Collection;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.net.URL;
24  
25  /**
26   * A plugin loader to load plugins from a classloading on disk.
27   * <p>
28   * This creates a classloader for that classloading and loads plugins from all JARs from within it.
29   */
30  public class ClassLoadingPluginLoader implements DynamicPluginLoader
31  {
32      private static Log log = LogFactory.getLog(ClassLoadingPluginLoader.class);
33      private final String pluginDescriptorFileName;
34      private final PluginFactory pluginFactory;
35      private final Scanner scanner;
36      /** Maps {@link DeploymentUnit}s to {@link Plugin}s. */
37      private final Map plugins;
38      private DescriptorParserFactory descriptorParserFactory;
39      
40      public ClassLoadingPluginLoader(File path, PluginFactory pluginFactory)
41      {
42          this(path, PluginManager.PLUGIN_DESCRIPTOR_FILENAME, pluginFactory);
43      }
44  
45      public ClassLoadingPluginLoader(File path, String pluginDescriptorFileName, PluginFactory pluginFactory)
46      {
47          log.debug("Creating classloader for url " + path);
48          scanner = new Scanner(path);
49          this.pluginDescriptorFileName = pluginDescriptorFileName;
50          this.pluginFactory = pluginFactory;
51          this.descriptorParserFactory = new XmlDescriptorParserFactory();
52          plugins = new HashMap();
53      }
54  
55      public Collection loadAllPlugins(ModuleDescriptorFactory moduleDescriptorFactory)
56      {
57          scanner.scan();
58  
59          for (Iterator iterator = scanner.getDeploymentUnits().iterator(); iterator.hasNext();)
60          {
61              DeploymentUnit deploymentUnit = (DeploymentUnit) iterator.next();
62              try
63              {
64                  Plugin plugin = deployPluginFromUnit(deploymentUnit, moduleDescriptorFactory);
65                  plugins.put(deploymentUnit, plugin);
66              }
67              catch (PluginParseException e)
68              {
69                  // This catches errors so that the successfully loaded plugins can be returned.
70                  // It might be nicer if this method returned an object containing both the succesfully loaded
71                  // plugins and the unsuccessfully loaded plugins.
72                  log.error("Error loading descriptor for : " + deploymentUnit, e);
73              }
74          }
75  
76          return plugins.values();
77      }
78  
79      /**
80       * @param deploymentUnit the unit to deploy
81       * @param moduleDescriptorFactory the factory for the module descriptors
82       * @return the plugin loaded from the deployment unit, or an UnloadablePlugin instance if loading fails.
83       * @throws com.atlassian.plugin.PluginParseException if the plugin could not be parsed
84       */
85      protected Plugin deployPluginFromUnit(DeploymentUnit deploymentUnit, ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
86      {
87          Plugin plugin = null;
88          InputStream pluginDescriptor = null;
89          PluginClassLoader loader = new PluginClassLoader(deploymentUnit.getPath(), Thread.currentThread().getContextClassLoader());
90          try
91          {
92              URL pluginResourceUrl = loader.getLocalResource(pluginDescriptorFileName);
93              if (pluginResourceUrl == null)
94                  return handleNoDescriptor(deploymentUnit);
95  
96              pluginDescriptor = pluginResourceUrl.openStream();
97              // The plugin we get back may not be the same (in the case of an UnloadablePlugin), so add what gets returned, rather than the original
98              DescriptorParser parser = descriptorParserFactory.getInstance(pluginDescriptor);
99              plugin = parser.configurePlugin(moduleDescriptorFactory, createPlugin(parser, deploymentUnit, loader));
100         }
101         // Under normal conditions, the loader would be closed when the plugins are undeployed. However,
102         // these are not normal conditions, so we need to make sure that we close them explicitly.        
103         catch (PluginParseException e)
104         {
105             loader.close();
106             throw e;
107         }
108         catch (RuntimeException e)
109         {
110             loader.close();
111             throw new PluginParseException(e);
112         }
113         catch (Error e)
114         {
115             loader.close();
116             throw e;
117         }
118         catch (IOException e)
119         {
120             loader.close();
121             throw new PluginParseException(e);
122         } finally
123         {
124             IOUtils.closeQuietly(pluginDescriptor);
125         }
126         return plugin;
127     }
128 
129     protected void setDescriptorParserFactory(DescriptorParserFactory factory)
130     {
131         this.descriptorParserFactory = factory;
132     }
133 
134     /**
135      * Handles plugins with no descriptor.  Extension point for those that want to load plugins without the traditional
136      * XML descriptor file
137      * @param deploymentUnit the unit to deploy
138      * @return The plugin created
139      * @throws PluginParseException if the plugin could not be parsed
140      */
141     protected Plugin handleNoDescriptor(DeploymentUnit deploymentUnit) throws PluginParseException {
142         throw new PluginParseException("No descriptor found in classloader for : " + deploymentUnit);
143     }
144 
145     /**
146      * Creates the plugin.  Extension point for subclasses that want to create their own plugin
147      * @param parser The descriptor parser
148      * @param unit The unit to deploy
149      * @param loader The classloader for the plugin
150      * @return The created plugin
151      */
152     protected Plugin createPlugin(DescriptorParser parser, DeploymentUnit unit, PluginClassLoader loader) {
153         return pluginFactory.createPlugin(unit, loader);
154     }
155     
156     public boolean supportsRemoval()
157     {
158         return true;
159     }
160 
161     public boolean supportsAddition()
162     {
163         return true;
164     }
165 
166     /**
167      * @return all plugins, now loaded by the pluginLoader, which have been discovered and added since the
168      * last time a check was performed.
169      */
170     public Collection addFoundPlugins(ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
171     {
172         // find missing plugins
173         Collection updatedDeploymentUnits = scanner.scan();
174 
175         // create list while updating internal state
176         List foundPlugins = new ArrayList();
177         for (Iterator it = updatedDeploymentUnits.iterator(); it.hasNext();)
178         {
179             DeploymentUnit deploymentUnit = (DeploymentUnit) it.next();
180             if (!plugins.containsKey(deploymentUnit))
181             {
182                 Plugin plugin = deployPluginFromUnit(deploymentUnit, moduleDescriptorFactory);
183                 plugins.put(deploymentUnit, plugin);
184                 foundPlugins.add(plugin);
185             }
186         }
187 
188         return foundPlugins;
189     }
190 
191     /**
192      * @param plugin - the plugin to remove
193      * @throws PluginException representing the reason for failure.
194      */
195     public void removePlugin(Plugin plugin) throws PluginException
196     {
197         if (plugin.isEnabled())
198             throw new PluginException("Cannot remove an enabled plugin");
199 
200         if (!plugin.isUninstallable())
201         {
202             throw new PluginException("Cannot remove an uninstallable plugin: [" + plugin.getName() + "]" );
203         }
204 
205         DeploymentUnit deploymentUnit = findMatchingDeploymentUnit(plugin);
206         File pluginOnDisk = deploymentUnit.getPath();
207         plugin.close();
208 
209         try
210         {
211             boolean found = false;
212             for (Iterator i = plugins.keySet().iterator(); i.hasNext();)
213             {
214                 DeploymentUnit unit = (DeploymentUnit) i.next();
215                 if(unit.getPath().equals(deploymentUnit.getPath()) && !unit.equals(deploymentUnit)){
216                     found = true;
217                 }
218             }
219 
220             if (!found && !pluginOnDisk.delete())
221                 throw new PluginException("Could not delete plugin [" + plugin.getName() + "].");
222         }
223         catch (SecurityException e)
224         {
225             throw new PluginException(e);
226         }
227 
228         scanner.clear(pluginOnDisk);
229         plugins.remove(deploymentUnit);
230     }
231 
232     private DeploymentUnit findMatchingDeploymentUnit(Plugin plugin)
233             throws PluginException
234     {
235         DeploymentUnit deploymentUnit = null;
236         for (Iterator iterator = plugins.entrySet().iterator(); iterator.hasNext();)
237         {
238             Map.Entry entry = (Map.Entry) iterator.next();
239             if (entry.getValue() == plugin)
240                 deploymentUnit = (DeploymentUnit) entry.getKey();
241         }
242 
243         if (deploymentUnit == null) //the pluginLoader has no memory of deploying this plugin
244             throw new PluginException("This pluginLoader has no memory of deploying the plugin you are trying remove: [" + plugin.getName() + "]" );
245         return deploymentUnit;
246     }
247 
248     public void shutDown()
249     {
250         scanner.clearAll();
251         for (Iterator it = plugins.values().iterator(); it.hasNext();)
252         {
253             Plugin plugin  = (Plugin) it.next();
254             plugin.close();
255         }
256     }
257 
258     public String canLoad(PluginJar pluginJar) throws PluginParseException
259     {
260         String pluginKey = null;
261         final InputStream descriptorStream = pluginJar.getFile(pluginDescriptorFileName);
262         if (descriptorStream != null)
263         {
264             final DescriptorParser descriptorParser = descriptorParserFactory.getInstance(descriptorStream);
265             pluginKey = descriptorParser.getKey();
266         }
267         return pluginKey;
268     }
269 }