View Javadoc
1   package com.atlassian.event.spring;
2   
3   import com.atlassian.event.api.EventListenerRegistrar;
4   import com.atlassian.event.config.ListenerHandlersConfiguration;
5   import com.atlassian.event.spi.ListenerHandler;
6   import com.atlassian.plugin.ModuleDescriptor;
7   import com.atlassian.plugin.Plugin;
8   import com.atlassian.plugin.event.PluginEventListener;
9   import com.atlassian.plugin.event.events.PluginDisabledEvent;
10  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
11  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
12  import com.atlassian.plugin.eventlistener.descriptors.EventListenerModuleDescriptor;
13  import com.atlassian.plugin.osgi.factory.descriptor.ComponentImportModuleDescriptor;
14  import com.google.common.collect.HashMultimap;
15  import com.google.common.collect.ImmutableSet;
16  import com.google.common.collect.Maps;
17  import com.google.common.collect.Multimap;
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  import org.springframework.beans.BeansException;
21  import org.springframework.beans.factory.BeanFactory;
22  import org.springframework.beans.factory.BeanFactoryAware;
23  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
24  import org.springframework.beans.factory.config.ConfigurableBeanFactory;
25  import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
26  import org.springframework.context.ApplicationEvent;
27  import org.springframework.context.ApplicationListener;
28  import org.springframework.context.event.ContextRefreshedEvent;
29  import org.springframework.core.Ordered;
30  
31  import java.util.Collection;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import static com.google.common.base.Preconditions.checkNotNull;
36  
37  /**
38   * Convenience class that registers/unregisters beans that implement the {@code EventListener} interfaces, or has
39   * method(s) annotated with the {@code EventListener} annotation with the {@code EventListenerRegistrar} on bean
40   * creation and bean destruction.
41   * <p>
42   * {@code EventListenerRegistrarBeanProcessor} is implemented as a Spring {@code BeanPostProcessor}, which means that it
43   * gets called whenever a bean is created or destroyed in the application context. Because we need to get callbacks for
44   * all beans that get created, it is important to inject the minimum of dependencies through CI, as the
45   * {@code EventListenerRegistrarBeanProcessor} can only be created AFTER all of its dependencies have been created.
46   * <p>
47   * For this reason, the <em>name</em> of the {@code EventListenerRegistrar} bean is injected, <em>not</em> the
48   * {@code EventListenerRegistrar} instance itself. While the {@code EventListenerRegistrarBeanProcessor} does not have
49   * the {@code EventListenerRegistrar} yet, all beans that should be registered are stored in the
50   * {@code listenersToBeRegistered} map. When the {@code EventListenerRegistrarBeanProcessor} gets hold of the
51   * {@code EventListenerRegistrar}, all beans in {@code listenersToBeRegistered} are registered and the map is cleared.
52   *
53   * @since 2.3.0
54   *
55   * @deprecated since 3.1. This class is unused across JIRA, Confluence and Stash
56   *
57   * It has potentially unsafe code (moduleDescriptorReturnsNewInstanceEveryTime method) which can result
58   * in race conditions, slow instance starting times and side effects on plugin enablement during application lifetime.
59   *
60   * Recommendation is not to use this post processor.
61   *
62   * Alternatively, you could use EventListenerModuleDescriptor.
63   *
64   * To be removed in 4.x version of Atlassian Event.
65   */
66  public class EventListenerRegistrarBeanProcessor implements DestructionAwareBeanPostProcessor, BeanFactoryAware, Ordered, ApplicationListener {
67  
68      /**
69       * These are blacklisted because components in these plugins are known to register themselves with the
70       * EventPublisher. We're skipping them to save time on startup. Functionally, nothing would go wrong if we
71       * did try to auto-register them as the EventPublisher will only register a listener once.
72       */
73      private static final Set<String> BLACKLISTED_PLUGIN_KEYS = ImmutableSet.of(
74              "com.atlassian.upm.atlassian-universal-plugin-manager-plugin",
75              "com.atlassian.activeobjects.activeobjects-plugin",
76              "com.atlassian.applinks.applinks-plugin",
77              "com.atlassian.crowd.embedded.admin",
78              "com.atlassian.oauth.admin",
79              "com.atlassian.oauth.consumer",
80              "com.atlassian.oauth.consumer.sal",
81              "com.atlassian.oauth.serviceprovider",
82              "com.atlassian.oauth.serviceprovider.sal",
83              "com.atlassian.plugins.rest.atlassian-rest-module",
84              "com.atlassian.soy.soy-template-plugin",
85              "com.atlassian.templaterenderer.api",
86              "com.atlassian.templaterenderer.atlassian-template-renderer-velocity1.6-plugin",
87              "com.atlassian.auiplugin"
88      );
89  
90      private static final Logger LOG = LoggerFactory.getLogger(EventListenerRegistrarBeanProcessor.class);
91  
92      private final String eventListenerRegistrarBeanName;
93      private final ListenerHandlersConfiguration listenerHandlersConfiguration;
94  
95      private final Map<String, Object> listenersToBeRegistered = Maps.newHashMap();
96      private final Multimap<String, Object> eventListenersFromPlugins = HashMultimap.create();
97  
98      private ConfigurableBeanFactory beanFactory;
99      private EventListenerRegistrar eventListenerRegistrar;
100     private boolean ignoreFurtherBeanProcessing;
101 
102     public EventListenerRegistrarBeanProcessor(String eventListenerRegistrarBeanName, ListenerHandlersConfiguration listenerHandlersConfiguration) {
103         this.eventListenerRegistrarBeanName = checkNotNull(eventListenerRegistrarBeanName);
104         this.listenerHandlersConfiguration = checkNotNull(listenerHandlersConfiguration);
105     }
106 
107     @Override
108     public int getOrder() {
109         // process EventListenerRegistrarBeanProcessor as early as possible to guarantee that we don't get passed any AOP-ed proxies.
110         return 1;
111     }
112 
113     @PluginEventListener
114     public void onPluginModuleEnabled(PluginModuleEnabledEvent event) {
115         Plugin plugin = event.getModule().getPlugin();
116 
117         if (BLACKLISTED_PLUGIN_KEYS.contains(plugin.getKey())) {
118             return;
119         }
120 
121         ModuleDescriptor moduleDescriptor = event.getModule();
122 
123         if (isSuitablePluginModule(moduleDescriptor)) {
124 
125             // moduleDescriptor.getModule creates a new instance every time it's called. There's no point
126             // in looking for @EventListener annotations.
127             if (moduleDescriptorReturnsNewInstanceEveryTime(moduleDescriptor)) {
128                 return;
129             }
130 
131             try {
132                 Object module = moduleDescriptor.getModule();
133 
134                 try {
135                     if (canBeRegisteredAsAListener(moduleDescriptor.getKey(), module)) {
136                         eventListenersFromPlugins.put(plugin.getKey(), module);
137                         registerListener(moduleDescriptor.getKey(), module);
138                     }
139                 } catch (Throwable t) {
140                     if (!(t instanceof NoClassDefFoundError)) {
141                         LOG.info("Error registering eventlisteners for module " + moduleDescriptor
142                                 .getCompleteKey() + "; skipping.", t);
143                     } else {
144                         LOG.debug("Skipping " + moduleDescriptor
145                                 .getCompleteKey() + " because not all referenced classes are visible from" +
146                                 " the classloader.");
147                     }
148                 }
149             } catch (Exception e) {
150                 // if there's no module to get, we don't need to scan for event listeners on the module
151                 return;
152             }
153         }
154     }
155 
156     private static boolean isSuitablePluginModule(ModuleDescriptor moduleDescriptor) {
157         final Class<? extends ModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
158         final Class moduleClass = moduleDescriptor.getModuleClass();
159 
160         return !moduleDescriptorClass.equals(ComponentImportModuleDescriptor.class)
161                 && moduleClass != null
162                 && !moduleClass.equals(Void.class)
163                 && !(moduleDescriptor instanceof EventListenerModuleDescriptor);
164     }
165 
166     @PluginEventListener
167     public void onPluginDisabled(PluginDisabledEvent event) {
168         Plugin plugin = event.getPlugin();
169         Collection<Object> listeners = eventListenersFromPlugins.get(plugin.getKey());
170         if (listeners != null) {
171             for (Object eventListener : listeners) {
172                 eventListenerRegistrar.unregister(eventListener);
173             }
174             eventListenersFromPlugins.removeAll(plugin.getKey());
175         }
176     }
177 
178     @PluginEventListener
179     public void onPluginModuleDisabled(PluginModuleDisabledEvent event) {
180         ModuleDescriptor moduleDescriptor = event.getModule();
181         Object module = null;
182         try {
183             module = moduleDescriptor.getModule();
184         } catch (Exception e) {
185             // there is no module to get; it would not have been registered anyway.
186         }
187         if (module != null && eventListenersFromPlugins.remove(moduleDescriptor.getPluginKey(), module)) {
188             eventListenerRegistrar.unregister(module);
189         }
190     }
191 
192     private static boolean moduleDescriptorReturnsNewInstanceEveryTime(ModuleDescriptor moduleDescriptor) {
193         return moduleDescriptor.getModule() != moduleDescriptor.getModule();
194     }
195 
196     @Override
197     public void onApplicationEvent(ApplicationEvent applicationEvent) {
198         if (applicationEvent instanceof ContextRefreshedEvent) {
199             // Once the spring context has been refreshed the only beans to be processed from that point onwards will be prototypes.
200             // These should not be listening for Events in the first place, and so we can safely ignore them.
201             ignoreFurtherBeanProcessing = true;
202         }
203     }
204 
205     @Override
206     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
207         if (beanName.equals(eventListenerRegistrarBeanName)) {
208             eventListenerRegistrar = (EventListenerRegistrar) bean;
209             if (isAListener(this)) {
210                 // If there is a ListenerHandler for @PluginEventListener, then register ourself as a listener
211                 eventListenerRegistrar.register(this);
212             }
213 
214             for (Object object : listenersToBeRegistered.values()) {
215                 eventListenerRegistrar.register(object);
216             }
217 
218             listenersToBeRegistered.clear();
219         }
220         return bean;
221     }
222 
223     @Override
224     public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
225         unregisterListener(bean, beanName);
226     }
227 
228     @Override
229     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
230         if (!ignoreFurtherBeanProcessing && canBeRegisteredAsAListener(beanName, bean)) {
231             registerListener(beanName, bean);
232         }
233         return bean;
234     }
235 
236     private boolean canBeRegisteredAsAListener(String beanName, Object bean) {
237         if (isAListener(bean)) {
238             try {
239                 // The cost of merging is relatively high, which can have a _huge_ impact for large numbers of prototype beans. eg Hibernate Validation
240                 // we only care about singleton beans; the prototype beans typically have a short lifespan
241                 return beanFactory.getMergedBeanDefinition(beanName).isSingleton();
242             } catch (NoSuchBeanDefinitionException e) {
243                 // no bean with that name; must be an anonymous bean, so register it anyway.
244                 return true;
245             }
246         } else {
247             return false;
248         }
249     }
250 
251     @Override
252     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
253         this.beanFactory = (ConfigurableBeanFactory) beanFactory;
254     }
255 
256     private void registerListener(String beanName, Object bean) {
257         LOG.debug("Registering {} instance as an eventlistener", beanName);
258         if (eventListenerRegistrar != null) {
259             eventListenerRegistrar.register(bean);
260         } else {
261             listenersToBeRegistered.put(beanName, bean);
262         }
263     }
264 
265     private void unregisterListener(Object bean, String beanName) {
266         if (eventListenerRegistrar != null) {
267             eventListenerRegistrar.unregister(bean);
268         } else {
269             listenersToBeRegistered.remove(beanName);
270         }
271     }
272 
273     private boolean isAListener(Object object) {
274         for (ListenerHandler handler : listenerHandlersConfiguration.getListenerHandlers()) {
275             if (!handler.getInvokers(object).isEmpty()) {
276                 return true;
277             }
278         }
279         return false;
280     }
281 }