View Javadoc

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