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