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