1. Atlassian
  2. Project: Atlassian
  3. atlassian-event

Commits

Kenny MacLeod  committed b7a2109

EVENT-17 - Add plugin event listener logic from Stash implementation

  • Participants
  • Parent commits bf41e50
  • Branches master

Comments (0)

Files changed (3)

File pom.xml

View file
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.atlassian.pom</groupId>
             <version>0.0.12</version>
         </dependency>
         <dependency>
+            <groupId>com.atlassian.plugins</groupId>
+            <artifactId>atlassian-plugins-osgi</artifactId>
+            <version>2.14.0-m5</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>10.0.1</version>

File src/main/java/com/atlassian/event/spring/EventListenerRegistrarBeanProcessor.java

View file
 package com.atlassian.event.spring;
 
+import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 
 import com.atlassian.event.api.EventListenerRegistrar;
 import com.atlassian.event.config.ListenerHandlersConfiguration;
 import com.atlassian.event.spi.ListenerHandler;
+import com.atlassian.plugin.ModuleDescriptor;
+import com.atlassian.plugin.Plugin;
+import com.atlassian.plugin.event.PluginEventListener;
+import com.atlassian.plugin.event.events.PluginDisabledEvent;
+import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
+import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
+import com.atlassian.plugin.osgi.factory.descriptor.ComponentImportModuleDescriptor;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 public class EventListenerRegistrarBeanProcessor implements DestructionAwareBeanPostProcessor, BeanFactoryAware, Ordered, ApplicationListener
 {
 
+    /**
+     * These are blacklisted because components in these plugins are known to register themselves with the
+     * EventPublisher. We're skipping them to save time on startup. Functionally, nothing would go wrong if we
+     * did try to auto-register them as the EventPublisher will only register a listener once.
+     */
+    private static final Set<String> BLACKLISTED_PLUGIN_KEYS = ImmutableSet.of(
+            "com.atlassian.upm.atlassian-universal-plugin-manager-plugin",
+            "com.atlassian.activeobjects.activeobjects-plugin",
+            "com.atlassian.applinks.applinks-plugin",
+            "com.atlassian.crowd.embedded.admin",
+            "com.atlassian.oauth.admin",
+            "com.atlassian.oauth.consumer",
+            "com.atlassian.oauth.consumer.sal",
+            "com.atlassian.oauth.serviceprovider",
+            "com.atlassian.oauth.serviceprovider.sal",
+            "com.atlassian.plugins.rest.atlassian-rest-module",
+            "com.atlassian.soy.soy-template-plugin",
+            "com.atlassian.templaterenderer.api",
+            "com.atlassian.templaterenderer.atlassian-template-renderer-velocity1.6-plugin",
+            "com.atlassian.auiplugin"
+    );
+
     private static final Logger LOG = LoggerFactory.getLogger(EventListenerRegistrarBeanProcessor.class);
 
     private final String eventListenerRegistrarBeanName;
     private final ListenerHandlersConfiguration listenerHandlersConfiguration;
 
     private final Map<String, Object> listenersToBeRegistered = Maps.newHashMap();
+    private final Multimap<String, Object> eventListenersFromPlugins = HashMultimap.create();
+
     private ConfigurableBeanFactory beanFactory;
     private EventListenerRegistrar eventListenerRegistrar;
     private boolean ignoreFurtherBeanProcessing;
         return 1;
     }
 
