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