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