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
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
64
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
120
121
122
123
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
135
136
137
138
139 protected void provideValidationRules(final ValidationPattern pattern)
140 {
141 pattern.rule(test("@key").withError("The key is required"));
142 }
143
144
145
146
147
148
149
150
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
160
161
162
163
164
165
166
167 protected void loadClass(final Plugin plugin, final String clazz) throws PluginParseException
168 {
169 if (moduleClassName != null)
170 {
171 if (moduleFactory instanceof LegacyModuleFactory)
172
173
174 {
175 moduleClass = ((LegacyModuleFactory) moduleFactory).getModuleClass(moduleClassName, this);
176 }
177
178
179
180
181 else if (moduleFactory instanceof PrefixDelegatingModuleFactory)
182 {
183 moduleClass = ((PrefixDelegatingModuleFactory) moduleFactory).guessModuleClass(moduleClassName, this);
184 }
185 }
186
187 else
188 {
189 moduleClass = (Class<T>) Void.class;
190 }
191
192
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
237
238
239
240
241
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
257
258
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
280
281 @Deprecated
282 public boolean isSingleton()
283 {
284 return singleton;
285 }
286
287
288
289
290 @Deprecated
291 protected boolean isSingletonByDefault()
292 {
293 return true;
294 }
295
296
297
298
299
300
301
302
303
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
400
401
402
403 public void setPlugin(final Plugin plugin)
404 {
405 this.completeKey = buildCompleteKey(plugin, key);
406 this.plugin = plugin;
407 }
408
409
410
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
437
438
439
440
441 public void enabled()
442 {
443 loadClass(plugin, moduleClassName);
444 enabled = true;
445 }
446
447
448
449
450
451 public void disabled()
452 {
453 enabled = false;
454 moduleClass = null;
455 }
456 }