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