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