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         final PluginState state = pluginState.get();
251         if ((state == PluginState.ENABLED) || (state == PluginState.ENABLING))
252         {
253             return;
254         }
255         if (getLog().isDebugEnabled())
256         {
257             getLog().debug("Enabling plugin '{}'", getKey());
258         }
259         try
260         {
261             // not ideal as comparison and set doesn't happen atomically
262             final PluginState desiredState = enableInternal();
263             if ((desiredState != PluginState.ENABLED) && (desiredState != PluginState.ENABLING))
264             {
265                 log.warn("Illegal state transition to {} for plugin '{}' on enable()", desiredState, getKey());
266             }
267             setPluginState(desiredState);
268         }
269         catch (final PluginException ex)
270         {
271             log.warn("Unable to enable plugin '{}'", getKey());
272             log.warn("Because of this exception", ex);
273             throw ex;
274         }
275         if (getLog().isDebugEnabled())
276         {
277             getLog().debug("Enabled plugin '{}'", getKey());
278         }
279     }
280 
281     /**
282      * Perform any internal enabling logic. Subclasses should only throw {@link PluginException}.
283      *
284      * @return Either {@link PluginState#ENABLED} or {@link PluginState#ENABLING}
285      * @throws PluginException If the plugin could not be enabled
286      * @since 2.2.0
287      */
288     protected PluginState enableInternal() throws PluginException
289     {
290         return PluginState.ENABLED;
291     }
292 
293     public final void disable()
294     {
295         if (pluginState.get() == PluginState.DISABLED)
296         {
297             return;
298         }
299         if (getLog().isDebugEnabled())
300         {
301             getLog().debug("Disabling plugin '" + getKey() + "'");
302         }
303         try
304         {
305             setPluginState(PluginState.DISABLING);
306             disableInternal();
307             setPluginState(PluginState.DISABLED);
308         }
309         catch (final PluginException ex)
310         {
311             setPluginState(PluginState.ENABLED);
312             log.warn("Unable to disable plugin '" + getKey() + "'", ex);
313             throw ex;
314         }
315         if (getLog().isDebugEnabled())
316         {
317             getLog().debug("Disabled plugin '" + getKey() + "'");
318         }
319     }
320 
321     /**
322      * Perform any internal disabling logic. Subclasses should only throw {@link PluginException}.
323      *
324      * @throws PluginException If the plugin could not be disabled
325      * @since 2.2.0
326      */
327     protected void disableInternal() throws PluginException
328     {
329     }
330 
331     public Set<String> getRequiredPlugins()
332     {
333         return Collections.emptySet();
334     }
335 
336     @Override
337     public final Set<String> getActivePermissions()
338     {
339         return permissions.get();
340     }
341 
342     private Set<String> getPermissionsInternal()
343     {
344         return ImmutableSet.copyOf(Iterables.transform(getPermissionsForCurrentInstallationMode(),
345                 new Function<PluginPermission, String>()
346                 {
347                     @Override
348                     public String apply(PluginPermission p)
349                     {
350                         return p.getName();
351                     }
352                 }));
353     }
354 
355     private Iterable<PluginPermission> getPermissionsForCurrentInstallationMode()
356     {
357         return Iterables.filter(getPluginInformation().getPermissions(),
358                 new Predicate<PluginPermission>()
359                 {
360                     @Override
361                     public boolean apply(PluginPermission p)
362                     {
363                         return isInstallationModeUndefinedOrEqualToCurrentInstallationMode(p.getInstallationMode());
364                     }
365                 });
366     }
367 
368     private boolean isInstallationModeUndefinedOrEqualToCurrentInstallationMode(Option<InstallationMode> installationMode)
369     {
370         return installationMode.fold(Suppliers.ofInstance(Boolean.TRUE),
371                 new Function<InstallationMode, Boolean>()
372                 {
373                     @Override
374                     public Boolean apply(InstallationMode mode)
375                     {
376                         return mode.equals(getInstallationMode());
377                     }
378                 });
379     }
380 
381     @Override
382     public final boolean hasAllPermissions()
383     {
384         return getActivePermissions().contains(Permissions.ALL_PERMISSIONS);
385     }
386 
387     protected InstallationMode getInstallationMode()
388     {
389         return InstallationMode.LOCAL;
390     }
391 
392     public void close()
393     {
394         uninstall();
395     }
396 
397     public final void install()
398     {
399         if (pluginState.get() == PluginState.INSTALLED)
400         {
401             return;
402         }
403         if (getLog().isDebugEnabled())
404         {
405             getLog().debug("Installing plugin '" + getKey() + "'");
406         }
407         try
408         {
409             installInternal();
410             setPluginState(PluginState.INSTALLED);
411         }
412         catch (final PluginException ex)
413         {
414             log.warn("Unable to install plugin '" + getKey() + "'", ex);
415             throw ex;
416         }
417         if (getLog().isDebugEnabled())
418         {
419             getLog().debug("Installed plugin '" + getKey() + "'");
420         }
421     }
422 
423     /**
424      * Perform any internal installation logic. Subclasses should only throw {@link PluginException}.
425      *
426      * @throws PluginException If the plugin could not be installed
427      * @since 2.2.0
428      */
429     protected void installInternal() throws PluginException
430     {
431     }
432 
433     public final void uninstall()
434     {
435         if (pluginState.get() == PluginState.UNINSTALLED)
436         {
437             return;
438         }
439         if (getLog().isDebugEnabled())
440         {
441             getLog().debug("Uninstalling plugin '" + getKey() + "'");
442         }
443         try
444         {
445             uninstallInternal();
446             setPluginState(PluginState.UNINSTALLED);
447         }
448         catch (final PluginException ex)
449         {
450             log.warn("Unable to uninstall plugin '" + getKey() + "'", ex);
451             throw ex;
452         }
453         if (getLog().isDebugEnabled())
454         {
455             getLog().debug("Uninstalled plugin '" + getKey() + "'");
456         }
457     }
458 
459     /**
460      * Perform any internal uninstallation logic. Subclasses should only throw {@link PluginException}.
461      *
462      * @throws PluginException If the plugin could not be uninstalled
463      * @since 2.2.0
464      */
465     protected void uninstallInternal() throws PluginException
466     {
467     }
468 
469     /**
470      * Setter for the enabled state of a plugin. If this is set to false then
471      * the plugin will not execute.
472      */
473     @Deprecated
474     public void setEnabled(final boolean enabled)
475     {
476         if (enabled)
477         {
478             enable();
479         }
480         else
481         {
482             disable();
483         }
484     }
485 
486     public boolean isSystemPlugin()
487     {
488         return system;
489     }
490 
491     public boolean containsSystemModule()
492     {
493         for (final ModuleDescriptor<?> moduleDescriptor : modules.values())
494         {
495             if (moduleDescriptor.isSystemModule())
496             {
497                 return true;
498             }
499         }
500         return false;
501     }
502 
503     public void setSystemPlugin(final boolean system)
504     {
505         this.system = system;
506     }
507 
508     public Date getDateLoaded()
509     {
510         return dateLoaded;
511     }
512 
513     public boolean isBundledPlugin()
514     {
515         return false;
516     }
517 
518     /**
519      * Compares this Plugin to another Plugin for order. The primary sort field is the key, and the secondary field
520      * is the version number.
521      *
522      * @param otherPlugin The plugin to be compared.
523      * @return a negative integer, zero, or a positive integer as this Plugin is less than, equal to, or greater than
524      * the specified Plugin.
525      * @see VersionStringComparator
526      * @see Comparable#compareTo
527      */
528     public int compareTo(final Plugin otherPlugin)
529     {
530         if (otherPlugin.getKey() == null)
531         {
532             if (getKey() == null)
533             {
534                 // both null keys - not going to bother checking the version,
535                 // who cares?
536                 return 0;
537             }
538             return 1;
539         }
540         if (getKey() == null)
541         {
542             return -1;
543         }
544 
545         // If the compared plugin doesn't have the same key, the current object
546         // is greater
547         if (!otherPlugin.getKey().equals(getKey()))
548         {
549             return getKey().compareTo(otherPlugin.getKey());
550         }
551 
552         final String thisVersion = cleanVersionString((getPluginInformation() != null ? getPluginInformation().getVersion() : null));
553         final String otherVersion = cleanVersionString((otherPlugin.getPluginInformation() != null ? otherPlugin.getPluginInformation().getVersion() : null));
554 
555         // Valid versions should come after invalid versions because when we
556         // find multiple instances of a plugin, we choose the "latest".
557         if (!VersionStringComparator.isValidVersionString(thisVersion))
558         {
559             if (!VersionStringComparator.isValidVersionString(otherVersion))
560             {
561                 // both invalid
562                 return 0;
563             }
564             return -1;
565         }
566         if (!VersionStringComparator.isValidVersionString(otherVersion))
567         {
568             return 1;
569         }
570 
571         return new VersionStringComparator().compare(thisVersion, otherVersion);
572     }
573 
574     private String cleanVersionString(final String version)
575     {
576         if ((version == null) || version.trim().equals(""))
577         {
578             return "0";
579         }
580         return version.replaceAll(" ", "");
581     }
582 
583     @Override
584     public String toString()
585     {
586         final PluginInformation info = getPluginInformation();
587         return getKey() + ":" + (info == null ? "?" : info.getVersion());
588     }
589 }