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