View Javadoc

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