View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import com.atlassian.plugin.PluginAccessor;
6   import com.atlassian.plugin.PluginController;
7   import com.atlassian.plugin.event.PluginEventListener;
8   import com.atlassian.plugin.event.PluginEventManager;
9   import com.atlassian.plugin.event.events.PluginDisabledEvent;
10  import com.atlassian.plugin.event.events.PluginEnabledEvent;
11  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
12  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
13  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
14  import com.atlassian.plugin.instrumentation.PluginSystemInstrumentation;
15  import com.atlassian.plugin.instrumentation.Timer;
16  import com.atlassian.plugin.predicate.EnabledModulePredicate;
17  import com.atlassian.plugin.predicate.ModuleOfClassPredicate;
18  import com.google.common.cache.CacheLoader;
19  import com.google.common.cache.LoadingCache;
20  
21  import javax.annotation.Nonnull;
22  import java.util.Collection;
23  import java.util.List;
24  import java.util.function.Predicate;
25  import java.util.stream.Collectors;
26  
27  import static com.google.common.base.Preconditions.checkNotNull;
28  import static com.google.common.cache.CacheBuilder.newBuilder;
29  import static java.util.concurrent.TimeUnit.SECONDS;
30  
31  /**
32   * A caching decorator which caches {@link #getEnabledModuleDescriptorsByClass(Class)} and {@link
33   * #getEnabledModulesByClass(Class)} on {@link com.atlassian.plugin.PluginAccessor} interface.
34   */
35  public final class EnabledModuleCachingPluginAccessor extends ForwardingPluginAccessor implements PluginAccessor {
36      private static final long DESCRIPTOR_TIMEOUT_SEC = Long.getLong("com.atlassian.plugin.descriptor.class.cache.timeout.sec", 30 * 60);
37      private static final long MODULE_TIMEOUT_SEC = Long.getLong("com.atlassian.plugin.module.class.cache.timeout.sec", 30 * 60);
38  
39      private final SafeModuleExtractor safeModuleExtractor;
40  
41      private final LoadingCache<Class<ModuleDescriptor<Object>>, List<ModuleDescriptor<Object>>> cacheByDescriptorClass =
42              newBuilder()
43                      .expireAfterAccess(DESCRIPTOR_TIMEOUT_SEC, SECONDS)
44                      .build(new ByModuleDescriptorClassCacheLoader());
45  
46      private final LoadingCache<Class<?>, List<ModuleDescriptor>> cacheByModuleClass =
47              newBuilder()
48                      .expireAfterAccess(MODULE_TIMEOUT_SEC, SECONDS)
49                      .build(new ByModuleClassCacheLoader());
50  
51      public EnabledModuleCachingPluginAccessor(final PluginAccessor delegate, final PluginEventManager pluginEventManager, final PluginController pluginController) {
52          super(delegate);
53          checkNotNull(pluginEventManager);
54  
55          this.safeModuleExtractor = new SafeModuleExtractor(pluginController);
56  
57          // Strictly speaking a bad idea to register in constructor, but probably unlikely to get events during startup
58          // https://extranet.atlassian.com/display/CONF/2015/04/28/Publishing+unconstructed+objects+into+main+memory+considered+harmful constructor may not be safe
59          pluginEventManager.register(this);
60      }
61  
62      /**
63       * Clears the enabled module cache when any plugin is disabled. This ensures old modules are never returned from
64       * disabled plugins.
65       *
66       * @param event The plugin disabled event
67       */
68      @SuppressWarnings("unused")
69      @PluginEventListener
70      public void onPluginDisable(PluginDisabledEvent event) {
71          // Safer not to assume we also receive module disable events for each individual module.
72          invalidateAll();
73      }
74  
75      /**
76       * Clears the cache when any plugin is enabled.
77       */
78      @SuppressWarnings("unused")
79      @PluginEventListener
80      public void onPluginEnable(PluginEnabledEvent event) {
81          // PLUG-840 Don't assume we also receive module enable events for each individual module
82          invalidateAll();
83      }
84  
85      @SuppressWarnings("unused")
86      @PluginEventListener
87      public void onPluginModuleEnabled(final PluginModuleEnabledEvent event) {
88          // We invalidate from here and let cache loader reconstruct the state,
89          // rather than having the cache entries be their own event listeners,
90          // because if the cache entry is being constructed, we have a race condition: it may already have been told
91          // the module is disabled, but not yet have registered to receive this enable event.
92  
93          // Just invalidate everything: entries for module/descriptor superclasses and interfaces all need invalidating,
94          // this is simpler, and the cache mainly benefits web requests during post-startup steady state of plugin system.
95          invalidateAll();
96      }
97  
98      @SuppressWarnings("unused")
99      @PluginEventListener
100     public void onPluginModuleDisabled(final PluginModuleDisabledEvent event) {
101         // We invalidate from here and let cache loader reconstruct the state,
102         // rather than having the cache entries be their own event listeners,
103         // because if the cache entry is being constructed, we have a race condition: it may already have been told
104         // the module is enabled, but not yet have registered to receive this disable event.
105 
106         // Just invalidate everything: entries for module/descriptor superclasses and interfaces all need invalidating,
107         // this is simpler, and the cache mainly benefits web requests during post-startup steady state of plugin system.
108         invalidateAll();
109     }
110 
111     @SuppressWarnings("unused")
112     @PluginEventListener
113     public void onPluginFrameworkShutdown(final PluginFrameworkShutdownEvent event) {
114         invalidateAll();
115     }
116 
117     private void invalidateAll() {
118         cacheByDescriptorClass.invalidateAll();
119         cacheByModuleClass.invalidateAll();
120     }
121 
122     /**
123      * Cache loader for module descriptor cache
124      */
125     private class ByModuleDescriptorClassCacheLoader extends CacheLoader<Class<ModuleDescriptor<Object>>, List<ModuleDescriptor<Object>>> {
126         public List<ModuleDescriptor<Object>> load(@Nonnull final Class<ModuleDescriptor<Object>> moduleDescriptorClass) {
127             return delegate.getEnabledModuleDescriptorsByClass(moduleDescriptorClass);
128         }
129     }
130 
131     /**
132      * Cache loader for module class cache
133      */
134     private class ByModuleClassCacheLoader extends CacheLoader<Class, List<ModuleDescriptor>> {
135         @SuppressWarnings("unchecked")
136         public List<ModuleDescriptor> load(@Nonnull final Class moduleClass) {
137             return getEnabledModuleDescriptorsByModuleClass(moduleClass);
138         }
139     }
140 
141     /**
142      * This method overrides the same method on PluginAccessor from the plugin system. We keep an up to date cache
143      * of (module descriptor class M -> collection of module descriptors of descriptor class M) so we can avoid the expensive
144      * call to the plugin system.
145      *
146      * @param descriptorClazz The module descriptor class you wish to find all enabled instances of
147      * @return A list of all instances of enabled plugin module descriptors of the specified descriptor class
148      */
149     @SuppressWarnings("unchecked")
150     @Override
151     public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz) {
152         try (Timer ignored = PluginSystemInstrumentation.instance().pullTimer("getEnabledModuleDescriptorsByClass")) {
153             final List<ModuleDescriptor<Object>> descriptors =
154                     cacheByDescriptorClass.getUnchecked((Class<ModuleDescriptor<Object>>) descriptorClazz);
155             return (List<D>) descriptors;
156         }
157     }
158 
159     /**
160      * This method overrides the same method on PluginAccessor from the plugin system. We keep an up to date cache
161      * of (module class M -> collection of module descriptors that have module class M) so we can avoid the expensive
162      * call to the plugin system.
163      *
164      * @param moduleClass The module class you wish to find all instances of
165      * @return A list of all instances of enabled plugin modules of the specified class
166      */
167     @Override
168     public <M> List<M> getEnabledModulesByClass(final Class<M> moduleClass) {
169         try (Timer ignored = PluginSystemInstrumentation.instance().pullTimer("getEnabledModulesByClass")) {
170             List<?> descriptors = cacheByModuleClass.getUnchecked(moduleClass);
171             //noinspection unchecked
172             return safeModuleExtractor.getModules((List<ModuleDescriptor<M>>) descriptors);
173         }
174     }
175 
176     /**
177      * Creates a collection of module descriptors that all share the same given module class
178      *
179      * @param moduleClass The module class we are interested in
180      * @param <M>         The module class we are interested in
181      * @return A collection of all module descriptors in the system with module class M
182      */
183     private <M> List<ModuleDescriptor<M>> getEnabledModuleDescriptorsByModuleClass(final Class<M> moduleClass) {
184         final ModuleOfClassPredicate<M> ofType = new ModuleOfClassPredicate<>(moduleClass);
185         final EnabledModulePredicate enabled = new EnabledModulePredicate();
186         return getModuleDescriptors(delegate.getEnabledPlugins(), ofType.and(enabled));
187     }
188 
189     /**
190      * Builds an iterable of module descriptors for the given module predicate out of a collection of plugins.
191      *
192      * @param plugins   A collection of plugins to search through
193      * @param predicate A predicate to filter the module descriptors by
194      * @param <M>       The module class type
195      * @return An Iterable of module descriptors of module class M
196      */
197     private <M> List<ModuleDescriptor<M>> getModuleDescriptors(final Collection<Plugin> plugins,
198                                                                final Predicate<ModuleDescriptor<M>> predicate) {
199         //noinspection unchecked
200         return plugins.stream()
201                 .flatMap(plugin -> plugin.getModuleDescriptors().stream())
202                 .map(moduleDescriptor -> (ModuleDescriptor<M>) moduleDescriptor)
203                 .filter(predicate)
204                 .collect(Collectors.toList());
205     }
206 }