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