Add an event bus (#10)

diff --git a/server/core/src/main/java/org/apache/vysper/event/EventBus.java b/server/core/src/main/java/org/apache/vysper/event/EventBus.java
new file mode 100644
index 0000000..003e695
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/event/EventBus.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface EventBus {
+
+    /**
+     * Publishes an event.
+     */
+    <T> EventBus publish(Class<T> eventType, T event);
+
+}
diff --git a/server/core/src/main/java/org/apache/vysper/event/EventListener.java b/server/core/src/main/java/org/apache/vysper/event/EventListener.java
new file mode 100644
index 0000000..14927f0
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/event/EventListener.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface EventListener<T> {
+
+    /**
+     * Called when an event is to be handled by this listener. Any exception thrown
+     * by this method will be swiftly trapped. This method cannot and should not try
+     * to stop/alter the workflow that produced the event.
+     */
+    void onEvent(T event);
+
+}
diff --git a/server/core/src/main/java/org/apache/vysper/event/EventListenerDictionary.java b/server/core/src/main/java/org/apache/vysper/event/EventListenerDictionary.java
new file mode 100644
index 0000000..32e6602
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/event/EventListenerDictionary.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+import java.util.Set;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface EventListenerDictionary {
+
+    /**
+     * @return The listeners registered for the provided event type.
+     */
+    Set<EventListener<?>> get(Class<?> eventType);
+
+}
diff --git a/server/core/src/main/java/org/apache/vysper/event/SimpleEventBus.java b/server/core/src/main/java/org/apache/vysper/event/SimpleEventBus.java
new file mode 100644
index 0000000..74ed21c
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/event/SimpleEventBus.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleEventBus implements EventBus {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SimpleEventBus.class);
+
+    private final Set<EventListenerDictionary> listenerDictionaries = new LinkedHashSet<>();
+
+    public SimpleEventBus addDictionary(EventListenerDictionary dictionary) {
+        listenerDictionaries.add(dictionary);
+        return this;
+    }
+
+    @Override
+    public <T> EventBus publish(Class<T> eventType, T event) {
+        listenerDictionaries.stream().map(eventListenerDictionary -> eventListenerDictionary.get(eventType))
+                .flatMap(Collection::stream).distinct().forEach(listener -> fireEvent(event, listener));
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> void fireEvent(T event, EventListener<?> listener) {
+        LOG.trace("Firing event {} on listener {}", event, listener);
+        try {
+            ((EventListener<T>) listener).onEvent(event);
+            LOG.trace("Fired event {} on listener {}", event, listener);
+        } catch (Exception e) {
+            LOG.error(e.getMessage(), e);
+        }
+    }
+
+}
diff --git a/server/core/src/main/java/org/apache/vysper/event/SimpleEventListenerDictionary.java b/server/core/src/main/java/org/apache/vysper/event/SimpleEventListenerDictionary.java
new file mode 100644
index 0000000..3402b60
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/event/SimpleEventListenerDictionary.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleEventListenerDictionary implements EventListenerDictionary {
+
+    private final Map<Class<?>, Set<EventListener<?>>> listenersByEventType;
+
+    private SimpleEventListenerDictionary(Map<Class<?>, Set<EventListener<?>>> listenersByEventType) {
+        this.listenersByEventType = new HashMap<>(listenersByEventType);
+    }
+
+    @Override
+    public Set<EventListener<?>> get(Class<?> eventType) {
+        return ofNullable(listenersByEventType.get(eventType)).orElse(Collections.emptySet());
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private final Map<Class<?>, Set<EventListener<?>>> listenersByEventType = new HashMap<>();
+
+        private Builder() {
+        }
+
+        /**
+         * Register the provided listener to the provided event type. Since the listener
+         * reference will never be released, be aware of possible memory leak.
+         */
+        public <T> Builder register(Class<T> eventType, EventListener<T> listener) {
+            listenersByEventType.computeIfAbsent(eventType, type -> new LinkedHashSet<>()).add(listener);
+            return this;
+        }
+
+        public SimpleEventListenerDictionary build() {
+            return new SimpleEventListenerDictionary(listenersByEventType);
+        }
+    }
+
+}
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/modules/DefaultModule.java b/server/core/src/main/java/org/apache/vysper/xmpp/modules/DefaultModule.java
index 05f6457..e8c2e4b 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/modules/DefaultModule.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/DefaultModule.java
@@ -21,7 +21,9 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
+import org.apache.vysper.event.EventListenerDictionary;
 import org.apache.vysper.xmpp.protocol.HandlerDictionary;
 import org.apache.vysper.xmpp.server.ServerRuntimeContext;
 
@@ -46,6 +48,11 @@
         // empty default implementation
     }
 
