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