1 package com.atlassian.plugin.impl;
2
3 import com.atlassian.annotations.ExperimentalApi;
4 import com.atlassian.annotations.Internal;
5 import com.atlassian.fugue.Option;
6 import com.atlassian.plugin.InstallationMode;
7 import com.atlassian.plugin.ModuleDescriptor;
8 import com.atlassian.plugin.Permissions;
9 import com.atlassian.plugin.Plugin;
10 import com.atlassian.plugin.PluginArtifact;
11 import com.atlassian.plugin.PluginDependencies;
12 import com.atlassian.plugin.PluginException;
13 import com.atlassian.plugin.PluginInformation;
14 import com.atlassian.plugin.PluginInternal;
15 import com.atlassian.plugin.PluginPermission;
16 import com.atlassian.plugin.PluginState;
17 import com.atlassian.plugin.Resourced;
18 import com.atlassian.plugin.Resources;
19 import com.atlassian.plugin.elements.ResourceDescriptor;
20 import com.atlassian.plugin.elements.ResourceLocation;
21 import com.atlassian.plugin.util.VersionStringComparator;
22 import com.atlassian.util.concurrent.CopyOnWriteMap;
23 import com.google.common.base.Function;
24 import com.google.common.base.Predicate;
25 import com.google.common.base.Supplier;
26 import com.google.common.base.Suppliers;
27 import com.google.common.collect.ImmutableSet;
28 import com.google.common.collect.Iterables;
29 import org.apache.commons.lang.StringUtils;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import javax.annotation.Nonnull;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Date;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Optional;
40 import java.util.Set;
41 import java.util.concurrent.CopyOnWriteArraySet;
42 import java.util.concurrent.atomic.AtomicReference;
43
44 import static com.google.common.base.Suppliers.memoize;
45
46
47
48
49
50
51
52
53
54
55
56 public abstract class AbstractPlugin implements PluginInternal, Plugin.EnabledMetricsSource, Comparable<Plugin> {
57 private static final Logger log = LoggerFactory.getLogger(AbstractPlugin.class);
58
59 private final Map<String, ModuleDescriptor<?>> modules = CopyOnWriteMap.<String, ModuleDescriptor<?>>builder().stableViews().newLinkedMap();
60 private final Set<ModuleDescriptor<?>> dynamicModules = new CopyOnWriteArraySet<>();
61 private String name;
62 private String i18nNameKey;
63 private String key;
64 private boolean enabledByDefault = true;
65 private PluginInformation pluginInformation = new PluginInformation();
66 private boolean system;
67 private Resourced resources = Resources.EMPTY_RESOURCES;
68 private int pluginsVersion = 1;
69 private final Date dateLoaded = new Date();
70
71
72
73 private volatile Date dateEnabling;
74
75
76
77 private volatile Date dateEnabled;
78 private final AtomicReference<PluginState> pluginState = new AtomicReference<>(PluginState.UNINSTALLED);
79
80 private final Supplier<Set<String>> permissions;
81
82 private volatile boolean bundledPlugin = false;
83
84 protected final PluginArtifact pluginArtifact;
85
86
87
88
89
90
91 @Deprecated
92 @Internal
93 public AbstractPlugin() {
94 this(null);
95 }
96
97 public AbstractPlugin(final PluginArtifact pluginArtifact) {
98 this.pluginArtifact = pluginArtifact;
99 permissions = memoize(new Supplier<Set<String>>() {
100 @Override
101 public Set<String> get() {
102 return getPermissionsInternal();
103 }
104 });
105 }
106
107 public String getName() {
108 return !StringUtils.isBlank(name) ? name : !StringUtils.isBlank(i18nNameKey) ? "" : getKey();
109 }
110
111 public void setName(final String name) {
112 this.name = name;
113 }
114
115
116
117
118 protected Logger getLog() {
119 return log;
120 }
121
122 public String getI18nNameKey() {
123 return i18nNameKey;
124 }
125
126 public void setI18nNameKey(final String i18nNameKey) {
127 this.i18nNameKey = i18nNameKey;
128 }
129
130 public String getKey() {
131 return key;
132 }
133
134 public void setKey(final String key) {
135 this.key = key;
136 }
137
138 public void addModuleDescriptor(final ModuleDescriptor<?> moduleDescriptor) {
139 modules.put(moduleDescriptor.getKey(), moduleDescriptor);
140 }
141
142 protected void removeModuleDescriptor(final String key) {
143 modules.remove(key);
144 }
145
146
147
148
149
150
151 public Collection<ModuleDescriptor<?>> getModuleDescriptors() {
152 return modules.values();
153 }
154
155 public ModuleDescriptor<?> getModuleDescriptor(final String key) {
156 return modules.get(key);
157 }
158
159 public <T> List<ModuleDescriptor<T>> getModuleDescriptorsByModuleClass(final Class<T> aClass) {
160 final List<ModuleDescriptor<T>> result = new ArrayList<>();
161 for (final ModuleDescriptor<?> moduleDescriptor : modules.values()) {
162 final Class<?> moduleClass = moduleDescriptor.getModuleClass();
163 if (moduleClass != null && aClass.isAssignableFrom(moduleClass)) {
164 @SuppressWarnings("unchecked")
165 final ModuleDescriptor<T> typedModuleDescriptor = (ModuleDescriptor<T>) moduleDescriptor;
166 result.add(typedModuleDescriptor);
167 }
168 }
169 return result;
170 }
171
172 public PluginState getPluginState() {
173 return pluginState.get();
174 }
175
176 protected void setPluginState(final PluginState state) {
177 if (log.isDebugEnabled()) {
178 log.debug("Plugin " + getKey() + " going from " + getPluginState() + " to " + state);
179 }
180
181 pluginState.set(state);
182 updateEnableTimes(state);
183 }
184
185
186
187
188
189
190
191
192
193 protected boolean compareAndSetPluginState(final PluginState requiredExistingState, final PluginState desiredState) {
194 if (log.isDebugEnabled()) {
195 log.debug("Plugin {} trying to go from {} to {} but only if in {}",
196 new Object[]{getKey(), getPluginState(), desiredState, requiredExistingState});
197 }
198 final boolean changed = pluginState.compareAndSet(requiredExistingState, desiredState);
199 if (changed) {
200 updateEnableTimes(desiredState);
201 }
202 return changed;
203 }
204
205 private void updateEnableTimes(final PluginState state) {
206 final Date now = new Date();
207 if (PluginState.ENABLING == state) {
208 dateEnabling = now;
209 dateEnabled = null;
210 } else if (PluginState.ENABLED == state) {
211
212 if (dateEnabling == null) {
213 dateEnabling = now;
214 }
215 dateEnabled = now;
216 }
217
218 }
219
220 public boolean isEnabledByDefault() {
221 return enabledByDefault && ((pluginInformation == null) || pluginInformation.satisfiesMinJavaVersion());
222 }
223
224 public void setEnabledByDefault(final boolean enabledByDefault) {
225 this.enabledByDefault = enabledByDefault;
226 }
227
228 public int getPluginsVersion() {
229 return pluginsVersion;
230 }
231
232 public void setPluginsVersion(final int pluginsVersion) {
233 this.pluginsVersion = pluginsVersion;
234 }
235
236 public PluginInformation getPluginInformation() {
237 return pluginInformation;
238 }
239
240 public void setPluginInformation(final PluginInformation pluginInformation) {
241 this.pluginInformation = pluginInformation;
242 }
243
244 public void setResources(final Resourced resources) {
245 this.resources = resources != null ? resources : Resources.EMPTY_RESOURCES;
246 }
247
248 public List<ResourceDescriptor> getResourceDescriptors() {
249 return resources.getResourceDescriptors();
250 }
251
252 public List<ResourceDescriptor> getResourceDescriptors(final String type) {
253 return resources.getResourceDescriptors(type);
254 }
255
256 public ResourceLocation getResourceLocation(final String type, final String name) {
257 return resources.getResourceLocation(type, name);
258 }
259
260
261
262
263 @Deprecated
264 public ResourceDescriptor getResourceDescriptor(final String type, final String name) {
265 return resources.getResourceDescriptor(type, name);
266 }
267
268
269
270
271 @Deprecated
272 public boolean isEnabled() {
273 return getPluginState() == PluginState.ENABLED;
274 }
275
276 public void enable() {
277 log.debug("Enabling plugin '{}'", getKey());
278
279 final PluginState state = pluginState.get();
280 if ((state == PluginState.ENABLED) || (state == PluginState.ENABLING)) {
281 log.debug("Plugin '{}' is already enabled, not doing anything.", getKey());
282 return;
283 }
284
285 try {
286 log.debug("Plugin '{}' is NOT already enabled, actually enabling.", getKey());
287 final PluginState desiredState = enableInternal();
288
289
290
291 if (desiredState != PluginState.PENDING) {
292 if ((desiredState != PluginState.ENABLED) && (desiredState != PluginState.ENABLING)) {
293 log.warn("Illegal state transition to {} for plugin '{}' on enable()", desiredState, getKey());
294 }
295 setPluginState(desiredState);
296 }
297
298 } catch (final PluginException ex) {
299 log.warn("Unable to enable plugin '{}'", getKey());
300 log.warn("Because of this exception", ex);
301 throw ex;
302 }
303
304 log.debug("Enabled plugin '{}'", getKey());
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 protected PluginState enableInternal() throws PluginException {
322 return PluginState.ENABLED;
323 }
324
325 public final void disable() {
326 if (pluginState.get() == PluginState.DISABLED) {
327 return;
328 }
329
330 log.debug("Disabling plugin '{}'", getKey());
331
332 try {
333 setPluginState(PluginState.DISABLING);
334 disableInternal();
335 setPluginState(PluginState.DISABLED);
336 } catch (final PluginException ex) {
337 setPluginState(PluginState.ENABLED);
338 log.warn("Unable to disable plugin '" + getKey() + "'", ex);
339 throw ex;
340 }
341
342 log.debug("Disabled plugin '{}'", getKey());
343 }
344
345
346
347
348
349
350
351 protected void disableInternal() throws PluginException {
352 }
353
354 public Set<String> getRequiredPlugins() {
355 return getDependencies().getAll();
356 }
357
358 @Nonnull
359 @Override
360 public PluginDependencies getDependencies() {
361 return new PluginDependencies();
362 }
363
364 @Override
365 public final Set<String> getActivePermissions() {
366 return permissions.get();
367 }
368
369 private Set<String> getPermissionsInternal() {
370 return ImmutableSet.copyOf(Iterables.transform(getPermissionsForCurrentInstallationMode(),
371 new Function<PluginPermission, String>() {
372 @Override
373 public String apply(final PluginPermission p) {
374 return p.getName();
375 }
376 }
377 ));
378 }
379
380 private Iterable<PluginPermission> getPermissionsForCurrentInstallationMode() {
381 return Iterables.filter(getPluginInformation().getPermissions(),
382 new Predicate<PluginPermission>() {
383 @Override
384 public boolean apply(final PluginPermission p) {
385 return isInstallationModeUndefinedOrEqualToCurrentInstallationMode(p.getInstallationMode());
386 }
387 }
388 );
389 }
390
391 private boolean isInstallationModeUndefinedOrEqualToCurrentInstallationMode(
392 final Option<InstallationMode> installationMode) {
393 return installationMode.fold(Suppliers.ofInstance(Boolean.TRUE),
394 new Function<InstallationMode, Boolean>() {
395 @Override
396 public Boolean apply(final InstallationMode mode) {
397 return mode.equals(getInstallationMode());
398 }
399 });
400 }
401
402 @Override
403 public final boolean hasAllPermissions() {
404 return getActivePermissions().contains(Permissions.ALL_PERMISSIONS);
405 }
406
407 public InstallationMode getInstallationMode() {
408 return InstallationMode.LOCAL;
409 }
410
411 public void close() {
412 uninstall();
413 }
414
415 public final void install() {
416 log.debug("Installing plugin '{}'.", getKey());
417
418 if (pluginState.get() == PluginState.INSTALLED) {
419 log.debug("Plugin '{}' is already installed, not doing anything.", getKey());
420 return;
421 }
422
423 try {
424 installInternal();
425 setPluginState(PluginState.INSTALLED);
426 } catch (final PluginException ex) {
427 log.warn("Unable to install plugin '" + getKey() + "'.", ex);
428 throw ex;
429 }
430
431 log.debug("Installed plugin '{}'.", getKey());
432 }
433
434
435
436
437
438
439
440 protected void installInternal() throws PluginException {
441 log.debug("Actually installing plugin '{}'.", getKey());
442 }
443
444 public final void uninstall() {
445 if (pluginState.get() == PluginState.UNINSTALLED) {
446 return;
447 }
448
449 log.debug("Uninstalling plugin '{}'", getKey());
450
451 try {
452 uninstallInternal();
453 setPluginState(PluginState.UNINSTALLED);
454 } catch (final PluginException ex) {
455 log.warn("Unable to uninstall plugin '" + getKey() + "'", ex);
456 throw ex;
457 }
458
459 log.debug("Uninstalled plugin '{}'", getKey());
460 }
461
462
463
464
465
466
467
468 protected void uninstallInternal() throws PluginException {
469 }
470
471
472
473
474 @Deprecated
475 public void setEnabled(final boolean enabled) {
476 if (enabled) {
477 enable();
478 } else {
479 disable();
480 }
481 }
482
483 public boolean isSystemPlugin() {
484 return system;
485 }
486
487 public boolean containsSystemModule() {
488 for (final ModuleDescriptor<?> moduleDescriptor : modules.values()) {
489 if (moduleDescriptor.isSystemModule()) {
490 return true;
491 }
492 }
493 return false;
494 }
495
496 public void setSystemPlugin(final boolean system) {
497 this.system = system;
498 }
499
500 public void resolve() {
501
502 }
503
504 public Date getDateLoaded() {
505 return dateLoaded;
506 }
507
508 @Override
509 public Date getDateInstalled() {
510 return new Date(dateLoaded.getTime());
511 }
512
513 @Override
514 @ExperimentalApi
515 public Date getDateEnabling() {
516 return dateEnabling;
517 }
518
519 @Override
520 @ExperimentalApi
521 public Date getDateEnabled() {
522 return dateEnabled;
523 }
524
525 @Override
526 public boolean isBundledPlugin() {
527 return bundledPlugin;
528 }
529
530 @Override
531 public void setBundledPlugin(final boolean bundledPlugin) {
532 this.bundledPlugin = bundledPlugin;
533 }
534
535 @Override
536 public PluginArtifact getPluginArtifact() {
537 return pluginArtifact;
538 }
539
540 @Override
541 public Optional<String> getScopeKey() {
542 return pluginInformation.getScopeKey();
543 }
544
545 @Override
546 public Iterable<ModuleDescriptor<?>> getDynamicModuleDescriptors() {
547 return ImmutableSet.copyOf(dynamicModules);
548 }
549
550 @Override
551 public boolean addDynamicModuleDescriptor(final ModuleDescriptor<?> module) {
552 addModuleDescriptor(module);
553 return dynamicModules.add(module);
554 }
555
556 @Override
557 public boolean removeDynamicModuleDescriptor(final ModuleDescriptor<?> module) {
558 removeModuleDescriptor(module.getKey());
559 return dynamicModules.remove(module);
560 }
561
562
563
564
565
566
567
568
569
570
571
572 public int compareTo(@Nonnull final Plugin otherPlugin) {
573 if (otherPlugin.getKey() == null) {
574 if (getKey() == null) {
575
576
577 return 0;
578 }
579 return 1;
580 }
581 if (getKey() == null) {
582 return -1;
583 }
584
585
586
587 if (!otherPlugin.getKey().equals(getKey())) {
588 return getKey().compareTo(otherPlugin.getKey());
589 }
590
591 final String thisVersion = cleanVersionString((getPluginInformation() != null ? getPluginInformation().getVersion() : null));
592 final String otherVersion = cleanVersionString((otherPlugin.getPluginInformation() != null ? otherPlugin.getPluginInformation().getVersion() : null));
593
594
595
596 if (!VersionStringComparator.isValidVersionString(thisVersion)) {
597 if (!VersionStringComparator.isValidVersionString(otherVersion)) {
598
599 return 0;
600 }
601 return -1;
602 }
603 if (!VersionStringComparator.isValidVersionString(otherVersion)) {
604 return 1;
605 }
606
607
608 if (VersionStringComparator.isSnapshotVersion(thisVersion) && VersionStringComparator.isSnapshotVersion(otherVersion)) {
609 final int comparison = new VersionStringComparator().compare(thisVersion, otherVersion);
610 if (comparison == 0) {
611 return this.getDateInstalled().compareTo(otherPlugin.getDateInstalled());
612 } else {
613 return comparison;
614 }
615 }
616
617 return new VersionStringComparator().compare(thisVersion, otherVersion);
618 }
619
620 @Internal
621 public static String cleanVersionString(final String version) {
622 if ((version == null) || version.trim().equals("")) {
623 return "0";
624 }
625 return version.replaceAll(" ", "");
626 }
627
628 @Override
629 public String toString() {
630 final PluginInformation info = getPluginInformation();
631 return getKey() + ":" + (info == null ? "?" : info.getVersion());
632 }
633 }