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