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 log.debug("Enabling plugin '{}'", getKey());
251
252 final PluginState state = pluginState.get();
253 if ((state == PluginState.ENABLED) || (state == PluginState.ENABLING))
254 {
255 log.debug("Plugin '{}' is already enabled, not doing anything.", getKey());
256 return;
257 }
258
259 try
260 {
261 log.debug("Plugin '{}' is NOT already enabled, actually enabling.", getKey());
262
263 final PluginState desiredState = enableInternal();
264 if ((desiredState != PluginState.ENABLED) && (desiredState != PluginState.ENABLING))
265 {
266 log.warn("Illegal state transition to {} for plugin '{}' on enable()", desiredState, getKey());
267 }
268 setPluginState(desiredState);
269 }
270 catch (final PluginException ex)
271 {
272 log.warn("Unable to enable plugin '{}'", getKey());
273 log.warn("Because of this exception", ex);
274 throw ex;
275 }
276
277 log.debug("Enabled plugin '{}'", getKey());
278 }
279
280
281
282
283
284
285
286
287 protected PluginState enableInternal() throws PluginException
288 {
289 return PluginState.ENABLED;
290 }
291
292 public final void disable()
293 {
294 if (pluginState.get() == PluginState.DISABLED)
295 {
296 return;
297 }
298 if (getLog().isDebugEnabled())
299 {
300 getLog().debug("Disabling plugin '" + getKey() + "'");
301 }
302 try
303 {
304 setPluginState(PluginState.DISABLING);
305 disableInternal();
306 setPluginState(PluginState.DISABLED);
307 }
308 catch (final PluginException ex)
309 {
310 setPluginState(PluginState.ENABLED);
311 log.warn("Unable to disable plugin '" + getKey() + "'", ex);
312 throw ex;
313 }
314 if (getLog().isDebugEnabled())
315 {
316 getLog().debug("Disabled plugin '" + getKey() + "'");
317 }
318 }
319
320
321
322
323
324
325
326 protected void disableInternal() throws PluginException
327 {
328 }
329
330 public Set<String> getRequiredPlugins()
331 {
332 return Collections.emptySet();
333 }
334
335 @Override
336 public final Set<String> getActivePermissions()
337 {
338 return permissions.get();
339 }
340
341 private Set<String> getPermissionsInternal()
342 {
343 return ImmutableSet.copyOf(Iterables.transform(getPermissionsForCurrentInstallationMode(),
344 new Function<PluginPermission, String>()
345 {
346 @Override
347 public String apply(PluginPermission p)
348 {
349 return p.getName();
350 }
351 }));
352 }
353
354 private Iterable<PluginPermission> getPermissionsForCurrentInstallationMode()
355 {
356 return Iterables.filter(getPluginInformation().getPermissions(),
357 new Predicate<PluginPermission>()
358 {
359 @Override
360 public boolean apply(PluginPermission p)
361 {
362 return isInstallationModeUndefinedOrEqualToCurrentInstallationMode(p.getInstallationMode());
363 }
364 });
365 }
366
367 private boolean isInstallationModeUndefinedOrEqualToCurrentInstallationMode(Option<InstallationMode> installationMode)
368 {
369 return installationMode.fold(Suppliers.ofInstance(Boolean.TRUE),
370 new Function<InstallationMode, Boolean>()
371 {
372 @Override
373 public Boolean apply(InstallationMode mode)
374 {
375 return mode.equals(getInstallationMode());
376 }
377 });
378 }
379
380 @Override
381 public final boolean hasAllPermissions()
382 {
383 return getActivePermissions().contains(Permissions.ALL_PERMISSIONS);
384 }
385
386 public InstallationMode getInstallationMode()
387 {
388 return InstallationMode.LOCAL;
389 }
390
391 public void close()
392 {
393 uninstall();
394 }
395
396 public final void install()
397 {
398 log.debug("Installing plugin '{}'.", getKey());
399
400 if (pluginState.get() == PluginState.INSTALLED)
401 {
402 log.debug("Plugin '{}' is already installed, not doing anything.", getKey());
403 return;
404 }
405
406 try
407 {
408 installInternal();
409 setPluginState(PluginState.INSTALLED);
410 }
411 catch (final PluginException ex)
412 {
413 log.warn("Unable to install plugin '" + getKey() + "'.", ex);
414 throw ex;
415 }
416
417 log.debug("Installed plugin '{}'.", getKey());
418 }
419
420
421
422
423
424
425
426 protected void installInternal() throws PluginException
427 {
428 log.debug("Actually installing plugin '{}'.", getKey());
429 }
430
431 public final void uninstall()
432 {
433 if (pluginState.get() == PluginState.UNINSTALLED)
434 {
435 return;
436 }
437 if (getLog().isDebugEnabled())
438 {
439 getLog().debug("Uninstalling plugin '" + getKey() + "'");
440 }
441 try
442 {
443 uninstallInternal();
444 setPluginState(PluginState.UNINSTALLED);
445 }
446 catch (final PluginException ex)
447 {
448 log.warn("Unable to uninstall plugin '" + getKey() + "'", ex);
449 throw ex;
450 }
451 if (getLog().isDebugEnabled())
452 {
453 getLog().debug("Uninstalled plugin '" + getKey() + "'");
454 }
455 }
456
457
458
459
460
461
462
463 protected void uninstallInternal() throws PluginException
464 {
465 }
466
467
468
469
470
471 @Deprecated
472 public void setEnabled(final boolean enabled)
473 {
474 if (enabled)
475 {
476 enable();
477 }
478 else
479 {
480 disable();
481 }
482 }
483
484 public boolean isSystemPlugin()
485 {
486 return system;
487 }
488
489 public boolean containsSystemModule()
490 {
491 for (final ModuleDescriptor<?> moduleDescriptor : modules.values())
492 {
493 if (moduleDescriptor.isSystemModule())
494 {
495 return true;
496 }
497 }
498 return false;
499 }
500
501 public void setSystemPlugin(final boolean system)
502 {
503 this.system = system;
504 }
505
506 public Date getDateLoaded()
507 {
508 return dateLoaded;
509 }
510
511 public boolean isBundledPlugin()
512 {
513 return false;
514 }
515
516
517
518
519
520
521
522
523
524
525
526 public int compareTo(final Plugin otherPlugin)
527 {
528 if (otherPlugin.getKey() == null)
529 {
530 if (getKey() == null)
531 {
532
533
534 return 0;
535 }
536 return 1;
537 }
538 if (getKey() == null)
539 {
540 return -1;
541 }
542
543
544
545 if (!otherPlugin.getKey().equals(getKey()))
546 {
547 return getKey().compareTo(otherPlugin.getKey());
548 }
549
550 final String thisVersion = cleanVersionString((getPluginInformation() != null ? getPluginInformation().getVersion() : null));
551 final String otherVersion = cleanVersionString((otherPlugin.getPluginInformation() != null ? otherPlugin.getPluginInformation().getVersion() : null));
552
553
554
555 if (!VersionStringComparator.isValidVersionString(thisVersion))
556 {
557 if (!VersionStringComparator.isValidVersionString(otherVersion))
558 {
559
560 return 0;
561 }
562 return -1;
563 }
564 if (!VersionStringComparator.isValidVersionString(otherVersion))
565 {
566 return 1;
567 }
568
569 return new VersionStringComparator().compare(thisVersion, otherVersion);
570 }
571
572 private String cleanVersionString(final String version)
573 {
574 if ((version == null) || version.trim().equals(""))
575 {
576 return "0";
577 }
578 return version.replaceAll(" ", "");
579 }
580
581 @Override
582 public String toString()
583 {
584 final PluginInformation info = getPluginInformation();
585 return getKey() + ":" + (info == null ? "?" : info.getVersion());
586 }
587 }