+    @PluginEventListener
+    public void onPluginModuleEnabled(PluginModuleEnabledEvent event)
+    {
+        Plugin plugin = event.getModule().getPlugin();
+
+        if (BLACKLISTED_PLUGIN_KEYS.contains(plugin.getKey()))
+        {
+            return;
+        }
+
+        ModuleDescriptor moduleDescriptor = event.getModule();
+
+        if (isSuitablePluginModule(moduleDescriptor))
+        {
+
+            // moduleDescriptor.getModule creates a new instance every time it's called. There's no point
+            // in looking for @EventListener annotations.
+            if (moduleDescriptorReturnsNewInstanceEveryTime(moduleDescriptor))
+            {
+                return;
+            }
+
+            try
+            {
+                Object module = moduleDescriptor.getModule();
+
+                try
+                {
+                    if (canBeRegisteredAsAListener(moduleDescriptor.getKey(), module))
+                    {
+                        eventListenersFromPlugins.put(plugin.getKey(), module);
+                        registerListener(moduleDescriptor.getKey(), module);
+                    }
+                }
+                catch (Throwable t)
+                {
+                    if (!(t instanceof NoClassDefFoundError))
+                    {
+                        LOG.info("Error registering eventlisteners for module " + moduleDescriptor
+                                .getCompleteKey() + "; skipping.", t);
+                    } else
+                    {
+                        LOG.debug("Skipping " + moduleDescriptor
+                                .getCompleteKey() + " because not all referenced classes are visible from" +
+                                " the classloader.");
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                // if there's no module to get, we don't need to scan for event listeners on the module
+                return;
+            }
+        }
+    }
+
+    private static boolean isSuitablePluginModule(ModuleDescriptor moduleDescriptor)
+    {
+        final Class<? extends ModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
+        final Class moduleClass = moduleDescriptor.getModuleClass();
+
+        return !moduleDescriptorClass.equals(ComponentImportModuleDescriptor.class) &&
+                moduleClass != null && !moduleClass.equals(Void.class);
+    }
+
+    @PluginEventListener
+    public void onPluginDisabled(PluginDisabledEvent event)
+    {
+        Plugin plugin = event.getPlugin();
+        Collection<Object> listeners = eventListenersFromPlugins.get(plugin.getKey());
+        if (listeners != null)
+        {
+            for (Object eventListener : listeners)
+            {
+                eventListenerRegistrar.unregister(eventListener);
+            }
+            eventListenersFromPlugins.removeAll(plugin.getKey());
+        }
+    }
+
+    @PluginEventListener
+    public void onPluginModuleDisabled(PluginModuleDisabledEvent event)
+    {
+        ModuleDescriptor moduleDescriptor = event.getModule();
+        Object module = null;
+        try
+        {
+            module = moduleDescriptor.getModule();
+        }
+        catch (Exception e)
+        {
+            // there is no module to get; it would not have been registered anyway.
+        }
+        if (module != null && eventListenersFromPlugins.remove(moduleDescriptor.getPluginKey(), module))
+        {
+            eventListenerRegistrar.unregister(module);
+        }
+    }
+
+    private static boolean moduleDescriptorReturnsNewInstanceEveryTime(ModuleDescriptor moduleDescriptor)
+    {
+        return moduleDescriptor.getModule() != moduleDescriptor.getModule();
+    }
+
     @Override
     public void onApplicationEvent(ApplicationEvent applicationEvent)
     {
         if (beanName.equals(eventListenerRegistrarBeanName))
         {
             eventListenerRegistrar = (EventListenerRegistrar) bean;
+            if (isAListener(this))
+            {
+                // If there is a ListenerHandler for @PluginEventListener, then register ourself as a listener
+                eventListenerRegistrar.register(this);
+            }
 
             for (Object object : listenersToBeRegistered.values())
             {
                 // The cost of merging is relatively high, which can have a _huge_ impact for large numbers of prototype beans. eg Hibernate Validation
                 // we only care about singleton beans; the prototype beans typically have a short lifespan
                 return beanFactory.getMergedBeanDefinition(beanName).isSingleton();
-            } catch (NoSuchBeanDefinitionException e)
+            }
+            catch (NoSuchBeanDefinitionException e)
             {
                 // no bean with that name; must be an anonymous bean, so register it anyway.
                 return true;
             }
-        }
-        else
+        } else
         {
             return false;
         }
         if (eventListenerRegistrar != null)
         {
             eventListenerRegistrar.register(bean);
-        }
-        else
+        } else
         {
             listenersToBeRegistered.put(beanName, bean);
         }
         if (eventListenerRegistrar != null)
         {
             eventListenerRegistrar.unregister(bean);
-        }
-        else
+        } else
         {
             listenersToBeRegistered.remove(beanName);
         }

File src/test/java/com/atlassian/event/spring/EventListenerRegistrarBeanProcessorTest.java

View file
 import com.atlassian.event.config.ListenerHandlersConfiguration;
 import com.atlassian.event.spi.ListenerHandler;
 import com.atlassian.event.spi.ListenerInvoker;
+import com.atlassian.plugin.ModuleDescriptor;
+import com.atlassian.plugin.Plugin;
+import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.test.util.ReflectionTestUtils;
     @Mock
     private ListenerInvoker listenerInvoker;
 
+    @Mock
+    private ModuleDescriptor moduleDescriptor;
+
+    @Mock
+    private Plugin plugin;
+
+    private PluginModuleEnabledEvent pluginModuleEnabledEvent;
+
     private final SomeListenerClass listenerBean = new SomeListenerClass();
 
     @Before
         // Individual tests may override this behaviour.
         registrarBeanProcessor.setBeanFactory(beanFactory);
         when(beanFactory.getMergedBeanDefinition(isA(String.class))).thenReturn(beanDefinition);
+        when(beanFactory.getMergedBeanDefinition(null)).thenThrow(new NoSuchBeanDefinitionException("null bean name"));
         when(beanDefinition.isSingleton()).thenReturn(true);
 
         // Set up a single ListenerHandler
         // fscking generics...
         List listenerInvokers = singletonList(listenerInvoker);
         when(listenerHandler.getInvokers(listenerBean)).thenReturn(listenerInvokers);
+
+        when(moduleDescriptor.getPlugin()).thenReturn(plugin);
+        when(moduleDescriptor.getModuleClass()).thenReturn(Object.class);
+        when(moduleDescriptor.getModule()).thenReturn(listenerBean);
+        when(plugin.getKey()).thenReturn("com.atlassian.test:some.plugin");
+
+        pluginModuleEnabledEvent = new PluginModuleEnabledEvent(moduleDescriptor);
     }
 
     @Test
         verifyThatNoMoreListenersNeedToBeRegistered();
     }
 
+    @Test
+    public void blacklistedPluginsAreNotRegistered()
+    {
+        when(plugin.getKey()).thenReturn("com.atlassian.activeobjects.activeobjects-plugin");
+
+        registrarBeanProcessor.onPluginModuleEnabled(pluginModuleEnabledEvent);
+        verifyThatNoMoreListenersNeedToBeRegistered();
+    }
+
+    @Test
+    public void validListenerModuklesAreRegisteredAsListeners()
+    {
+        registrarBeanProcessor.onPluginModuleEnabled(pluginModuleEnabledEvent);
+        processTheRegistrarProcessorBean();
+        verifyThatTheListenerHasBeenRegistered();
+    }
+
     private Collection listenersToBeRegistered()
     {
         return ((Map) ReflectionTestUtils.getField(registrarBeanProcessor, "listenersToBeRegistered")).values();