View Javadoc

1   package com.atlassian.plugin.loaders;
2   
3   import com.atlassian.plugin.AutowireCapablePlugin;
4   import com.atlassian.plugin.DefaultPluginArtifactFactory;
5   import com.atlassian.plugin.Plugin;
6   import com.atlassian.plugin.PluginArtifact;
7   import com.atlassian.plugin.PluginArtifactBackedPlugin;
8   import com.atlassian.plugin.PluginException;
9   import com.atlassian.plugin.event.PluginEventManager;
10  import com.atlassian.plugin.factories.PluginFactory;
11  import com.atlassian.plugin.impl.AbstractDelegatingPlugin;
12  import com.atlassian.plugin.loaders.classloading.DeploymentUnit;
13  import com.atlassian.plugin.loaders.classloading.EmptyScanner;
14  import com.atlassian.plugin.loaders.classloading.ForwardingScanner;
15  import com.atlassian.plugin.loaders.classloading.Scanner;
16  import com.atlassian.plugin.module.ContainerAccessor;
17  import com.atlassian.plugin.module.ContainerManagedPlugin;
18  import org.apache.commons.io.FileUtils;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import static com.atlassian.plugin.PluginArtifact.AllowsReference.ReferenceMode.PERMIT_REFERENCE;
29  import static com.google.common.base.Preconditions.checkArgument;
30  import static com.google.common.base.Preconditions.checkNotNull;
31  
32  /**
33   * A Plugin loader that manages a set of bundled plugins, meaning that they can can be upgraded, but
34   * not deleted.
35   * <p>
36   * This loader can source plugins from:
37   * <p>
38   * <ul>
39   *     <li>A directory containing Plugin artifacts.
40   *     <li>A file with a {@link #getListSuffix()} suffix, where each line of that
41   *         file gives the path to a Plugin artifact.
42   *     <li>A URL identifying a zip file, and a path to explode the zip into, and the exploded
43   *         contents of the zip are used as the plugin artifacts.
44   * </ul>
45   */
46  public class BundledPluginLoader extends ScanningPluginLoader
47  {
48      private static final Logger log = LoggerFactory.getLogger(BundledPluginLoader.class);
49  
50      /** The suffix used for bundled plugin list files. */
51      public static String getListSuffix()
52      {
53          return ".list";
54      }
55  
56      private BundledPluginLoader(
57              final Scanner scanner,
58              final List<PluginFactory> pluginFactories,
59              final PluginEventManager eventManager)
60      {
61          super(new NonRemovingScanner(scanner), pluginFactories, new DefaultPluginArtifactFactory(PERMIT_REFERENCE),
62                  eventManager);
63      }
64  
65      /**
66       * Construct a bundled plugin loader for a directory or list file source.
67       *
68       * @param source a directory of containing plugin artifacts, or a file with suffix
69       * {@link #getListSuffix()} containing a list of paths to plugin artifacts.
70       * @param pluginFactories as per {@link ScanningPluginLoader}.
71       * @param eventManager as per {@link ScanningPluginLoader}.
72       * @since 3.0.16
73       */
74      public BundledPluginLoader(
75              final File source,
76              final List<PluginFactory> pluginFactories,
77              final PluginEventManager eventManager)
78      {
79          this(buildSourceScanner(source), pluginFactories, eventManager);
80      }
81  
82      /**
83       * Construct a bundled plugin loader for a zip source.
84       * <p>
85       * For backwards compatibility, if the zipUrl is in fact a file url (acccording to
86       * {@link FileUtils#toFile}), this constructor has the same semantics as
87       * {@link #BundledPluginLoader(File, List, PluginEventManager)} where the first argument
88       * is the file referred to by zipUrl, and the pluginPath argument is ignored. That
89       * constructor should be used directly in such cases.
90       * <p>
91       * @param zipUrl a url to a zipFile containing plugin artifacts, or a file url to a directory or
92       * list file.
93       * @param pluginPath path to the directory to expand zipUrl.
94       * @param pluginFactories as per {@link ScanningPluginLoader}.
95       * @param eventManager as per {@link ScanningPluginLoader}.
96       */
97      public BundledPluginLoader(
98              final URL zipUrl,
99              final File pluginPath,
100             final List<PluginFactory> pluginFactories,
101             final PluginEventManager eventManager)
102     {
103         this(buildZipScanner(zipUrl, pluginPath), pluginFactories, eventManager);
104     }
105 
106     @Override
107     protected Plugin postProcess(final Plugin plugin)
108     {
109         if (plugin instanceof ContainerManagedPlugin)
110         {
111             return new BundledPluginContainerManagedPluginDelegate((ContainerManagedPlugin) plugin);
112         }
113         else if (plugin instanceof PluginArtifactBackedPlugin)
114         {
115             return new BundledPluginArtifactBackedPluginDelegate((PluginArtifactBackedPlugin)plugin);
116         }
117         return new BundledPluginDelegate(plugin);
118     }
119 
120     private static Scanner buildScannerCommon(final File file)
121     {
122         if (file.isDirectory())
123         {
124             // file points directly to a directory of jars
125             return new DirectoryScanner(file);
126         }
127         else if (file.isFile() && file.getName().endsWith(getListSuffix()))
128         {
129             // file contains a list of jars.
130             final List<File> files = readListFile(file);
131             return new FileListScanner(files);
132         }
133         else
134         {
135             // unknown - let caller figure out fallback
136             return null;
137         }
138     }
139 
140     private static Scanner buildSourceScanner(final File source)
141     {
142         checkNotNull(source, "Source must not be null");
143         final Scanner scanner = buildScannerCommon(source);
144         if (null == scanner)
145         {
146             log.error("Cannot build a scanner for source '{}'", source);
147             // This is approximately what the code used to do - produce a scanner which would have
148             // no entries (unless it accidentally had a random collection from a prior boot).
149             return new EmptyScanner();
150         }
151         else
152         {
153             return scanner;
154         }
155     }
156 
157     private static Scanner buildZipScanner(final URL url, final File pluginPath)
158     {
159         // checkArgument used to preserve historical behaviour of throwing IllegalArgumentException
160         checkArgument(null != url, "Bundled plugins url cannot be null");
161 
162 
163         Scanner scanner = null;
164 
165         // Legacy behaviour - treat file:// urls as per buildSourceScanner, but we don't
166         // want the error or the empty scanner.
167         final File file = FileUtils.toFile(url);
168         if (null != file)
169         {
170             scanner = buildScannerCommon(file);
171         }
172 
173         if (null == scanner)
174         {
175             // Not handled by file:// urls, so it's a zip url
176             com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(url, pluginPath);
177             scanner = new DirectoryScanner(pluginPath);
178         }
179 
180         return scanner;
181     }
182 
183     private static List<File> readListFile(final File file)
184     {
185         try
186         {
187             final List<String> fnames = (List<String>) FileUtils.readLines(file);
188             final List<File> files = new ArrayList<File>();
189             for (final String fname : fnames)
190             {
191                 files.add(new File(fname));
192             }
193             return files;
194         }
195         catch (IOException e)
196         {
197             throw new IllegalStateException("Unable to read list from " + file, e);
198         }
199     }
200 
201     /**
202      * A forwarding scanner which suppresses remove operation.
203      * <p>
204      * Bundled plugins are never actually removed from the system, so we wrap the scanner to
205      * suppress removal.
206      */
207     private static class NonRemovingScanner extends ForwardingScanner
208     {
209         NonRemovingScanner(final Scanner scanner)
210         {
211             super(scanner);
212         }
213 
214         @Override
215         public void remove(final DeploymentUnit unit) throws PluginException
216         {
217             // Suppressed - see class documentation.
218         }
219     }
220 
221     /**
222      * Delegate that overrides methods to enforce bundled plugin behavior.
223      *
224      * This wrapper does not override {@link #isDeleteable()}, but passes it through to the base class and
225      * hence delegate. We need OsgiPlugin instances to be isDeleteable or they won't be uninstalled
226      * by DefaultPluginManager, which will leak the bundle, classloaders, and all sorts. We stop the
227      * actual deletion on disk via the Scanner wrapper in buildScanner. We should probably revisit
228      * isDeleteable vs isUninstallable usage in DefaultPluginManager, but i'm looking for minimal
229      * changes to address PLUGDEV-10.
230      *
231      * @since 2.2.0
232      */
233     private static class BundledPluginDelegate extends AbstractDelegatingPlugin
234     {
235 
236         public BundledPluginDelegate(final Plugin delegate)
237         {
238             super(delegate);
239         }
240 
241         @Override
242         public boolean isBundledPlugin()
243         {
244             return true;
245         }
246 
247         // See class documentation for why we don't override isDeletable().
248     }
249 
250     /**
251      * Delegate that overrides methods to enforce bundled plugin behavior for {@link PluginArtifactBackedPlugin} implementors
252      * @since 2.9.3
253      */
254     private static class BundledPluginArtifactBackedPluginDelegate extends BundledPluginDelegate implements PluginArtifactBackedPlugin
255     {
256         private final PluginArtifactBackedPlugin delegate;
257 
258         private BundledPluginArtifactBackedPluginDelegate(final PluginArtifactBackedPlugin delegate)
259         {
260             super(delegate);
261             this.delegate = delegate;
262         }
263 
264         public PluginArtifact getPluginArtifact()
265         {
266             return delegate.getPluginArtifact();
267         }
268     }
269 
270     private static class BundledPluginContainerManagedPluginDelegate extends BundledPluginArtifactBackedPluginDelegate implements ContainerManagedPlugin,
271             AutowireCapablePlugin
272     {
273         private final ContainerManagedPlugin delegate;
274 
275         private BundledPluginContainerManagedPluginDelegate(final ContainerManagedPlugin delegate)
276         {
277             super(delegate);
278             this.delegate = delegate;
279         }
280 
281         @Override
282         public ContainerAccessor getContainerAccessor()
283         {
284             return delegate.getContainerAccessor();
285         }
286 
287         @Override
288         public <T> T autowire(final Class<T> clazz)
289         {
290             if (delegate instanceof AutowireCapablePlugin)
291             {
292                 return ((AutowireCapablePlugin)delegate).autowire(clazz);
293             }
294             else
295             {
296                 return delegate.getContainerAccessor().createBean(clazz);
297             }
298         }
299 
300         @Override
301         public <T> T autowire(final Class<T> clazz, final AutowireStrategy autowireStrategy)
302         {
303             if (delegate instanceof AutowireCapablePlugin)
304             {
305                 return ((AutowireCapablePlugin)delegate).autowire(clazz, autowireStrategy);
306             }
307             else
308             {
309                 return delegate.getContainerAccessor().createBean(clazz);
310             }
311         }
312 
313         @Override
314         public void autowire(final Object instance)
315         {
316             if (delegate instanceof AutowireCapablePlugin)
317             {
318                 ((AutowireCapablePlugin)delegate).autowire(instance);
319             }
320             else
321             {
322                 delegate.getContainerAccessor().injectBean(instance);
323             }
324         }
325 
326         @Override
327         public void autowire(final Object instance, final AutowireStrategy autowireStrategy)
328         {
329             if (delegate instanceof AutowireCapablePlugin)
330             {
331                 ((AutowireCapablePlugin)delegate).autowire(instance, autowireStrategy);
332             }
333             else
334             {
335                 delegate.getContainerAccessor().injectBean(instance);
336             }
337         }
338     }
339 }