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