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.ForwardingScanner;
14  import com.atlassian.plugin.loaders.classloading.Scanner;
15  import com.atlassian.plugin.module.ContainerAccessor;
16  import com.atlassian.plugin.module.ContainerManagedPlugin;
17  import org.apache.commons.io.FileUtils;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.net.URL;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  /**
26   * Plugin loader that can find plugins via a single URL, and treats all plugins loaded from
27   * the directory as bundled plugins, meaning they can can be upgraded, but not deleted.
28   * <p>
29   * Depending on the URL:
30   * <ul>
31   *     <li>If it is a file:// url and represents a directory, all the files in that directory are scanned.</li>
32   *     <li>if it is a file:// url and represents a file with a <code>.list</code> suffix, each line in that files
33   *     is read as a path to a plugin jar.</li>
34   *     <li>Otherwise it assumes the URL is a zip and unzips plugins from it into a local directory,
35   *     and ensures that directory only contains plugins from that zip file.  It also</li>
36   * </ul>
37   *
38   */
39  public class BundledPluginLoader extends ScanningPluginLoader
40  {
41      public BundledPluginLoader(final URL zipUrl, final File pluginPath, final List<PluginFactory> pluginFactories, final PluginEventManager eventManager)
42      {
43          super(buildScanner(zipUrl, pluginPath), pluginFactories, new DefaultPluginArtifactFactory(true), eventManager);
44      }
45  
46      @Override
47      protected Plugin postProcess(final Plugin plugin)
48      {
49          if (plugin instanceof ContainerManagedPlugin)
50          {
51              return new BundledPluginContainerManagedPluginDelegate((ContainerManagedPlugin) plugin);
52          }
53          else if (plugin instanceof PluginArtifactBackedPlugin)
54          {
55              return new BundledPluginArtifactBackedPluginDelegate((PluginArtifactBackedPlugin)plugin);
56          }
57          return new BundledPluginDelegate(plugin);
58      }
59  
60      private static Scanner buildScanner(final URL url, final File pluginPath)
61      {
62          if (url == null)
63          {
64              throw new IllegalArgumentException("Bundled plugins url cannot be null");
65          }
66  
67          Scanner scanner = null;
68  
69          final File file = FileUtils.toFile(url);
70  
71          if (file != null)
72          {
73              if (file.isDirectory())
74              {
75                  // URL points directly to a directory of jars
76                  scanner = new DirectoryScanner(file);
77              }
78              else if (file.isFile() && file.getName().endsWith(".list"))
79              {
80                  // URL points to a file containg a list of jars
81                  final List<File> files = readListFile(file);
82                  scanner = new FileListScanner(files);
83              }
84          }
85  
86          if (scanner == null)
87          {
88              // default: assume it is a zip
89              com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(url, pluginPath);
90              scanner = new DirectoryScanner(pluginPath);
91          }
92  
93          return new ForwardingScanner(scanner)
94          {
95              @Override
96              public void remove(DeploymentUnit unit) throws PluginException
97              {
98                  // We suppress the call to remove, because BundledPlugins should never actually be
99                  // removed from the scanner.
100             }
101         };
102     }
103 
104     private static List<File> readListFile(final File file)
105     {
106         try
107         {
108             final List<String> fnames = (List<String>) FileUtils.readLines(file);
109             final List<File> files = new ArrayList<File>();
110             for (String fname : fnames)
111             {
112                 files.add(new File(fname));
113             }
114             return files;
115         }
116         catch (IOException e)
117         {
118             throw new IllegalStateException("Unable to read list from " + file, e);
119         }
120     }
121 
122 
123     /**
124      * Delegate that overrides methods to enforce bundled plugin behavior.
125      *
126      * This wrapper does not override isDeletable(), but passes it through to the base class and
127      * hence delegate. We need OsgiPlugin instances to be isDeletable or they won't be uninstalled
128      * by DefaultPluginManager, which will leak the bundle, classloaders, and all sorts. We stop the
129      * actual deletion on disk via the Scanner wrapper in buildScanner. We should probably revisit
130      * isDeletable vs isUninstallable usage in DefaultPluginManager, but i'm looking for minimal
131      * changes to address PLUGDEV-10.
132      *
133      * @since 2.2.0
134      */
135     private static class BundledPluginDelegate extends AbstractDelegatingPlugin
136     {
137 
138         public BundledPluginDelegate(Plugin delegate)
139         {
140             super(delegate);
141         }
142 
143         @Override
144         public boolean isBundledPlugin()
145         {
146             return true;
147         }
148 
149         // See class documentation for why we don't override isDeletable().
150     }
151 
152     /**
153      * Delegate that overrides methods to enforce bundled plugin behavior for {@link PluginArtifactBackedPlugin} implementors
154      * @since 2.9.3
155      */
156     private static class BundledPluginArtifactBackedPluginDelegate extends BundledPluginDelegate implements PluginArtifactBackedPlugin
157     {
158         private final PluginArtifactBackedPlugin delegate;
159 
160         private BundledPluginArtifactBackedPluginDelegate(final PluginArtifactBackedPlugin delegate)
161         {
162             super(delegate);
163             this.delegate = delegate;
164         }
165 
166         public PluginArtifact getPluginArtifact()
167         {
168             return delegate.getPluginArtifact();
169         }
170     }
171 
172     private static class BundledPluginContainerManagedPluginDelegate extends BundledPluginArtifactBackedPluginDelegate implements ContainerManagedPlugin,
173             AutowireCapablePlugin
174     {
175         private final ContainerManagedPlugin delegate;
176 
177         private BundledPluginContainerManagedPluginDelegate(ContainerManagedPlugin delegate)
178         {
179             super(delegate);
180             this.delegate = delegate;
181         }
182 
183         @Override
184         public ContainerAccessor getContainerAccessor()
185         {
186             return delegate.getContainerAccessor();
187         }
188 
189         @Override
190         public <T> T autowire(Class<T> clazz)
191         {
192             if (delegate instanceof AutowireCapablePlugin)
193             {
194                 return ((AutowireCapablePlugin)delegate).autowire(clazz);
195             }
196             else
197             {
198                 return delegate.getContainerAccessor().createBean(clazz);
199             }
200         }
201 
202         @Override
203         public <T> T autowire(Class<T> clazz, AutowireStrategy autowireStrategy)
204         {
205             if (delegate instanceof AutowireCapablePlugin)
206             {
207                 return ((AutowireCapablePlugin)delegate).autowire(clazz, autowireStrategy);
208             }
209             else
210             {
211                 return delegate.getContainerAccessor().createBean(clazz);
212             }
213         }
214 
215         @Override
216         public void autowire(Object instance)
217         {
218             if (delegate instanceof AutowireCapablePlugin)
219             {
220                 ((AutowireCapablePlugin)delegate).autowire(instance);
221             }
222             else
223             {
224                 delegate.getContainerAccessor().injectBean(instance);
225             }
226         }
227 
228         @Override
229         public void autowire(Object instance, AutowireStrategy autowireStrategy)
230         {
231             if (delegate instanceof AutowireCapablePlugin)
232             {
233                 ((AutowireCapablePlugin)delegate).autowire(instance, autowireStrategy);
234             }
235             else
236             {
237                 delegate.getContainerAccessor().injectBean(instance);
238             }
239         }
240     }
241 }