+    @Override
+    public Optional<EventListenerDictionary> getEventListenerDictionary() {
+        return Optional.empty();
+    }
+
     public List<ServerRuntimeContextService> getServerServices() {
         List<ServerRuntimeContextService> serviceList = new ArrayList<ServerRuntimeContextService>();
         addServerServices(serviceList);
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/modules/Module.java b/server/core/src/main/java/org/apache/vysper/xmpp/modules/Module.java
index e1fee0f..2a9ef08 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/modules/Module.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/Module.java
@@ -20,7 +20,9 @@
 package org.apache.vysper.xmpp.modules;
 
 import java.util.List;
+import java.util.Optional;
 
+import org.apache.vysper.event.EventListenerDictionary;
 import org.apache.vysper.xmpp.protocol.HandlerDictionary;
 import org.apache.vysper.xmpp.server.ServerRuntimeContext;
 import org.apache.vysper.xmpp.server.XMPPServer;
@@ -33,6 +35,8 @@
  * <li>doing initializations, for example adding request listeners to the ServiceDiscoveryRequestListenerRegistry</li>
  * <li>adding dictionaries with new stanza handlers getting registered with the server which then get called as
  *     matching stanzas arrive</li>
+ * <li>adding dictionaries with new event listeners getting registered with the server which then get called as
+ *     matching events are published</li>
  * </ul>
  *
  * TODO: think about returning the supported XEPs
@@ -41,6 +45,7 @@
  * @see org.apache.vysper.xmpp.modules.DefaultDiscoAwareModule recommended for modules responding to service disco requests
  * @see org.apache.vysper.xmpp.modules.ServerRuntimeContextService
  * @see org.apache.vysper.xmpp.protocol.HandlerDictionary
+ * @see EventListenerDictionary
  *
  * @author The Apache MINA Project (dev@mina.apache.org)
  */
@@ -51,11 +56,16 @@
     String getVersion();
 
     /**
-     * all dictionaries to be added to the server
+     * all stanza handler dictionaries to be added to the server
      */
     List<HandlerDictionary> getHandlerDictionaries();
 
     /**
+     * @return The event listener dictionary to be added to the server
+     */
+    Optional<EventListenerDictionary> getEventListenerDictionary(); 
+
+    /**
      * all objects to be added to the server runtime context
      */
     List<ServerRuntimeContextService> getServerServices();
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContext.java b/server/core/src/main/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContext.java
index fde5102..439a6c3 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContext.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContext.java
@@ -28,6 +28,8 @@
 
 import javax.net.ssl.SSLContext;
 
+import org.apache.vysper.event.EventBus;
+import org.apache.vysper.event.SimpleEventBus;
 import org.apache.vysper.storage.OpenStorageProviderRegistry;
 import org.apache.vysper.storage.StorageProvider;
 import org.apache.vysper.storage.StorageProviderRegistry;
@@ -63,7 +65,7 @@
  */
 public class DefaultServerRuntimeContext implements ServerRuntimeContext, ModuleRegistry {
 
-    final Logger logger = LoggerFactory.getLogger(DefaultServerRuntimeContext.class);
+    private final Logger logger = LoggerFactory.getLogger(DefaultServerRuntimeContext.class);
 
     // basic internal data structures and configuration...
 
@@ -124,7 +126,7 @@
     /**
      * collection of all other services, which are mostly add-ons to the minimal setup
      */
-    final private Map<String, ServerRuntimeContextService> serverRuntimeContextServiceMap = new HashMap<String, ServerRuntimeContextService>();
+    private final Map<String, ServerRuntimeContextService> serverRuntimeContextServiceMap = new HashMap<String, ServerRuntimeContextService>();
 
     private List<Module> modules = new ArrayList<Module>();
     
@@ -132,12 +134,15 @@
      * map of all registered components, index by the subdomain they are registered for
      */
     protected final Map<String, Component> componentMap = new HashMap<String, Component>();
+    
+    private final SimpleEventBus eventBus;
 
     public DefaultServerRuntimeContext(Entity serverEntity, StanzaRelay stanzaRelay) {
         this.serverEntity = serverEntity;
         this.stanzaRelay = stanzaRelay;
         this.resourceRegistry = new DefaultResourceRegistry();
         this.stanzaHandlerLookup = new StanzaHandlerLookup(this);
+        this.eventBus = new SimpleEventBus();
     }
 
     public DefaultServerRuntimeContext(Entity serverEntity, StanzaRelay stanzaRelay,
@@ -304,7 +309,7 @@
      * XMPP extension ('XEP') to it.
      * @see org.apache.vysper.xmpp.modules.Module
      * @see DefaultServerRuntimeContext#addModules(java.util.List) for adding a number of modules at once
-     * @param modules
+     * @param module
      */
     public void addModule(Module module) {
         addModuleInternal(module);
@@ -337,6 +342,8 @@
 
         }
 
+        module.getEventListenerDictionary().ifPresent(eventBus::addDictionary);
+
         if (module instanceof Component) {
             registerComponent((Component) module);
         }
@@ -354,7 +361,12 @@
         }
         return null;
     }
-    
+
+    @Override
+    public EventBus getEventBus() {
+        return eventBus;
+    }
+
     public void registerComponent(Component component) {
         componentMap.put(component.getSubdomain(), component);
     }
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/server/ServerRuntimeContext.java b/server/core/src/main/java/org/apache/vysper/xmpp/server/ServerRuntimeContext.java
index 0972ab4..88ebbb3 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/server/ServerRuntimeContext.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/server/ServerRuntimeContext.java
@@ -24,6 +24,7 @@
 
 import javax.net.ssl.SSLContext;
 
+import org.apache.vysper.event.EventBus;
 import org.apache.vysper.storage.StorageProvider;
 import org.apache.vysper.xmpp.addressing.Entity;
 import org.apache.vysper.xmpp.authentication.UserAuthentication;
@@ -83,4 +84,6 @@
     <T> T getModule(Class<T> clazz);
     
     void addModule(Module module);
+    
+    EventBus getEventBus();
 }
