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