View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import com.atlassian.plugin.DefaultPluginArtifactFactory;
4   import com.atlassian.plugin.ModuleDescriptorFactory;
5   import com.atlassian.plugin.Plugin;
6   import com.atlassian.plugin.PluginArtifact;
7   import com.atlassian.plugin.PluginArtifactFactory;
8   import com.atlassian.plugin.PluginException;
9   import com.atlassian.plugin.PluginParseException;
10  import com.atlassian.plugin.PluginState;
11  import com.atlassian.plugin.event.PluginEventListener;
12  import com.atlassian.plugin.event.PluginEventManager;
13  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
14  import com.atlassian.plugin.factories.PluginFactory;
15  import com.atlassian.plugin.impl.UnloadablePlugin;
16  import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
17  import com.atlassian.plugin.loaders.classloading.Scanner;
18  
19  import org.apache.commons.lang.Validate;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.TreeMap;
29  
30  /**
31   * Plugin loader that delegates the detection of plugins to a Scanner instance. The scanner may monitor the contents
32   * of a directory on disk, a database, or any other place plugins may be hidden.
33   *
34   * @since 2.1.0
35   */
36  public class ScanningPluginLoader implements DynamicPluginLoader
37  {
38      private static Log log = LogFactory.getLog(DirectoryPluginLoader.class);
39      protected final com.atlassian.plugin.loaders.classloading.Scanner scanner;
40      protected final Map<DeploymentUnit, Plugin> plugins;
41      protected final List<PluginFactory> pluginFactories;
42      protected final PluginArtifactFactory pluginArtifactFactory;
43  
44      /**
45       * Constructor that provides a default plugin artifact factory
46       *
47       * @param scanner The scanner to use to detect new plugins
48       * @param pluginFactories The deployers that will handle turning an artifact into a plugin
49       * @param pluginEventManager The event manager, used for listening for shutdown events
50       * @since 2.0.0
51       */
52      public ScanningPluginLoader(final Scanner scanner, final List<PluginFactory> pluginFactories, final PluginEventManager pluginEventManager)
53      {
54          this(scanner, pluginFactories, new DefaultPluginArtifactFactory(), pluginEventManager);
55      }
56  
57      /**
58       * Construct a new scanning plugin loader with no default values
59       *
60       * @param scanner The scanner to use to detect new plugins
61       * @param pluginFactories The deployers that will handle turning an artifact into a plugin
62       * @param pluginArtifactFactory used to create new plugin artifacts from an URL
63       * @param pluginEventManager The event manager, used for listening for shutdown events
64       * @since 2.0.0
65       */
66      public ScanningPluginLoader(final Scanner scanner, final List<PluginFactory> pluginFactories, final PluginArtifactFactory pluginArtifactFactory, final PluginEventManager pluginEventManager)
67      {
68          Validate.notNull(pluginFactories, "The list of plugin factories must be specified");
69          Validate.notNull(pluginEventManager, "The event manager must be specified");
70          Validate.notNull(scanner, "The scanner must be specified");
71  
72          plugins = new TreeMap<DeploymentUnit, Plugin>();
73  
74          this.pluginArtifactFactory = pluginArtifactFactory;
75          this.scanner = scanner;
76          this.pluginFactories = new ArrayList<PluginFactory>(pluginFactories);
77  
78          pluginEventManager.register(this);
79      }
80  
81      public Collection<Plugin> loadAllPlugins(final ModuleDescriptorFactory moduleDescriptorFactory)
82      {
83          scanner.scan();
84  
85          for (final DeploymentUnit deploymentUnit : scanner.getDeploymentUnits())
86          {
87              Plugin plugin = deployPluginFromUnit(deploymentUnit, moduleDescriptorFactory);
88              plugin = postProcess(plugin);
89              plugins.put(deploymentUnit, plugin);
90          }
91  
92          if (scanner.getDeploymentUnits().isEmpty())
93          {
94              log.info("No plugins found to be deployed");
95          }
96  
97          return plugins.values();
98      }
99  
100     protected Plugin deployPluginFromUnit(final DeploymentUnit deploymentUnit, final ModuleDescriptorFactory moduleDescriptorFactory)
101     {
102         Plugin plugin = null;
103         String errorText = "No plugin factories found for plugin file " + deploymentUnit;
104 
105         String pluginKey = null;
106         for (final PluginFactory factory : pluginFactories)
107         {
108             try
109             {
110                 final PluginArtifact artifact = pluginArtifactFactory.create(deploymentUnit.getPath().toURI());
111                 pluginKey = factory.canCreate(artifact);
112                 if (pluginKey != null)
113                 {
114                     plugin = factory.create(artifact, moduleDescriptorFactory);
115                     if (plugin != null)
116                     {
117                         break;
118                     }
119                 }
120             }
121             catch (final RuntimeException ex)
122             {
123                 log.error("Unable to deploy plugin '" + pluginKey + "', file " + deploymentUnit, ex);
124                 errorText = ex.getMessage();
125                 break;
126             }
127         }
128         if (plugin == null)
129         {
130             plugin = new UnloadablePlugin(errorText);
131             if (pluginKey != null)
132             {
133                 plugin.setKey(pluginKey);
134             }
135             else
136             {
137                 plugin.setKey(deploymentUnit.getPath().getName());
138             }
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.uninstall();
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.uninstall();
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 
304     /**
305      * Template method that can be used by a specific {@link PluginLoader} to
306      * add information to a {@link Plugin} after it has been loaded.
307      * 
308      * @param plugin a plugin that has been loaded
309      * @since v2.2.0
310      */
311     protected Plugin postProcess(final Plugin plugin)
312     {
313         return plugin;
314     }
315 }