View Javadoc

1   package com.atlassian.plugin.descriptors;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import com.atlassian.plugin.PluginParseException;
6   import com.atlassian.plugin.Resources;
7   import com.atlassian.plugin.StateAware;
8   import com.atlassian.plugin.elements.ResourceDescriptor;
9   import com.atlassian.plugin.elements.ResourceLocation;
10  import com.atlassian.plugin.loaders.LoaderUtils;
11  import com.atlassian.plugin.module.LegacyModuleFactory;
12  import com.atlassian.plugin.module.ModuleFactory;
13  import com.atlassian.plugin.module.PrefixDelegatingModuleFactory;
14  import com.atlassian.plugin.util.ClassUtils;
15  import com.atlassian.plugin.util.JavaVersionUtils;
16  import com.atlassian.plugin.util.validation.ValidationPattern;
17  import com.atlassian.util.concurrent.NotNull;
18  import org.apache.commons.lang.Validate;
19  import org.dom4j.Element;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import java.util.List;
24  import java.util.Map;
25  
26  import static com.atlassian.plugin.util.Assertions.notNull;
27  import static com.atlassian.plugin.util.validation.ValidationPattern.createPattern;
28  import static com.atlassian.plugin.util.validation.ValidationPattern.test;
29  
30  public abstract class AbstractModuleDescriptor<T> implements ModuleDescriptor<T>, StateAware
31  {
32      protected Plugin plugin;
33      String key;
34      String name;
35      protected String moduleClassName;
36      protected Class<T> moduleClass;
37      String description;
38      boolean enabledByDefault = true;
39      boolean systemModule = false;
40  
41      /**
42       * @deprecated since 2.2.0
43       */
44      @Deprecated
45      protected boolean singleton = true;
46      Map<String, String> params;
47      protected Resources resources = Resources.EMPTY_RESOURCES;
48      private Float minJavaVersion;
49      private String i18nNameKey;
50      private String descriptionKey;
51      private String completeKey;
52      boolean enabled = false;
53      protected final ModuleFactory moduleFactory;
54      private final Logger log = LoggerFactory.getLogger(getClass());
55  
56      public AbstractModuleDescriptor(final ModuleFactory moduleFactory)
57      {
58          Validate.notNull(moduleFactory, "Module creator factory cannot be null");
59          this.moduleFactory = moduleFactory;
60      }
61  
62      /**
63       * @deprecated since 2.5.0 use the constructor which requires a
64       *             {@link com.atlassian.plugin.module.ModuleFactory}
65       */
66      @Deprecated
67      public AbstractModuleDescriptor()
68      {
69          this(ModuleFactory.LEGACY_MODULE_FACTORY);
70      }
71  
72      public void init(@NotNull final Plugin plugin, @NotNull final Element element) throws PluginParseException
73      {
74          validate(element);
75  
76          this.plugin = notNull("plugin", plugin);
77          this.key = element.attributeValue("key");
78          this.name = element.attributeValue("name");
79          this.i18nNameKey = element.attributeValue("i18n-name-key");
80          this.completeKey = buildCompleteKey(plugin, key);
81          this.description = element.elementTextTrim("description");
82          this.moduleClassName = element.attributeValue("class");
83          final Element descriptionElement = element.element("description");
84          this.descriptionKey = (descriptionElement != null) ? descriptionElement.attributeValue("key") : null;
85          params = LoaderUtils.getParams(element);
86  
87          if ("disabled".equalsIgnoreCase(element.attributeValue("state")))
88          {
89              enabledByDefault = false;
90          }
91  
92          if ("true".equalsIgnoreCase(element.attributeValue("system")))
93          {
94              systemModule = true;
95          }
96  
97          if (element.element("java-version") != null)
98          {
99              minJavaVersion = Float.valueOf(element.element("java-version").attributeValue("min"));
100         }
101 
102         if ("false".equalsIgnoreCase(element.attributeValue("singleton")))
103         {
104             singleton = false;
105         }
106         else if ("true".equalsIgnoreCase(element.attributeValue("singleton")))
107         {
108             singleton = true;
109         }
110         else
111         {
112             singleton = isSingletonByDefault();
113         }
114 
115         resources = Resources.fromXml(element);
116     }
117 
118     /**
119      * Validates the element, collecting the rules from
120      * {@link #provideValidationRules(ValidationPattern)}
121      * 
122      * @param element The configuration element
123      * @since 2.2.0
124      */
125     private void validate(final Element element)
126     {
127         notNull("element", element);
128         final ValidationPattern pattern = createPattern();
129         provideValidationRules(pattern);
130         pattern.evaluate(element);
131     }
132 
133     /**
134      * Provides validation rules for the pattern
135      * 
136      * @param pattern The validation pattern
137      * @since 2.2.0
138      */
139     protected void provideValidationRules(final ValidationPattern pattern)
140     {
141         pattern.rule(test("@key").withError("The key is required"));
142     }
143 
144     /**
145      * Override this for module descriptors which don't expect to be able to
146      * load a class successfully
147      * 
148      * @param plugin
149      * @param element
150      * @deprecated Since 2.1.0, use {@link #loadClass(Plugin,String)} instead
151      */
152     @Deprecated
153     protected void loadClass(final Plugin plugin, final Element element) throws PluginParseException
154     {
155         loadClass(plugin, element.attributeValue("class"));
156     }
157 
158     /**
159      * Loads the module class that this descriptor provides, and will not
160      * necessarily be the implementation class. Override this for module
161      * descriptors whose type cannot be determined via generics.
162      * 
163      * @param clazz The module class name to load
164      * @throws IllegalStateException If the module class cannot be determined
165      *             and the descriptor doesn't define a module type via generics
166      */
167     protected void loadClass(final Plugin plugin, final String clazz) throws PluginParseException
168     {
169         if (moduleClassName != null)
170         {
171             if (moduleFactory instanceof LegacyModuleFactory) // not all plugins
172             // have to have a
173             // class
174             {
175                 moduleClass = ((LegacyModuleFactory) moduleFactory).getModuleClass(moduleClassName, this);
176             }
177 
178             // This is only here for backwards compatibility with old code that
179             // uses {@link
180             // com.atlassian.plugin.PluginAccessor#getEnabledModulesByClass(Class)}
181             else if (moduleFactory instanceof PrefixDelegatingModuleFactory)
182             {
183                 moduleClass = ((PrefixDelegatingModuleFactory) moduleFactory).guessModuleClass(moduleClassName, this);
184             }
185         }
186         // If this module has no class, then we assume Void
187         else
188         {
189             moduleClass = (Class<T>) Void.class;
190         }
191 
192         // Usually is null when a prefix is used in the class name
193         if (moduleClass == null)
194         {
195             try
196             {
197 
198                 Class moduleTypeClass = null;
199                 try
200                 {
201                     moduleTypeClass = ClassUtils.getTypeArguments(AbstractModuleDescriptor.class, getClass()).get(0);
202                 }
203                 catch (RuntimeException ex)
204                 {
205                     log.debug("Unable to get generic type, usually due to Class.forName() problems", ex);
206                     moduleTypeClass = getModuleReturnClass();
207                 }
208                 moduleClass = moduleTypeClass;
209             }
210             catch (final ClassCastException ex)
211             {
212                 throw new IllegalStateException("The module class must be defined in a concrete instance of " + "ModuleDescriptor and not as another generic type.");
213             }
214 
215             if (moduleClass == null)
216             {
217                 throw new IllegalStateException("The module class cannot be determined, likely because it needs a concrete "
218                     + "module type defined in the generic type it passes to AbstractModuleDescriptor");
219             }
220         }
221     }
222 
223     Class<?> getModuleReturnClass()
224     {
225         try
226         {
227             return getClass().getMethod("getModule").getReturnType();
228         }
229         catch (NoSuchMethodException e)
230         {
231             throw new IllegalStateException("The getModule() method is missing (!) on " + getClass());
232         }
233     }
234 
235     /**
236      * Build the complete key based on the provided plugin and module key. This
237      * method has no side effects.
238      * 
239      * @param plugin The plugin for which the module belongs
240      * @param moduleKey The key for the module
241      * @return A newly constructed complete key, null if the plugin is null
242      */
243     private String buildCompleteKey(final Plugin plugin, final String moduleKey)
244     {
245         if (plugin == null)
246         {
247             return null;
248         }
249 
250         final StringBuffer completeKeyBuffer = new StringBuffer(32);
251         completeKeyBuffer.append(plugin.getKey()).append(":").append(moduleKey);
252         return completeKeyBuffer.toString();
253     }
254 
255     /**
256      * The default implementation disables the module if it's still enabled, and unreference the plugin.
257      * Override this if your plugin needs to clean up when it's been removed,
258      * and call super.destroy(Plugin) before returning.
259      */
260     public void destroy(final Plugin plugin)
261     {
262         if (enabled)
263         {
264             this.disabled();
265         }
266     }
267 
268     public boolean isEnabledByDefault()
269     {
270         return enabledByDefault && satisfiesMinJavaVersion();
271     }
272 
273     public boolean isSystemModule()
274     {
275         return systemModule;
276     }
277 
278     /**
279      * @deprecated Since 2.2.0
280      */
281     @Deprecated
282     public boolean isSingleton()
283     {
284         return singleton;
285     }
286 
287     /**
288      * @deprecated Since 2.2.0
289      */
290     @Deprecated
291     protected boolean isSingletonByDefault()
292     {
293         return true;
294     }
295 
296     /**
297      * Check that the module class of this descriptor implements a given
298      * interface, or extends a given class.
299      * 
300      * @param requiredModuleClazz The class this module's class must implement
301      *            or extend.
302      * @throws PluginParseException If the module class does not implement or
303      *             extend the given class.
304      */
305     final protected void assertModuleClassImplements(final Class<T> requiredModuleClazz) throws PluginParseException
306     {
307         if (!enabled)
308         {
309             throw new PluginParseException("Plugin module " + getKey() + " not enabled");
310         }
311         if (!requiredModuleClazz.isAssignableFrom(getModuleClass()))
312         {
313             throw new PluginParseException("Given module class: " + getModuleClass().getName() + " does not implement " + requiredModuleClazz.getName());
314         }
315     }
316 
317     public String getCompleteKey()
318     {
319         return completeKey;
320     }
321 
322     public String getPluginKey()
323     {
324         return getPlugin().getKey();
325     }
326 
327     public String getKey()
328     {
329         return key;
330     }
331 
332     public String getName()
333     {
334         return name;
335     }
336 
337     public Class<T> getModuleClass()
338     {
339         return moduleClass;
340     }
341 
342     public abstract T getModule();
343 
344     public String getDescription()
345     {
346         return description;
347     }
348 
349     public Map<String, String> getParams()
350     {
351         return params;
352     }
353 
354     public String getI18nNameKey()
355     {
356         return i18nNameKey;
357     }
358 
359     public String getDescriptionKey()
360     {
361         return descriptionKey;
362     }
363 
364     public List<ResourceDescriptor> getResourceDescriptors()
365     {
366         return resources.getResourceDescriptors();
367     }
368 
369     public List<ResourceDescriptor> getResourceDescriptors(final String type)
370     {
371         return resources.getResourceDescriptors(type);
372     }
373 
374     public ResourceLocation getResourceLocation(final String type, final String name)
375     {
376         return resources.getResourceLocation(type, name);
377     }
378 
379     public ResourceDescriptor getResourceDescriptor(final String type, final String name)
380     {
381         return resources.getResourceDescriptor(type, name);
382     }
383 
384     public Float getMinJavaVersion()
385     {
386         return minJavaVersion;
387     }
388 
389     public boolean satisfiesMinJavaVersion()
390     {
391         if (minJavaVersion != null)
392         {
393             return JavaVersionUtils.satisfiesMinVersion(minJavaVersion);
394         }
395         return true;
396     }
397 
398     /**
399      * Sets the plugin for the ModuleDescriptor
400      *
401      * @param plugin The plugin to set for this descriptor.
402      */
403     public void setPlugin(final Plugin plugin)
404     {
405         this.completeKey = buildCompleteKey(plugin, key);
406         this.plugin = plugin;
407     }
408 
409     /**
410      * @return The plugin this module descriptor is associated with.
411      */
412     public Plugin getPlugin()
413     {
414         return plugin;
415     }
416 
417     @Override
418     public boolean equals(Object obj)
419     {
420         return new ModuleDescriptors.EqualsBuilder().descriptor(this).isEqualTo(obj);
421     }
422 
423     @Override
424     public int hashCode()
425     {
426         return new ModuleDescriptors.HashCodeBuilder().descriptor(this).toHashCode();
427     }
428 
429     @Override
430     public String toString()
431     {
432         return getCompleteKey() + " (" + getDescription() + ")";
433     }
434 
435     /**
436      * Enables the descriptor by loading the module class. Classes overriding
437      * this method MUST call super.enabled() before their own enabling code.
438      * 
439      * @since 2.1.0
440      */
441     public void enabled()
442     {
443         loadClass(plugin, moduleClassName);
444         enabled = true;
445     }
446 
447     /**
448      * Disables the module descriptor. Classes overriding this method MUST call
449      * super.disabled() after their own disabling code.
450      */
451     public void disabled()
452     {
453         enabled = false;
454         moduleClass = null;
455     }
456 }