1 package com.atlassian.plugin.descriptors;
2
3 import com.atlassian.plugin.ModuleDescriptor;
4 import com.atlassian.plugin.ModulePermissionException;
5 import com.atlassian.plugin.Permissions;
6 import com.atlassian.plugin.Plugin;
7 import com.atlassian.plugin.PluginParseException;
8 import com.atlassian.plugin.RequirePermission;
9 import com.atlassian.plugin.Resources;
10 import com.atlassian.plugin.StateAware;
11 import com.atlassian.plugin.elements.ResourceDescriptor;
12 import com.atlassian.plugin.elements.ResourceLocation;
13 import com.atlassian.plugin.loaders.LoaderUtils;
14 import com.atlassian.plugin.module.LegacyModuleFactory;
15 import com.atlassian.plugin.module.ModuleFactory;
16 import com.atlassian.plugin.module.PrefixDelegatingModuleFactory;
17 import com.atlassian.plugin.util.ClassUtils;
18 import com.atlassian.plugin.util.JavaVersionUtils;
19 import com.atlassian.plugin.util.validation.ValidationPattern;
20 import com.atlassian.util.concurrent.NotNull;
21 import com.google.common.collect.ImmutableSet;
22 import com.google.common.collect.Sets;
23 import org.apache.commons.lang.Validate;
24 import org.dom4j.Element;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31
32 import static com.atlassian.plugin.util.Assertions.notNull;
33 import static com.atlassian.plugin.util.validation.ValidationPattern.createPattern;
34 import static com.atlassian.plugin.util.validation.ValidationPattern.test;
35
36 public abstract class AbstractModuleDescriptor<T> implements ModuleDescriptor<T>, StateAware
37 {
38 protected Plugin plugin;
39 String key;
40 String name;
41 protected String moduleClassName;
42 protected Class<T> moduleClass;
43 String description;
44 boolean enabledByDefault = true;
45 boolean systemModule = false;
46
47
48
49
50 @Deprecated
51 protected boolean singleton = true;
52 Map<String, String> params;
53 protected Resources resources = Resources.EMPTY_RESOURCES;
54 private Float minJavaVersion;
55 private String i18nNameKey;
56 private String descriptionKey;
57 private String completeKey;
58 boolean enabled = false;
59 protected final ModuleFactory moduleFactory;
60 private final Logger log = LoggerFactory.getLogger(getClass());
61
62 public AbstractModuleDescriptor(final ModuleFactory moduleFactory)
63 {
64 Validate.notNull(moduleFactory, "Module creator factory cannot be null");
65 this.moduleFactory = moduleFactory;
66 }
67
68
69
70
71
72 @Deprecated
73 public AbstractModuleDescriptor()
74 {
75 this(ModuleFactory.LEGACY_MODULE_FACTORY);
76 }
77
78 public void init(@NotNull final Plugin plugin, @NotNull final Element element) throws PluginParseException
79 {
80 validate(element);
81
82 this.plugin = notNull("plugin", plugin);
83 this.key = element.attributeValue("key");
84 this.name = element.attributeValue("name");
85 this.i18nNameKey = element.attributeValue("i18n-name-key");
86 this.completeKey = buildCompleteKey(plugin, key);
87 this.description = element.elementTextTrim("description");
88 this.moduleClassName = element.attributeValue("class");
89 final Element descriptionElement = element.element("description");
90 this.descriptionKey = (descriptionElement != null) ? descriptionElement.attributeValue("key") : null;
91 params = LoaderUtils.getParams(element);
92
93 if ("disabled".equalsIgnoreCase(element.attributeValue("state")))
94 {
95 enabledByDefault = false;
96 }
97
98 if ("true".equalsIgnoreCase(element.attributeValue("system")))
99 {
100 systemModule = true;
101 }
102
103 if (element.element("java-version") != null)
104 {
105 minJavaVersion = Float.valueOf(element.element("java-version").attributeValue("min"));
106 }
107
108 if ("false".equalsIgnoreCase(element.attributeValue("singleton")))
109 {
110 singleton = false;
111 }
112 else if ("true".equalsIgnoreCase(element.attributeValue("singleton")))
113 {
114 singleton = true;
115 }
116 else
117 {
118 singleton = isSingletonByDefault();
119 }
120
121 resources = Resources.fromXml(element);
122 }
123
124
125
126
127
128
129
130
131
132
133 protected final void checkPermissions() throws ModulePermissionException
134 {
135 final Set<String> pluginPermissions = plugin.getPermissions();
136 if (pluginPermissions.contains(Permissions.ALL_PERMISSIONS))
137 {
138 return;
139 }
140
141 final Sets.SetView<String> difference = Sets.difference(getAllRequiredPermissions(), pluginPermissions);
142 if (!difference.isEmpty())
143 {
144 throw new ModulePermissionException(getCompleteKey(), difference.immutableCopy());
145 }
146 }
147
148 private Set<String> getAllRequiredPermissions()
149 {
150 final ImmutableSet.Builder<String> permissions = ImmutableSet.builder();
151 permissions.addAll(getPermissionsFromAnnotations());
152 permissions.addAll(getRequiredPermissions());
153 return permissions.build();
154 }
155
156 private Set<String> getPermissionsFromAnnotations()
157 {
158 if (this.getClass().isAnnotationPresent(RequirePermission.class))
159 {
160 return ImmutableSet.copyOf(this.getClass().getAnnotation(RequirePermission.class).value());
161 }
162 else
163 {
164 return ImmutableSet.of();
165 }
166 }
167
168
169 protected Set<String> getRequiredPermissions()
170 {
171 return ImmutableSet.of();
172 }
173
174
175
176
177
178
179
180
181 private void validate(final Element element)
182 {
183 notNull("element", element);
184 final ValidationPattern pattern = createPattern();
185 provideValidationRules(pattern);
186 pattern.evaluate(element);
187 }
188
189
190
191
192
193
194
195 protected void provideValidationRules(final ValidationPattern pattern)
196 {
197 pattern.rule(test("@key").withError("The key is required"));
198 }
199
200
201
202
203
204
205
206
207
208 @Deprecated
209 protected void loadClass(final Plugin plugin, final Element element) throws PluginParseException
210 {
211 loadClass(plugin, element.attributeValue("class"));
212 }
213
214
215
216
217
218
219
220
221
222
223 protected void loadClass(final Plugin plugin, final String clazz) throws PluginParseException
224 {
225 if (moduleClassName != null)
226 {
227 if (moduleFactory instanceof LegacyModuleFactory)
228
229
230 {
231 moduleClass = ((LegacyModuleFactory) moduleFactory).getModuleClass(moduleClassName, this);
232 }
233
234
235
236
237 else if (moduleFactory instanceof PrefixDelegatingModuleFactory)
238 {
239 moduleClass = ((PrefixDelegatingModuleFactory) moduleFactory).guessModuleClass(moduleClassName, this);
240 }
241 }
242
243 else
244 {
245 moduleClass = (Class<T>) Void.class;
246 }
247
248
249 if (moduleClass == null)
250 {
251 try
252 {
253
254 Class moduleTypeClass = null;
255 try
256 {
257 moduleTypeClass = ClassUtils.getTypeArguments(AbstractModuleDescriptor.class, getClass()).get(0);
258 }
259 catch (RuntimeException ex)
260 {
261 log.debug("Unable to get generic type, usually due to Class.forName() problems", ex);
262 moduleTypeClass = getModuleReturnClass();
263 }
264 moduleClass = moduleTypeClass;
265 }
266 catch (final ClassCastException ex)
267 {
268 throw new IllegalStateException("The module class must be defined in a concrete instance of " + "ModuleDescriptor and not as another generic type.");
269 }
270
271 if (moduleClass == null)
272 {
273 throw new IllegalStateException("The module class cannot be determined, likely because it needs a concrete "
274 + "module type defined in the generic type it passes to AbstractModuleDescriptor");
275 }
276 }
277 }
278
279 Class<?> getModuleReturnClass()
280 {
281 try
282 {
283 return getClass().getMethod("getModule").getReturnType();
284 }
285 catch (NoSuchMethodException e)
286 {
287 throw new IllegalStateException("The getModule() method is missing (!) on " + getClass());
288 }
289 }
290
291
292
293
294
295
296
297
298
299 private String buildCompleteKey(final Plugin plugin, final String moduleKey)
300 {
301 if (plugin == null)
302 {
303 return null;
304 }
305
306 final StringBuffer completeKeyBuffer = new StringBuffer(32);
307 completeKeyBuffer.append(plugin.getKey()).append(":").append(moduleKey);
308 return completeKeyBuffer.toString();
309 }
310
311
312
313
314
315
316 public void destroy(final Plugin plugin)
317 {
318 if (enabled)
319 {
320 this.disabled();
321 }
322 }
323
324 public boolean isEnabledByDefault()
325 {
326 return enabledByDefault && satisfiesMinJavaVersion();
327 }
328
329 public boolean isSystemModule()
330 {
331 return systemModule;
332 }
333
334
335
336
337 @Deprecated
338 public boolean isSingleton()
339 {
340 return singleton;
341 }
342
343
344
345
346 @Deprecated
347 protected boolean isSingletonByDefault()
348 {
349 return true;
350 }
351
352
353
354
355
356
357
358
359
360
361 final protected void assertModuleClassImplements(final Class<T> requiredModuleClazz) throws PluginParseException
362 {
363 if (!enabled)
364 {
365 throw new PluginParseException("Plugin module " + getKey() + " not enabled");
366 }
367 if (!requiredModuleClazz.isAssignableFrom(getModuleClass()))
368 {
369 throw new PluginParseException("Given module class: " + getModuleClass().getName() + " does not implement " + requiredModuleClazz.getName());
370 }
371 }
372
373 public String getCompleteKey()
374 {
375 return completeKey;
376 }
377
378 public String getPluginKey()
379 {
380 return getPlugin().getKey();
381 }
382
383 public String getKey()
384 {
385 return key;
386 }
387
388 public String getName()
389 {
390 return name;
391 }
392
393 public Class<T> getModuleClass()
394 {
395 return moduleClass;
396 }
397
398 public abstract T getModule();
399
400 public String getDescription()
401 {
402 return description;
403 }
404
405 public Map<String, String> getParams()
406 {
407 return params;
408 }
409
410 public String getI18nNameKey()
411 {
412 return i18nNameKey;
413 }
414
415 public String getDescriptionKey()
416 {
417 return descriptionKey;
418 }
419
420 public List<ResourceDescriptor> getResourceDescriptors()
421 {
422 return resources.getResourceDescriptors();
423 }
424
425 public List<ResourceDescriptor> getResourceDescriptors(final String type)
426 {
427 return resources.getResourceDescriptors(type);
428 }
429
430 public ResourceLocation getResourceLocation(final String type, final String name)
431 {
432 return resources.getResourceLocation(type, name);
433 }
434
435 public ResourceDescriptor getResourceDescriptor(final String type, final String name)
436 {
437 return resources.getResourceDescriptor(type, name);
438 }
439
440 public Float getMinJavaVersion()
441 {
442 return minJavaVersion;
443 }
444
445 public boolean satisfiesMinJavaVersion()
446 {
447 if (minJavaVersion != null)
448 {
449 return JavaVersionUtils.satisfiesMinVersion(minJavaVersion);
450 }
451 return true;
452 }
453
454
455
456
457
458
459 public void setPlugin(final Plugin plugin)
460 {
461 this.completeKey = buildCompleteKey(plugin, key);
462 this.plugin = plugin;
463 }
464
465
466
467
468 public Plugin getPlugin()
469 {
470 return plugin;
471 }
472
473 @Override
474 public boolean equals(Object obj)
475 {
476 return new ModuleDescriptors.EqualsBuilder().descriptor(this).isEqualTo(obj);
477 }
478
479 @Override
480 public int hashCode()
481 {
482 return new ModuleDescriptors.HashCodeBuilder().descriptor(this).toHashCode();
483 }
484
485 @Override
486 public String toString()
487 {
488 return getCompleteKey() + " (" + getDescription() + ")";
489 }
490
491
492
493
494
495
496
497 public void enabled()
498 {
499 loadClass(plugin, moduleClassName);
500 enabled = true;
501 }
502
503
504
505
506
507 public void disabled()
508 {
509 enabled = false;
510 moduleClass = null;
511 }
512 }