diff --git a/server/core/src/test/java/org/apache/vysper/event/EventListenerMock.java b/server/core/src/test/java/org/apache/vysper/event/EventListenerMock.java
new file mode 100644
index 0000000..7fd2d9c
--- /dev/null
+++ b/server/core/src/test/java/org/apache/vysper/event/EventListenerMock.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.Assert;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class EventListenerMock<T> implements EventListener<T> {
+
+    private final List<T> receivedEventsSequence = new ArrayList<>();
+
+    private boolean failOnReceivedEvent;
+
+    public EventListenerMock<T> failOnReceivedEvent() {
+        failOnReceivedEvent = true;
+        return this;
+    }
+
+    public void assertReceivedEventsSequence(T... events) {
+        Assert.assertEquals(Arrays.asList(events), receivedEventsSequence);
+    }
+
+    public void assertNoEventReceived() {
+        Assert.assertTrue(receivedEventsSequence.isEmpty());
+    }
+
+    @Override
+    public void onEvent(T event) {
+        receivedEventsSequence.add(event);
+        if (failOnReceivedEvent) {
+            throw new RuntimeException("Failing as asked");
+        }
+    }
+}
diff --git a/server/core/src/test/java/org/apache/vysper/event/SimpleEventBusTest.java b/server/core/src/test/java/org/apache/vysper/event/SimpleEventBusTest.java
new file mode 100644
index 0000000..0c0f93d
--- /dev/null
+++ b/server/core/src/test/java/org/apache/vysper/event/SimpleEventBusTest.java
@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.event;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleEventBusTest {
+
+    private EventListenerMock<FooEvent> fooListener;
+
+    private EventListenerMock<BarEvent> barListener;
+
+    private EventListenerMock<BazEvent> bazListener;
+
+    private SimpleEventBus tested;
+
+    @Before
+    public void before() {
+        tested = new SimpleEventBus();
+
+        fooListener = new EventListenerMock<>();
+        barListener = new EventListenerMock<>();
+        bazListener = new EventListenerMock<>();
+    }
+
+    @Test
+    public void publishEventGivenOneDictionary() {
+        tested.addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, fooListener)
+                .register(BarEvent.class, barListener).register(BazEvent.class, bazListener).build());
+
+        FooEvent fooEvent = new FooEvent();
+        tested.publish(FooEvent.class, fooEvent);
+        BarEvent barEvent = new BarEvent();
+        tested.publish(BarEvent.class, barEvent);
+        BazEvent bazEvent = new BazEvent();
+        tested.publish(BazEvent.class, bazEvent);
+
+        fooListener.assertReceivedEventsSequence(fooEvent);
+        barListener.assertReceivedEventsSequence(barEvent);
+        bazListener.assertReceivedEventsSequence(bazEvent);
+    }
+
+    @Test
+    public void publishEventGivenTwoDictionaries() {
+        tested.addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, fooListener)
+                .register(BarEvent.class, barListener).build())
+                .addDictionary(SimpleEventListenerDictionary.builder().register(BazEvent.class, bazListener).build());
+
+        FooEvent fooEvent = new FooEvent();
+        tested.publish(FooEvent.class, fooEvent);
+        BarEvent barEvent = new BarEvent();
+        tested.publish(BarEvent.class, barEvent);
+        BazEvent bazEvent = new BazEvent();
+        tested.publish(BazEvent.class, bazEvent);
+
+        fooListener.assertReceivedEventsSequence(fooEvent);
+        barListener.assertReceivedEventsSequence(barEvent);
+        bazListener.assertReceivedEventsSequence(bazEvent);
+    }
+
+    @Test
+    public void publishEventGivenListenerRegisteredTwiceInOneDictionary() {
+        tested.addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, fooListener)
+                .register(FooEvent.class, fooListener).build());
+
+        FooEvent fooEvent = new FooEvent();
+        tested.publish(FooEvent.class, fooEvent);
+        fooListener.assertReceivedEventsSequence(fooEvent);
+    }
+
+    @Test
+    public void publishEventGivenListenerRegisteredTwiceInTwoDifferentDictionary() {
+        tested.addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, fooListener).build())
+                .addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, fooListener).build());
+
+        FooEvent event = new FooEvent();
+        tested.publish(FooEvent.class, event);
+        fooListener.assertReceivedEventsSequence(event);
+    }
+
+    @Test
+    public void publishSubClassEvent() {
+        tested.addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, fooListener).build());
+
+        SubFooEvent event = new SubFooEvent();
+        tested.publish(FooEvent.class, event);
+        fooListener.assertReceivedEventsSequence(event);
+    }
+
+    @Test
+    public void publishOnTwoListenersGivenOneFailingListener() {
+        EventListenerMock<FooEvent> failingListener = new EventListenerMock<FooEvent>().failOnReceivedEvent();
+        tested.addDictionary(SimpleEventListenerDictionary.builder().register(FooEvent.class, failingListener)
+                .register(FooEvent.class, fooListener).build());
+
+        FooEvent event = new FooEvent();
+        tested.publish(FooEvent.class, event);
+
+        failingListener.assertReceivedEventsSequence(event);
+        fooListener.assertReceivedEventsSequence(event);
+    }
+
+    private static class FooEvent {
+
+    }
+
+    private static class BarEvent {
+
+    }
+
+    private static class BazEvent {
+
+    }
+
+    private static class SubFooEvent extends FooEvent {
+
+    }
+
+}
\ No newline at end of file
diff --git a/server/core/src/test/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContextEventListenerTest.java b/server/core/src/test/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContextEventListenerTest.java
new file mode 100644
index 0000000..2e2f520
--- /dev/null
+++ b/server/core/src/test/java/org/apache/vysper/xmpp/server/DefaultServerRuntimeContextEventListenerTest.java
@@ -0,0 +1,84 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.server;
+
+import java.util.Optional;
+
+import org.apache.vysper.event.EventListenerDictionary;
+import org.apache.vysper.event.EventListenerMock;
+import org.apache.vysper.event.SimpleEventListenerDictionary;
+import org.apache.vysper.xmpp.modules.DefaultModule;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class DefaultServerRuntimeContextEventListenerTest {
+
+    private EventListenerMock<Event> eventListener;
+
+    private DefaultServerRuntimeContext tested;
+
+    @Before
+    public void before() {
+        eventListener = new EventListenerMock<>();
+        EventListenerDictionary listenerDictionary = SimpleEventListenerDictionary.builder()
+                .register(Event.class, eventListener).build();
+
+		tested = new DefaultServerRuntimeContext(null, null);
+        tested.addModule(new MyModule(listenerDictionary));
+    }
+
+    @Test
+    public void publishEventGivenRegisteredModule() {
+        Event event = new Event();
+        tested.getEventBus().publish(Event.class, event);
+        eventListener.assertReceivedEventsSequence(event);
+    }
+
+    private static class Event {
+
+    }
+
+    private static class MyModule extends DefaultModule {
+
+        private final EventListenerDictionary eventListenerDictionary;
+
+        private MyModule(EventListenerDictionary eventListenerDictionary) {
+            this.eventListenerDictionary = eventListenerDictionary;
+        }
+
+        @Override
+        public String getName() {
+            return "MyModule";
+        }
+
+        @Override
+        public String getVersion() {
+            return "1.0";
+        }
+
+        @Override
+        public Optional<EventListenerDictionary> getEventListenerDictionary() {
+            return Optional.of(eventListenerDictionary);
+        }
+    }
+}
\ No newline at end of file