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
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
93
94
95
96
97 private void validate(Element element)
98 {
99 ValidationPattern pattern = createPattern();
100 provideValidationRules(pattern);
101 pattern.evaluate(element);
102 }
103
104
105
106
107
108
109
110 protected void provideValidationRules(ValidationPattern pattern)
111 {
112 pattern.rule(test("@key").withError("The key is required"));
113 }
114
115
116
117
118
119
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
129
130
131
132
133 protected void loadClass(final Plugin plugin, final String clazz) throws PluginParseException
134 {
135 try
136 {
137 if (clazz != null)
138 {
139
140 @SuppressWarnings("unchecked")
141 final Class<T> loadedClass = (Class<T>) plugin.loadClass(clazz, getClass());
142 moduleClass = loadedClass;
143
144
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
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
179
180
181
182
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
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
214
215 @Deprecated
216 public boolean isSingleton()
217 {
218 return singleton;
219 }
220
221
222
223
224 @Deprecated
225 protected boolean isSingletonByDefault()
226 {
227 return true;
228 }
229
230
231
232
233
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
331
332 public void setPlugin(final Plugin plugin)
333 {
334 this.completeKey = buildCompleteKey(plugin, key);
335 this.plugin = plugin;
336 }
337
338
339
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
354
355
356
357
358 public void enabled()
359 {
360 loadClass(plugin, moduleClassName);
361 enabled = true;
362 }
363
364
365
366
367
368 public void disabled()
369 {
370 enabled = false;
371 moduleClass = null;
372 }
373 }