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