View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import com.atlassian.plugin.*;
4   import com.atlassian.plugin.factories.PluginFactory;
5   import com.atlassian.plugin.impl.UnloadablePlugin;
6   import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
7   import com.atlassian.plugin.event.PluginEventManager;
8   import com.atlassian.plugin.event.PluginEventListener;
9   import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
10  import com.atlassian.plugin.loaders.classloading.Scanner;
11  import org.apache.commons.logging.Log;
12  import org.apache.commons.logging.LogFactory;
13  import org.apache.commons.lang.Validate;
14  
15  import java.io.File;
16  import java.util.*;
17  import java.net.MalformedURLException;
18  
19  /**
20   * A plugin loader to load plugins from a directory on disk.  A {@link Scanner} is used to locate plugin artifacts
21   * and determine if they need to be redeployed or not.
22   */
23  public class DirectoryPluginLoader implements DynamicPluginLoader
24  {
25      private static Log log = LogFactory.getLog(DirectoryPluginLoader.class);
26      private final Scanner scanner;
27      private final Map<DeploymentUnit,Plugin> plugins;
28      private final List<PluginFactory> pluginFactories;
29      private final PluginArtifactFactory pluginArtifactFactory;
30  
31      /**
32       * Constructs a loader for a particular directory and set of deployers
33       * @param path The directory containing the plugins
34       * @param pluginFactories The deployers that will handle turning an artifact into a plugin
35       * @param pluginEventManager The event manager, used for listening for shutdown events
36       * @since 2.0.0
37       */
38      public DirectoryPluginLoader(File path, List<PluginFactory> pluginFactories,
39                                   PluginEventManager pluginEventManager)
40      {
41          this(path, pluginFactories, new DefaultPluginArtifactFactory(), pluginEventManager);
42      }
43  
44      /**
45       * Constructs a loader for a particular directory and set of deployers
46       * @param path The directory containing the plugins
47       * @param pluginFactories The deployers that will handle turning an artifact into a plugin
48       * @param pluginArtifactFactory The plugin artifact factory
49       * @param pluginEventManager The event manager, used for listening for shutdown events
50       * @since 2.1.0
51       */
52      public DirectoryPluginLoader(File path, List<PluginFactory> pluginFactories, PluginArtifactFactory pluginArtifactFactory,
53                                   PluginEventManager pluginEventManager)
54      {
55          if (log.isDebugEnabled())
56              log.debug("Creating plugin loader for url " + path);
57  
58          Validate.notNull(path, "The directory file must be specified");
59          Validate.notNull(pluginFactories, "The list of plugin factories must be specified");
60          Validate.notNull(pluginEventManager, "The event manager must be specified");
61  
62          scanner = new Scanner(path);
63          plugins = new HashMap<DeploymentUnit,Plugin>();
64          this.pluginFactories = new ArrayList<PluginFactory>(pluginFactories);
65          this.pluginArtifactFactory = pluginArtifactFactory;
66          pluginEventManager.register(this);
67      }
68  
69      public Collection<Plugin> loadAllPlugins(ModuleDescriptorFactory moduleDescriptorFactory)
70      {
71          scanner.scan();
72  
73          for (DeploymentUnit deploymentUnit : scanner.getDeploymentUnits())
74          {
75              try
76              {
77                  Plugin plugin = deployPluginFromUnit(deploymentUnit, moduleDescriptorFactory);
78                  plugins.put(deploymentUnit, plugin);
79              }
80              catch (PluginParseException e)
81              {
82                  // This catches errors so that the successfully loaded plugins can be returned.
83                  // It might be nicer if this method returned an object containing both the succesfully loaded
84                  // plugins and the unsuccessfully loaded plugins.
85                  log.error("Error loading descriptor for : " + deploymentUnit, e);
86              }
87          }
88  
89          if (scanner.getDeploymentUnits().size() == 0)
90              log.info("No plugins found to be deployed");
91  
92          return plugins.values();
93      }
94  
95  
96      protected Plugin deployPluginFromUnit(DeploymentUnit deploymentUnit, ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
97      {
98          Plugin plugin = null;
99          String errorText = "No plugin factories found for plugin file "+deploymentUnit;
100 
101         for (PluginFactory factory : pluginFactories)
102         {
103             try
104             {
105                 PluginArtifact artifact = pluginArtifactFactory.create(deploymentUnit.getPath().toURL());
106                 if (factory.canCreate(artifact) != null)
107                 {
108                     plugin = factory.create(deploymentUnit, moduleDescriptorFactory);
109                     if (plugin != null)
110                         break;
111                 }
112 
113             } catch (MalformedURLException e)
114             {
115                 // Should never happen
116                 throw new RuntimeException(e);
117             } catch (IllegalArgumentException ex)
118             {
119                 errorText = ex.getMessage();
120             }
121         }
122         if (plugin == null)
123             plugin = new UnloadablePlugin(errorText);
124         else
125             log.info("Plugin " + deploymentUnit + " created");
126 
127         return plugin;
128     }
129 
130     public boolean supportsRemoval()
131     {
132         return true;
133     }
134 
135     public boolean supportsAddition()
136     {
137         return true;
138     }
139 
140     /**
141      * @return all plugins, now loaded by the pluginLoader, which have been discovered and added since the
142      * last time a check was performed.
143      */
144     public Collection<Plugin> addFoundPlugins(ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
145     {
146         // find missing plugins
147         Collection<DeploymentUnit> updatedDeploymentUnits = scanner.scan();
148 
149         // create list while updating internal state
150         List<Plugin> foundPlugins = new ArrayList<Plugin>();
151         for (DeploymentUnit deploymentUnit : updatedDeploymentUnits)
152         {
153             if (!plugins.containsKey(deploymentUnit))
154             {
155                 Plugin plugin = deployPluginFromUnit(deploymentUnit, moduleDescriptorFactory);
156                 plugins.put(deploymentUnit, plugin);
157                 foundPlugins.add(plugin);
158             }
159         }
160         if (foundPlugins.size() == 0)
161             log.info("No plugins found to be installed");
162 
163         return foundPlugins;
164     }
165 
166     /**
167      * @param plugin - the plugin to remove
168      * @throws PluginException representing the reason for failure.
169      */
170     public void removePlugin(Plugin plugin) throws PluginException
171     {
172         if (plugin.isEnabled())
173             throw new PluginException("Cannot remove an enabled plugin");
174 
175         if (!plugin.isUninstallable())
176         {
177             throw new PluginException("Cannot remove an uninstallable plugin: [" + plugin.getName() + "]" );
178         }
179 
180         DeploymentUnit deploymentUnit = findMatchingDeploymentUnit(plugin);
181         File pluginOnDisk = deploymentUnit.getPath();
182         plugin.close();
183 
184         try
185         {
186             boolean found = false;
187             for (DeploymentUnit unit : plugins.keySet())
188             {
189                 if(unit.getPath().equals(deploymentUnit.getPath()) && !unit.equals(deploymentUnit))
190                 {
191                     found = true;
192                     break;
193                 }
194             }
195 
196             if (!found && !pluginOnDisk.delete())
197                 throw new PluginException("Could not delete plugin [" + plugin.getName() + "].");
198         }
199         catch (SecurityException e)
200         {
201             throw new PluginException(e);
202         }
203 
204         scanner.clear(pluginOnDisk);
205         plugins.remove(deploymentUnit);
206         log.info("Removed plugin " + plugin.getKey());
207     }
208 
209     private DeploymentUnit findMatchingDeploymentUnit(Plugin plugin)
210             throws PluginException
211     {
212         DeploymentUnit deploymentUnit = null;
213         for (Iterator<Map.Entry<DeploymentUnit,Plugin>> iterator = plugins.entrySet().iterator(); iterator.hasNext();)
214         {
215             Map.Entry<DeploymentUnit,Plugin> entry = iterator.next();
216             // no, you don't want to use entry.getValue().equals(plugin) here as it breaks upgrades where it is a new
217             // version of the plugin but the key and version number hasn't changed, and hence, equals() will always return
218             // true
219             if (entry.getValue() == plugin)
220             {
221                 deploymentUnit = entry.getKey();
222                 break;
223             }
224         }
225 
226         if (deploymentUnit == null) //the pluginLoader has no memory of deploying this plugin
227             throw new PluginException("This pluginLoader has no memory of deploying the plugin you are trying remove: [" + plugin.getName() + "]" );
228         return deploymentUnit;
229     }
230 
231     /**
232      * Called during plugin framework shutdown
233      * @param event The shutdown event
234      */
235     @PluginEventListener
236     public void onShutdown(PluginFrameworkShutdownEvent event)
237     {
238         scanner.clearAll();
239         for (Iterator<Plugin> it = plugins.values().iterator(); it.hasNext();)
240         {
241             Plugin plugin  = it.next();
242             plugin.close();
243             it.remove();
244         }
245     }
246 
247     /**
248      * @deprecated Since 2.0.0, shutdown will automatically occur when the plugin framework is shutdown
249      */
250     public void shutDown()
251     {
252         onShutdown(null);
253     }
254 
255     /**
256      * Determines if the artifact can be loaded by any of its deployers
257      *
258      * @param pluginArtifact The artifact to test
259      * @return True if this artifact can be loaded by this loader
260      * @throws PluginParseException
261      */
262     public String canLoad(PluginArtifact pluginArtifact) throws PluginParseException
263     {
264         String pluginKey = null;
265         for (PluginFactory factory : pluginFactories)
266         {
267             pluginKey = factory.canCreate(pluginArtifact);
268             if (pluginKey != null)
269                 break;
270         }
271         return pluginKey;
272     }
273 }