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.util.JavaVersionUtils;
12  import static com.atlassian.plugin.util.validation.ValidationPattern.createPattern;
13  import static com.atlassian.plugin.util.validation.ValidationPattern.test;
14  import com.atlassian.plugin.util.validation.ValidationPattern;
15  
16  import org.dom4j.Element;
17  
18  import java.lang.reflect.Constructor;
19  import java.util.List;
20  import java.util.Map;
21  
22  public abstract class AbstractModuleDescriptor<T> implements ModuleDescriptor<T>, StateAware
23  {
24      protected Plugin plugin;
25      String key;
26      String name;
27      String moduleClassName;
28      Class<T> moduleClass;
29      String description;
30      boolean enabledByDefault = true;
31      boolean systemModule = false;
32  
33      /**
34       * @deprecated since 2.2.0
35       */
36      protected boolean singleton = true;
37      Map<String, String> params;
38      protected Resources resources = Resources.EMPTY_RESOURCES;
39      private Float minJavaVersion;
40      private String i18nNameKey;
41      private String descriptionKey;
42      private String completeKey;
43      boolean enabled = false;
44  
45      public void init(final Plugin plugin, final Element element) throws PluginParseException
46      {
47          validate(element);
48  
49          this.plugin = plugin;
50          this.key = element.attributeValue("key");
51          this.name = element.attributeValue("name");
52          this.i18nNameKey = element.attributeValue("i18n-name-key");
53          this.completeKey = buildCompleteKey(plugin, key);
54          this.description = element.elementTextTrim("description");
55          this.moduleClassName = element.attributeValue("class");
56          final Element descriptionElement = element.element("description");
57          this.descriptionKey = (descriptionElement != null) ? descriptionElement.attributeValue("key") : null;
58          params = LoaderUtils.getParams(element);
59  
60          if ("disabled".equalsIgnoreCase(element.attributeValue("state")))
61          {
62              enabledByDefault = false;
63          }
64  
65          if ("true".equalsIgnoreCase(element.attributeValue("system")))
66          {
67              systemModule = true;
68          }
69  
70          if (element.element("java-version") != null)
71          {
72              minJavaVersion = Float.valueOf(element.element("java-version").attributeValue("min"));
73          }
74  
75          if ("false".equalsIgnoreCase(element.attributeValue("singleton")))
76          {
77              singleton = false;
78          }
79          else if ("true".equalsIgnoreCase(element.attributeValue("singleton")))
80          {
81              singleton = true;
82          }
83          else
84          {
85              singleton = isSingletonByDefault();
86          }
87  
88          resources = Resources.fromXml(element);
89      }
90  
91      /**
92       * Validates the element, collecting the rules from {@link #provideValidationRules(ValidationPattern)}
93       *
94       * @param element The configuration element
95       * @since 2.2.0
96       */
97      private void validate(Element element)
98      {
99          ValidationPattern pattern = createPattern();
100         provideValidationRules(pattern);
101         pattern.evaluate(element);
102     }
103 
104     /**
105      * Provides validation rules for the pattern
106      *
107      * @param pattern The validation pattern
108      * @since 2.2.0
109      */
110     protected void provideValidationRules(ValidationPattern pattern)
111     {
112         pattern.rule(test("@key").withError("The key is required"));
113     }
114 
115     /**
116      * Override this for module descriptors which don't expect to be able to load a class successfully
117      * @param plugin
118      * @param element
119      * @deprecated Since 2.1.0, use {@link #loadClass(Plugin,String)} instead
120      */
121     @Deprecated
122     protected void loadClass(final Plugin plugin, final Element element) throws PluginParseException
123     {
124         loadClass(plugin, element.attributeValue("class"));
125     }
126 
127     /**
128      * Override this for module descriptors which don't expect to be able to load a class successfully
129      * @param plugin
130      * @param clazz The module class name to load
131      * @since 2.1.0
132      */
133     protected void loadClass(final Plugin plugin, final String clazz) throws PluginParseException
134     {
135         try
136         {
137             if (clazz != null) //not all plugins have to have a class
138             {
139                 // First try and load the class, to make sure the class exists
140                 @SuppressWarnings("unchecked")
141                 final Class<T> loadedClass = (Class<T>) plugin.loadClass(clazz, getClass());
142                 moduleClass = loadedClass;
143 
144                 // Then instantiate the class, so we can see if there are any dependencies that aren't satisfied
145                 try
146                 {
147                     final Constructor<T> noargConstructor = moduleClass.getConstructor(new Class[] {});
148                     if (noargConstructor != null)
149                     {
150                         moduleClass.newInstance();
151                     }
152                 }
153                 catch (final NoSuchMethodException e)
154                 {
155                     // If there is no "noarg" constructor then don't do the check
156                 }
157             }
158         }
159         catch (final ClassNotFoundException e)
160         {
161             throw new PluginParseException("Could not load class: " + clazz, e);
162         }
163         catch (final NoClassDefFoundError e)
164         {
165             throw new PluginParseException("Error retrieving dependency of class: " + clazz + ". Missing class: " + e.getMessage(), e);
166         }
167         catch (final UnsupportedClassVersionError e)
168         {
169             throw new PluginParseException("Class version is incompatible with current JVM: " + clazz, e);
170         }
171         catch (final Throwable t)
172         {
173             throw new PluginParseException(t);
174         }
175     }
176 
177     /**
178      * Build the complete key based on the provided plugin and module key. This method has no
179      * side effects.
180      * @param plugin The plugin for which the module belongs
181      * @param moduleKey The key for the module
182      * @return A newly constructed complete key, null if the plugin is null
183      */
184     private String buildCompleteKey(final Plugin plugin, final String moduleKey)
185     {
186         if (plugin == null)
187         {
188             return null;
189         }
190 
191         final StringBuffer completeKeyBuffer = new StringBuffer(32);
192         completeKeyBuffer.append(plugin.getKey()).append(":").append(moduleKey);
193         return completeKeyBuffer.toString();
194     }
195 
196     /**
197      * Override this if your plugin needs to clean up when it's been removed.
198      */
199     public void destroy(final Plugin plugin)
200     {}
201 
202     public boolean isEnabledByDefault()
203     {
204         return enabledByDefault && satisfiesMinJavaVersion();
205     }
206 
207     public boolean isSystemModule()
208     {
209         return systemModule;
210     }
211 
212     /**
213      * @deprecated Since 2.2.0
214      */
215     @Deprecated
216     public boolean isSingleton()
217     {
218         return singleton;
219     }
220 
221     /**
222      * @deprecated Since 2.2.0
223      */
224     @Deprecated
225     protected boolean isSingletonByDefault()
226     {
227         return true;
228     }
229 
230     /**
231      * Check that the module class of this descriptor implements a given interface, or extends a given class.
232      * @param requiredModuleClazz The class this module's class must implement or extend.
233      * @throws PluginParseException If the module class does not implement or extend the given class.
234      */
235     final protected void assertModuleClassImplements(final Class<T> requiredModuleClazz) throws PluginParseException
236     {
237         if (!enabled)
238         {
239             throw new PluginParseException("Plugin module " + getKey() + " not enabled");
240         }
241         if (!requiredModuleClazz.isAssignableFrom(getModuleClass()))
242         {
243             throw new PluginParseException(
244                 "Given module class: " + getModuleClass().getName() + " does not implement " + requiredModuleClazz.getName());
245         }
246     }
247 
248     public String getCompleteKey()
249     {
250         return completeKey;
251     }
252 
253     public String getPluginKey()
254     {
255         return plugin.getKey();
256     }
257 
258     public String getKey()
259     {
260         return key;
261     }
262 
263     public String getName()
264     {
265         return name;
266     }
267 
268     public Class<T> getModuleClass()
269     {
270         return moduleClass;
271     }
272 
273     public abstract T getModule();
274 
275     public String getDescription()
276     {
277         return description;
278     }
279 
280     public Map<String, String> getParams()
281     {
282         return params;
283     }
284 
285     public String getI18nNameKey()
286     {
287         return i18nNameKey;
288     }
289 
290     public String getDescriptionKey()
291     {
292         return descriptionKey;
293     }
294 
295     public List<ResourceDescriptor> getResourceDescriptors()
296     {
297         return resources.getResourceDescriptors();
298     }
299 
300     public List<ResourceDescriptor> getResourceDescriptors(final String type)
301     {
302         return resources.getResourceDescriptors(type);
303     }
304 
305     public ResourceLocation getResourceLocation(final String type, final String name)
306     {
307         return resources.getResourceLocation(type, name);
308     }
309 
310     public ResourceDescriptor getResourceDescriptor(final String type, final String name)
311     {
312         return resources.getResourceDescriptor(type, name);
313     }
314 
315     public Float getMinJavaVersion()
316     {
317         return minJavaVersion;
318     }
319 
320     public boolean satisfiesMinJavaVersion()
321     {
322         if (minJavaVersion != null)
323         {
324             return JavaVersionUtils.satisfiesMinVersion(minJavaVersion);
325         }
326         return true;
327     }
328 
329     /**
330      * Sets the plugin for the ModuleDescriptor
331      */
332     public void setPlugin(final Plugin plugin)
333     {
334         this.completeKey = buildCompleteKey(plugin, key);
335         this.plugin = plugin;
336     }
337 
338     /**
339      * @return The plugin this module descriptor is associated with
340      */
341     public Plugin getPlugin()
342     {
343         return plugin;
344     }
345 
346     @Override
347     public String toString()
348     {
349         return getCompleteKey() + " (" + getDescription() + ")";
350     }
351 
352     /**
353      * Enables the descriptor by loading the module class. Classes overriding this method MUST
354      * call super.enabled() before their own enabling code.
355      *
356      * @since 2.1.0
357      */
358     public void enabled()
359     {
360         loadClass(plugin, moduleClassName);
361         enabled = true;
362     }
363 
364     /**
365      * Disables the module descriptor. Classes overriding this method MUST call super.disabled() after
366      * their own disabling code.
367      */
368     public void disabled()
369     {
370         enabled = false;
371         moduleClass = null;
372     }
373 }