View Javadoc

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