View Javadoc

1   package com.atlassian.plugin.impl;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Collections;
6   import java.util.Date;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.Set;
10  import java.util.concurrent.atomic.AtomicReference;
11  
12  import com.atlassian.plugin.InstallationMode;
13  import com.atlassian.plugin.PluginPermission;
14  import com.google.common.base.Function;
15  import com.google.common.base.Predicate;
16  import com.google.common.collect.ImmutableSet;
17  import com.google.common.collect.Iterables;
18  import org.apache.commons.lang.StringUtils;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  import com.atlassian.plugin.ModuleDescriptor;
23  import com.atlassian.plugin.Plugin;
24  import com.atlassian.plugin.PluginException;
25  import com.atlassian.plugin.PluginInformation;
26  import com.atlassian.plugin.PluginState;
27  import com.atlassian.plugin.Resourced;
28  import com.atlassian.plugin.Resources;
29  import com.atlassian.plugin.elements.ResourceDescriptor;
30  import com.atlassian.plugin.elements.ResourceLocation;
31  import com.atlassian.plugin.util.VersionStringComparator;
32  import com.atlassian.util.concurrent.CopyOnWriteMap;
33  
34  public abstract class AbstractPlugin implements Plugin, Comparable<Plugin>
35  {
36      private final Map<String, ModuleDescriptor<?>> modules = CopyOnWriteMap.<String, ModuleDescriptor<?>> builder().stableViews().newLinkedMap();
37      private String name;
38      private String i18nNameKey;
39      private String key;
40      private boolean enabledByDefault = true;
41      private PluginInformation pluginInformation = new PluginInformation();
42      private boolean system;
43      private Resourced resources = Resources.EMPTY_RESOURCES;
44      private int pluginsVersion = 1;
45      private final Date dateLoaded = new Date();
46      private final AtomicReference<PluginState> pluginState = new AtomicReference<PluginState>(PluginState.UNINSTALLED);
47  
48      private final Logger log = LoggerFactory.getLogger(this.getClass());
49  
50      public String getName()
51      {
52          return !StringUtils.isBlank(name) ? name : !StringUtils.isBlank(i18nNameKey) ? "" : getKey();
53      }
54  
55      public void setName(final String name)
56      {
57          this.name = name;
58      }
59  
60      /**
61       * @return the logger used internally
62       */
63      protected Logger getLog()
64      {
65          return log;
66      }
67  
68      public String getI18nNameKey()
69      {
70          return i18nNameKey;
71      }
72  
73      public void setI18nNameKey(final String i18nNameKey)
74      {
75          this.i18nNameKey = i18nNameKey;
76      }
77  
78      public String getKey()
79      {
80          return key;
81      }
82  
83      public void setKey(final String aPackage)
84      {
85          key = aPackage;
86      }
87  
88      public void addModuleDescriptor(final ModuleDescriptor<?> moduleDescriptor)
89      {
90          modules.put(moduleDescriptor.getKey(), moduleDescriptor);
91      }
92  
93      protected void removeModuleDescriptor(final String key)
94      {
95          modules.remove(key);
96      }
97  
98      /**
99       * Returns the module descriptors for this plugin
100      * 
101      * @return An unmodifiable list of the module descriptors.
102      */
103     public Collection<ModuleDescriptor<?>> getModuleDescriptors()
104     {
105         return modules.values();
106     }
107 
108     public ModuleDescriptor<?> getModuleDescriptor(final String key)
109     {
110         return modules.get(key);
111     }
112 
113     public <T> List<ModuleDescriptor<T>> getModuleDescriptorsByModuleClass(final Class<T> aClass)
114     {
115         final List<ModuleDescriptor<T>> result = new ArrayList<ModuleDescriptor<T>>();
116         for (final ModuleDescriptor<?> moduleDescriptor : modules.values())
117         {
118             final Class<?> moduleClass = moduleDescriptor.getModuleClass();
119             if (moduleClass != null && aClass.isAssignableFrom(moduleClass))
120             {
121                 @SuppressWarnings("unchecked")
122                 final ModuleDescriptor<T> typedModuleDescriptor = (ModuleDescriptor<T>) moduleDescriptor;
123                 result.add(typedModuleDescriptor);
124             }
125         }
126         return result;
127     }
128 
129     public PluginState getPluginState()
130     {
131         return pluginState.get();
132     }
133 
134     protected void setPluginState(final PluginState state)
135     {
136         if (log.isDebugEnabled())
137         {
138             log.debug("Plugin " + getKey() + " going from " + getPluginState() + " to " + state);
139         }
140         pluginState.set(state);
141     }
142 
143     /**
144      * Only sets the plugin state if it is in the expected state.
145      * 
146      * @param requiredExistingState The expected state
147      * @param desiredState The desired state
148      * @return True if the set was successful, false if not in the expected
149      *         state
150      * @since 2.4
151      */
152     protected boolean compareAndSetPluginState(final PluginState requiredExistingState, final PluginState desiredState)
153     {
154         if (log.isDebugEnabled())
155         {
156             log.debug("Plugin " + getKey() + " trying to go from " + getPluginState() + " to " + desiredState + " but only if in " + requiredExistingState);
157         }
158         return pluginState.compareAndSet(requiredExistingState, desiredState);
159     }
160 
161     public boolean isEnabledByDefault()
162     {
163         return enabledByDefault && ((pluginInformation == null) || pluginInformation.satisfiesMinJavaVersion());
164     }
165 
166     public void setEnabledByDefault(final boolean enabledByDefault)
167     {
168         this.enabledByDefault = enabledByDefault;
169     }
170 
171     public int getPluginsVersion()
172     {
173         return pluginsVersion;
174     }
175 
176     public void setPluginsVersion(final int pluginsVersion)
177     {
178         this.pluginsVersion = pluginsVersion;
179     }
180 
181     public PluginInformation getPluginInformation()
182     {
183         return pluginInformation;
184     }
185 
186     public void setPluginInformation(final PluginInformation pluginInformation)
187     {
188         this.pluginInformation = pluginInformation;
189     }
190 
191     public void setResources(final Resourced resources)
192     {
193         this.resources = resources != null ? resources : Resources.EMPTY_RESOURCES;
194     }
195 
196     public List<ResourceDescriptor> getResourceDescriptors()
197     {
198         return resources.getResourceDescriptors();
199     }
200 
201     public List<ResourceDescriptor> getResourceDescriptors(final String type)
202     {
203         return resources.getResourceDescriptors(type);
204     }
205 
206     public ResourceLocation getResourceLocation(final String type, final String name)
207     {
208         return resources.getResourceLocation(type, name);
209     }
210 
211     /**
212      * @deprecated
213      */
214     @Deprecated
215     public ResourceDescriptor getResourceDescriptor(final String type, final String name)
216     {
217         return resources.getResourceDescriptor(type, name);
218     }
219 
220     /**
221      * @return true if the plugin has been enabled
222      */
223     @Deprecated
224     public boolean isEnabled()
225     {
226         return getPluginState() == PluginState.ENABLED;
227     }
228 
229     public final void enable()
230     {
231         final PluginState state = pluginState.get();
232         if ((state == PluginState.ENABLED) || (state == PluginState.ENABLING))
233         {
234             return;
235         }
236         if (getLog().isDebugEnabled())
237         {
238             getLog().debug("Enabling plugin '" + getKey() + "'");
239         }
240         try
241         {
242             // not ideal as comparison and set doesn't happen atomically
243             final PluginState desiredState = enableInternal();
244             if ((desiredState != PluginState.ENABLED) && (desiredState != PluginState.ENABLING))
245             {
246                 log.warn("Illegal state transition to " + desiredState + " for plugin '" + getKey() + "' on enable()");
247             }
248             setPluginState(desiredState);
249         }
250         catch (final PluginException ex)
251         {
252             log.warn("Unable to enable plugin '" + getKey() + "'", ex);
253             throw ex;
254         }
255         if (getLog().isDebugEnabled())
256         {
257             getLog().debug("Enabled plugin '" + getKey() + "'");
258         }
259     }
260 
261     /**
262      * Perform any internal enabling logic. Subclasses should only throw
263      * {@link PluginException}.
264      * 
265      * @throws PluginException If the plugin could not be enabled
266      * @since 2.2.0
267      * @return Either {@link PluginState#ENABLED} or
268      *         {@link PluginState#ENABLING}
269      */
270     protected PluginState enableInternal() throws PluginException
271     {
272         return PluginState.ENABLED;
273     }
274 
275     public final void disable()
276     {
277         if (pluginState.get() == PluginState.DISABLED)
278         {
279             return;
280         }
281         if (getLog().isDebugEnabled())
282         {
283             getLog().debug("Disabling plugin '" + getKey() + "'");
284         }
285         try
286         {
287             setPluginState(PluginState.DISABLING);
288             disableInternal();
289             setPluginState(PluginState.DISABLED);
290         }
291         catch (final PluginException ex)
292         {
293             setPluginState(PluginState.ENABLED);
294             log.warn("Unable to disable plugin '" + getKey() + "'", ex);
295             throw ex;
296         }
297         if (getLog().isDebugEnabled())
298         {
299             getLog().debug("Disabled plugin '" + getKey() + "'");
300         }
301     }
302 
303     /**
304      * Perform any internal disabling logic. Subclasses should only throw
305      * {@link PluginException}.
306      * 
307      * @throws PluginException If the plugin could not be disabled
308      * @since 2.2.0
309      */
310     protected void disableInternal() throws PluginException
311     {}
312 
313     public Set<String> getRequiredPlugins()
314     {
315         return Collections.emptySet();
316     }
317 
318     @Override
319     public final Set<String> getPermissions()
320     {
321         return ImmutableSet.copyOf(
322                 Iterables.transform(
323                         Iterables.filter(getPluginInformation().getPermissions(),
324                                 new Predicate<PluginPermission>()
325                                 {
326                                     @Override
327                                     public boolean apply(PluginPermission p)
328                                     {
329                                         return p.getInstallationMode() == null || p.getInstallationMode().equals(getInstallationMode());
330                                     }
331                                 }),
332                         new Function<PluginPermission, String>()
333                         {
334                             @Override
335                             public String apply(PluginPermission p)
336                             {
337                                 return p.getName();
338                             }
339                         }));
340     }
341 
342     protected InstallationMode getInstallationMode()
343     {
344         return InstallationMode.LOCAL;
345     }
346 
347     public void close()
348     {
349         uninstall();
350     }
351 
352     public final void install()
353     {
354         if (pluginState.get() == PluginState.INSTALLED)
355         {
356             return;
357         }
358         if (getLog().isDebugEnabled())
359         {
360             getLog().debug("Installing plugin '" + getKey() + "'");
361         }
362         try
363         {
364             installInternal();
365             setPluginState(PluginState.INSTALLED);
366         }
367         catch (final PluginException ex)
368         {
369             log.warn("Unable to install plugin '" + getKey() + "'", ex);
370             throw ex;
371         }
372         if (getLog().isDebugEnabled())
373         {
374             getLog().debug("Installed plugin '" + getKey() + "'");
375         }
376     }
377 
378     /**
379      * Perform any internal installation logic. Subclasses should only throw
380      * {@link PluginException}.
381      * 
382      * @throws PluginException If the plugin could not be installed
383      * @since 2.2.0
384      */
385     protected void installInternal() throws PluginException
386     {}
387 
388     public final void uninstall()
389     {
390         if (pluginState.get() == PluginState.UNINSTALLED)
391         {
392             return;
393         }
394         if (getLog().isDebugEnabled())
395         {
396             getLog().debug("Uninstalling plugin '" + getKey() + "'");
397         }
398         try
399         {
400             uninstallInternal();
401             setPluginState(PluginState.UNINSTALLED);
402         }
403         catch (final PluginException ex)
404         {
405             log.warn("Unable to uninstall plugin '" + getKey() + "'", ex);
406             throw ex;
407         }
408         if (getLog().isDebugEnabled())
409         {
410             getLog().debug("Uninstalled plugin '" + getKey() + "'");
411         }
412     }
413 
414     /**
415      * Perform any internal uninstallation logic. Subclasses should only throw
416      * {@link PluginException}.
417      * 
418      * @throws PluginException If the plugin could not be uninstalled
419      * @since 2.2.0
420      */
421     protected void uninstallInternal() throws PluginException
422     {}
423 
424     /**
425      * Setter for the enabled state of a plugin. If this is set to false then
426      * the plugin will not execute.
427      */
428     @Deprecated
429     public void setEnabled(final boolean enabled)
430     {
431         if (enabled)
432         {
433             enable();
434         }
435         else
436         {
437             disable();
438         }
439     }
440 
441     public boolean isSystemPlugin()
442     {
443         return system;
444     }
445 
446     public boolean containsSystemModule()
447     {
448         for (final ModuleDescriptor<?> moduleDescriptor : modules.values())
449         {
450             if (moduleDescriptor.isSystemModule())
451             {
452                 return true;
453             }
454         }
455         return false;
456     }
457 
458     public void setSystemPlugin(final boolean system)
459     {
460         this.system = system;
461     }
462 
463     public Date getDateLoaded()
464     {
465         return dateLoaded;
466     }
467 
468     public boolean isBundledPlugin()
469     {
470         return false;
471     }
472 
473     /**
474      * Compares this Plugin to another Plugin for order. The primary sort field
475      * is the key, and the secondary field is the version number.
476      * 
477      * @param otherPlugin The plugin to be compared.
478      * @return a negative integer, zero, or a positive integer as this Plugin is
479      *         less than, equal to, or greater than the specified Plugin.
480      * @see VersionStringComparator
481      * @see Comparable#compareTo
482      */
483     public int compareTo(final Plugin otherPlugin)
484     {
485         if (otherPlugin.getKey() == null)
486         {
487             if (getKey() == null)
488             {
489                 // both null keys - not going to bother checking the version,
490                 // who cares?
491                 return 0;
492             }
493             return 1;
494         }
495         if (getKey() == null)
496         {
497             return -1;
498         }
499 
500         // If the compared plugin doesn't have the same key, the current object
501         // is greater
502         if (!otherPlugin.getKey().equals(getKey()))
503         {
504             return getKey().compareTo(otherPlugin.getKey());
505         }
506 
507         final String thisVersion = cleanVersionString((getPluginInformation() != null ? getPluginInformation().getVersion() : null));
508         final String otherVersion = cleanVersionString((otherPlugin.getPluginInformation() != null ? otherPlugin.getPluginInformation().getVersion() : null));
509 
510         // Valid versions should come after invalid versions because when we
511         // find multiple instances of a plugin, we choose the "latest".
512         if (!VersionStringComparator.isValidVersionString(thisVersion))
513         {
514             if (!VersionStringComparator.isValidVersionString(otherVersion))
515             {
516                 // both invalid
517                 return 0;
518             }
519             return -1;
520         }
521         if (!VersionStringComparator.isValidVersionString(otherVersion))
522         {
523             return 1;
524         }
525 
526         return new VersionStringComparator().compare(thisVersion, otherVersion);
527     }
528 
529     private String cleanVersionString(final String version)
530     {
531         if ((version == null) || version.trim().equals(""))
532         {
533             return "0";
534         }
535         return version.replaceAll(" ", "");
536     }
537 
538     @Override
539     public String toString()
540     {
541         final PluginInformation info = getPluginInformation();
542         return getKey() + ":" + (info == null ? "?" : info.getVersion());
543     }
544 }