You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by bd...@apache.org on 2016/07/11 21:04:36 UTC

[09/13] shiro git commit: SHIRO-395: event bus + event + infrastructure changes. 100% class/line coverage for all new events + event bus and supporting components.

SHIRO-395: event bus + event + infrastructure changes.  100% class/line coverage for all new events + event bus and supporting components.

git-svn-id: https://svn.apache.org/repos/asf/shiro/trunk@1454782 13f79535-47bb-0310-9956-ffa450edef68


Project: http://git-wip-us.apache.org/repos/asf/shiro/repo
Commit: http://git-wip-us.apache.org/repos/asf/shiro/commit/4929b4a4
Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/4929b4a4
Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/4929b4a4

Branch: refs/heads/1.3.x
Commit: 4929b4a415c48da576568efd41659cbb9d547d96
Parents: 41c3183
Author: Les Hazlewood <lh...@apache.org>
Authored: Sun Mar 10 04:12:51 2013 +0000
Committer: Brian Demers <bd...@apache.org>
Committed: Fri Jul 8 13:55:44 2016 -0400

----------------------------------------------------------------------
 .../main/java/org/apache/shiro/config/Ini.java  |  15 +-
 .../apache/shiro/config/ReflectionBuilder.java  |  65 ++++++--
 .../apache/shiro/config/event/BeanEvent.java    |  26 +++-
 .../shiro/config/event/ConfiguredBeanEvent.java |   7 +
 .../shiro/config/event/DestroyedBeanEvent.java  |  27 ++++
 .../config/event/InitializedBeanEvent.java      |  37 +++++
 .../config/event/InstantiatedBeanEvent.java     |  27 ++++
 .../config/event/LoggingBeanEventListener.java  |  42 ++++++
 .../shiro/config/event/LoggingBeanListener.java |  53 -------
 .../main/java/org/apache/shiro/event/Event.java |  27 ++++
 .../shiro/event/support/ClassComparator.java    |  73 ---------
 .../shiro/event/support/DefaultEventBus.java    | 122 +++++++++++----
 .../event/support/EventClassComparator.java     |  74 +++++++++
 .../event/support/EventListenerComparator.java  |   7 +-
 .../SingleArgumentMethodEventListener.java      |  12 +-
 .../shiro/mgt/CachingSecurityManager.java       |  15 +-
 .../apache/shiro/mgt/RealmSecurityManager.java  |  36 +++++
 .../shiro/mgt/SessionsSecurityManager.java      |  31 ++++
 .../mgt/AbstractNativeSessionManager.java       |  44 +++++-
 .../shiro/config/ReflectionBuilderTest.groovy   | 151 +++++++++++--------
 .../shiro/config/event/BeanEventTest.groovy     |  48 ++++++
 .../org/apache/shiro/event/EventTest.groovy     |  30 ++++
 .../event/support/ClassComparatorTest.groovy    |  62 --------
 .../event/support/DefaultEventBusTest.groovy    |  12 ++
 .../support/EventClassComparatorTest.groovy     |  64 ++++++++
 .../InvalidMethodModiferSubscriber.groovy       |  32 ++++
 ...SingleArgumentMethodEventListenerTest.groovy |  20 ++-
 .../shiro/config/RecordingBeanListener.java     |  26 +++-
 samples/web/src/main/webapp/WEB-INF/shiro.ini   |   2 +-
 29 files changed, 875 insertions(+), 312 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/Ini.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/Ini.java b/core/src/main/java/org/apache/shiro/config/Ini.java
index 8dbaef0..a71bc20 100644
--- a/core/src/main/java/org/apache/shiro/config/Ini.java
+++ b/core/src/main/java/org/apache/shiro/config/Ini.java
@@ -24,8 +24,17 @@ import org.apache.shiro.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.*;
-import java.util.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
 
 /**
  * A class representing the <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> text configuration format.
@@ -542,7 +551,7 @@ public class Ini implements Map<String, Ini.Section> {
                 throw new IllegalArgumentException(msg);
             }
 
-            log.trace("Discovered key/value pair: {}={}", key, value);
+            log.trace("Discovered key/value pair: {} = {}", key, value);
 
             return new String[]{key, value};
         }

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java b/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
index ac4d2a1..d7f0d1a 100644
--- a/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
+++ b/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
@@ -25,17 +25,33 @@ import org.apache.shiro.codec.Hex;
 import org.apache.shiro.config.event.BeanEvent;
 import org.apache.shiro.config.event.ConfiguredBeanEvent;
 import org.apache.shiro.config.event.DestroyedBeanEvent;
+import org.apache.shiro.config.event.InitializedBeanEvent;
 import org.apache.shiro.config.event.InstantiatedBeanEvent;
 import org.apache.shiro.event.EventBus;
 import org.apache.shiro.event.EventBusAware;
 import org.apache.shiro.event.Subscribe;
 import org.apache.shiro.event.support.DefaultEventBus;
-import org.apache.shiro.util.*;
+import org.apache.shiro.util.Assert;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Factory;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.Nameable;
+import org.apache.shiro.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.beans.PropertyDescriptor;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 
 /**
@@ -83,7 +99,7 @@ public class ReflectionBuilder {
     private final Map<String,Object> registeredEventSubscribers;
 
     //@since 1.3
-    private static Map<String,Object> createDefaultObjectMap() {
+    private Map<String,Object> createDefaultObjectMap() {
         Map<String,Object> map = new LinkedHashMap<String, Object>();
         map.put(EVENT_BUS_NAME, new DefaultEventBus());
         return map;
@@ -252,13 +268,25 @@ public class ReflectionBuilder {
 
     public void destroy() {
         final Map<String, Object> immutableObjects = Collections.unmodifiableMap(objects);
-        for(Map.Entry<String, ?> entry: objects.entrySet()) {
+
+        //destroy objects in the opposite order they were initialized:
+        List<Map.Entry<String,?>> entries = new ArrayList<Map.Entry<String,?>>(objects.entrySet());
+        Collections.reverse(entries);
+
+        for(Map.Entry<String, ?> entry: entries) {
             String id = entry.getKey();
             Object bean = entry.getValue();
-            BeanEvent event = new DestroyedBeanEvent(id, bean, immutableObjects);
-            eventBus.publish(event);
-            LifecycleUtils.destroy(bean);
+
+            //don't destroy the eventbus until the end - we need it to still be 'alive' while publishing destroy events:
+            if (bean != this.eventBus) { //memory equality check (not .equals) on purpose
+                LifecycleUtils.destroy(bean);
+                BeanEvent event = new DestroyedBeanEvent(id, bean, immutableObjects);
+                eventBus.publish(event);
+                this.eventBus.unregister(bean); //bean is now destroyed - it should not receive any other events
+            }
         }
+        //only now destroy the event bus:
+        LifecycleUtils.destroy(this.eventBus);
     }
 
     protected void createNewInstance(Map<String, Object> objects, String name, String value) {
@@ -740,6 +768,7 @@ public class ReflectionBuilder {
 
                 if (bd.isExecuted()) { //bean is fully configured, no more statements to execute for it:
 
+                    //bean configured overrides the 'eventBus' bean - replace the existing eventBus with the one configured:
                     if (bd.getBeanName().equals(EVENT_BUS_NAME)) {
                         EventBus eventBus = (EventBus)bd.getBean();
                         enableEvents(eventBus);
@@ -751,6 +780,16 @@ public class ReflectionBuilder {
                                 Collections.unmodifiableMap(objects));
                         eventBus.publish(event);
                     }
+
+                    //initialize the bean if necessary:
+                    LifecycleUtils.init(bd.getBean());
+
+                    //ignore global 'shiro.' shortcut mechanism:
+                    if (!bd.isGlobalConfig()) {
+                        BeanEvent event = new InitializedBeanEvent(bd.getBeanName(), bd.getBean(),
+                                Collections.unmodifiableMap(objects));
+                        eventBus.publish(event);
+                    }
                 }
             }
         }
@@ -884,11 +923,17 @@ public class ReflectionBuilder {
 
         @Override
         protected Object doExecute() {
-            createNewInstance(objects, this.lhs, this.rhs);
-            Object instantiated = objects.get(this.lhs);
+            String beanName = this.lhs;
+            createNewInstance(objects, beanName, this.rhs);
+            Object instantiated = objects.get(beanName);
             setBean(instantiated);
 
-            BeanEvent event = new InstantiatedBeanEvent(this.lhs, instantiated, Collections.unmodifiableMap(objects));
+            //also ensure the instantiated bean has access to the event bus or is subscribed to events if necessary:
+            //Note: because events are being enabled on this bean here (before the instantiated event below is
+            //triggered), beans can react to their own instantiation events.
+            enableEventsIfNecessary(instantiated, beanName);
+
+            BeanEvent event = new InstantiatedBeanEvent(beanName, instantiated, Collections.unmodifiableMap(objects));
             eventBus.publish(event);
 
             return instantiated;

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/BeanEvent.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/BeanEvent.java b/core/src/main/java/org/apache/shiro/config/event/BeanEvent.java
index 09e83fc..7c3c967 100644
--- a/core/src/main/java/org/apache/shiro/config/event/BeanEvent.java
+++ b/core/src/main/java/org/apache/shiro/config/event/BeanEvent.java
@@ -1,9 +1,31 @@
+/*
+ * 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.shiro.config.event;
 
-import java.util.EventObject;
+import org.apache.shiro.event.Event;
+
 import java.util.Map;
 
-public class BeanEvent extends EventObject {
+/**
+ * @since 1.3
+ */
+public abstract class BeanEvent extends Event {
 
     private String beanName;
     private Object bean;

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/ConfiguredBeanEvent.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/ConfiguredBeanEvent.java b/core/src/main/java/org/apache/shiro/config/event/ConfiguredBeanEvent.java
index 8d210a2..2636330 100644
--- a/core/src/main/java/org/apache/shiro/config/event/ConfiguredBeanEvent.java
+++ b/core/src/main/java/org/apache/shiro/config/event/ConfiguredBeanEvent.java
@@ -3,7 +3,14 @@ package org.apache.shiro.config.event;
 import java.util.Map;
 
 /**
+ * Event triggered when a configured bean has been instantiated and fully configured but right before the bean has been
+ * initialized.
+ *
  * @since 1.3
+ * @see InstantiatedBeanEvent
+ * @see org.apache.shiro.util.Initializable Initializable
+ * @see InitializedBeanEvent
+ * @see DestroyedBeanEvent
  */
 public class ConfiguredBeanEvent extends BeanEvent {
 

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/DestroyedBeanEvent.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/DestroyedBeanEvent.java b/core/src/main/java/org/apache/shiro/config/event/DestroyedBeanEvent.java
index f05ee7e..d040e6b 100644
--- a/core/src/main/java/org/apache/shiro/config/event/DestroyedBeanEvent.java
+++ b/core/src/main/java/org/apache/shiro/config/event/DestroyedBeanEvent.java
@@ -1,7 +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.shiro.config.event;
 
 import java.util.Map;
 
+/**
+ * Event triggered when a configured bean has been destroyed.
+ *
+ * @since 1.3
+ * @see org.apache.shiro.util.Destroyable Destroyable
+ * @see InstantiatedBeanEvent
+ * @see ConfiguredBeanEvent
+ * @see InitializedBeanEvent
+ */
 public class DestroyedBeanEvent extends BeanEvent {
 
     public DestroyedBeanEvent(final String beanName, final Object bean, final Map<String, Object> beanContext) {

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/InitializedBeanEvent.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/InitializedBeanEvent.java b/core/src/main/java/org/apache/shiro/config/event/InitializedBeanEvent.java
new file mode 100644
index 0000000..070d548
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/event/InitializedBeanEvent.java
@@ -0,0 +1,37 @@
+/*
+ * 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.shiro.config.event;
+
+import java.util.Map;
+
+/**
+ * Event triggered when a configured bean has been instantiated, fully configured and initialized.
+ *
+ * @since 1.3
+ * @see org.apache.shiro.util.Initializable Initializable
+ * @see InstantiatedBeanEvent
+ * @see ConfiguredBeanEvent
+ * @see DestroyedBeanEvent
+ */
+public class InitializedBeanEvent extends BeanEvent {
+
+    public InitializedBeanEvent(String beanName, Object bean, Map<String, Object> beanContext) {
+        super(beanName, bean, beanContext);
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/InstantiatedBeanEvent.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/InstantiatedBeanEvent.java b/core/src/main/java/org/apache/shiro/config/event/InstantiatedBeanEvent.java
index 4ba9d6e..991217f 100644
--- a/core/src/main/java/org/apache/shiro/config/event/InstantiatedBeanEvent.java
+++ b/core/src/main/java/org/apache/shiro/config/event/InstantiatedBeanEvent.java
@@ -1,7 +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.shiro.config.event;
 
 import java.util.Map;
 
+/**
+ * Event triggered when a configured bean has been instantiated but before it is configured or initialized.
+ *
+ * @since 1.3
+ * @see ConfiguredBeanEvent
+ * @see InitializedBeanEvent
+ * @see DestroyedBeanEvent
+ * @see org.apache.shiro.util.Initializable Initializable
+ */
 public class InstantiatedBeanEvent extends BeanEvent {
 
     public InstantiatedBeanEvent(final String beanName, final Object bean, final Map<String, Object> beanContext) {

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/LoggingBeanEventListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/LoggingBeanEventListener.java b/core/src/main/java/org/apache/shiro/config/event/LoggingBeanEventListener.java
new file mode 100644
index 0000000..eb1e8a1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/event/LoggingBeanEventListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.shiro.config.event;
+
+import org.apache.shiro.event.Subscribe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A stock bean listener implementation that logs all BeanEvents as TRACE log statements.
+ *
+ * @since 1.3
+ */
+public class LoggingBeanEventListener {
+
+    private static final Logger logger = LoggerFactory.getLogger(LoggingBeanEventListener.class);
+    private static final String SUFFIX = BeanEvent.class.getSimpleName();
+
+    @Subscribe
+    public void onEvent(BeanEvent e) {
+        String className = e.getClass().getSimpleName();
+        int i = className.lastIndexOf(SUFFIX);
+        String subclassPrefix = className.substring(0, i);
+        logger.trace("{} bean '{}' [{}]", new Object[]{subclassPrefix, e.getBeanName(), e.getBean()});
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/config/event/LoggingBeanListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/config/event/LoggingBeanListener.java b/core/src/main/java/org/apache/shiro/config/event/LoggingBeanListener.java
deleted file mode 100644
index a6b9bbe..0000000
--- a/core/src/main/java/org/apache/shiro/config/event/LoggingBeanListener.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.shiro.config.event;
-
-import org.apache.shiro.event.Subscribe;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A stock bean listener implementation that logs all BeanEvents via the standard logging mechanism.
- *
- * @since 1.3
- */
-public class LoggingBeanListener {
-
-    private static final Logger logger = LoggerFactory.getLogger(LoggingBeanListener.class);
-
-    @Subscribe
-    protected void onUnhandledBeanEvent(BeanEvent beanEvent) {
-        logger.warn("UNHANDLED EVENT :: {} :: {}", beanEvent.getBeanName(), beanEvent.getBean());
-    }
-
-    @Subscribe
-    protected void onInstantiatedBeanEvent(InstantiatedBeanEvent beanEvent) {
-        logger.info("INSTANTIATED :: {} :: {}", beanEvent.getBeanName(), beanEvent.getBean());
-    }
-
-    @Subscribe
-    protected void onConfiguredBeanEvent(ConfiguredBeanEvent beanEvent) {
-        logger.info("CONFIGURED :: {} :: {}", beanEvent.getBeanName(), beanEvent.getBean());
-    }
-
-    @Subscribe
-    protected void onDestroyedBeanEvent(DestroyedBeanEvent beanEvent) {
-        logger.info("DESTROYED :: {} :: {}", beanEvent.getBeanName(), beanEvent.getBean());
-    }
-}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/event/Event.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/Event.java b/core/src/main/java/org/apache/shiro/event/Event.java
new file mode 100644
index 0000000..8989796
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/Event.java
@@ -0,0 +1,27 @@
+package org.apache.shiro.event;
+
+import java.util.EventObject;
+
+/**
+ * Root class for all of Shiro's event classes.  Provides access to the timestamp when the event occurred.
+ *
+ * @since 1.3
+ */
+public abstract class Event extends EventObject {
+
+    private final long timestamp; //millis since Epoch (UTC time zone).
+
+    public Event(Object source) {
+        super(source);
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    /**
+     * Returns the timestamp when this event occurred as the number of milliseconds since Epoch (UTC time zone).
+     *
+     * @return the timestamp when this event occurred as the number of milliseconds since Epoch (UTC time zone).
+     */
+    public long getTimestamp() {
+        return this.timestamp;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/event/support/ClassComparator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/support/ClassComparator.java b/core/src/main/java/org/apache/shiro/event/support/ClassComparator.java
deleted file mode 100644
index 21db4ba..0000000
--- a/core/src/main/java/org/apache/shiro/event/support/ClassComparator.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.shiro.event.support;
-
-import java.util.Comparator;
-
-/**
- * Compares two classes based on their position in a hierarchy.  Classes higher up in a hierarchy are 'greater than'
- * (ordered later) than classes lower in a hierarchy (ordered earlier).  Classes in unrelated hierarchies have the same
- * order priority.
- * <p/>
- * Event bus implementations use this comparator to determine which event listener method to invoke when polymorphic
- * listener methods are defined:
- * <p/>
- * If two event classes exist A and B, where A is the parent class of B (and B is a subclass of A) and an event
- * subscriber listens to both events:
- * <pre>
- * &#64;Subscribe
- * public void onEvent(A a) { ... }
- *
- * &#64;Subscribe
- * public void onEvent(B b) { ... }
- * </pre>
- *
- * The {@code onEvent(B b)} method will be invoked on the subscriber and the
- * {@code onEvent(A a)} method will <em>not</em> be invoked.  This is to prevent multiple dispatching of a single event
- * to the same consumer.
- * <p/>
- * The ClassComparator is used to order listener method priority based on their event argument class - methods handling
- * event subclasses have higher precedence than superclasses.
- *
- * @since 1.3
- */
-public class ClassComparator implements Comparator<Class> {
-
-    public int compare(Class a, Class b) {
-        if (a == null) {
-            if (b == null) {
-                return 0;
-            } else {
-                return -1;
-            }
-        } else if (b == null) {
-            return 1;
-        } else if (a == b || a.equals(b)) {
-            return 0;
-        } else {
-            if (a.isAssignableFrom(b)) {
-                return 1;
-            } else if (b.isAssignableFrom(a)) {
-                return -1;
-            } else {
-                return 0;
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/event/support/DefaultEventBus.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/support/DefaultEventBus.java b/core/src/main/java/org/apache/shiro/event/support/DefaultEventBus.java
index e8520db..d4d5e8c 100644
--- a/core/src/main/java/org/apache/shiro/event/support/DefaultEventBus.java
+++ b/core/src/main/java/org/apache/shiro/event/support/DefaultEventBus.java
@@ -22,8 +22,15 @@ import org.apache.shiro.event.EventBus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * A default event bus implementation that synchronously publishes events to registered listeners.  Listeners can be
@@ -44,35 +51,62 @@ import java.util.concurrent.ConcurrentHashMap;
  * <h2>Receiving Events</h2>
  * A component can receive events of interest by doing the following.
  * <ol>
- *     <li>For each type of event you wish to consume, create a public method that accepts a single event argument.
- *     The method argument type indicates the type of event to receive.</li>
- *     <li>Annotate each of these public methods with the {@link org.apache.shiro.event.Subscribe Subscribe} annotation.</li>
- *     <li>Register the component with the event bus:
- *     <pre>
+ * <li>For each type of event you wish to consume, create a public method that accepts a single event argument.
+ * The method argument type indicates the type of event to receive.</li>
+ * <li>Annotate each of these public methods with the {@link org.apache.shiro.event.Subscribe Subscribe} annotation.</li>
+ * <li>Register the component with the event bus:
+ * <pre>
  *         eventBus.register(myComponent);
  *     </pre>
- *     </li>
+ * </li>
  * </ol>
  * After registering the component, when when an event of a respective type is published, the component's
  * {@code Subscribe}-annotated method(s) will be invoked as expected.
- * <p/>
+ *
  * This design (and its constituent helper components) was largely influenced by
  * Guava's <a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/eventbus/EventBus.html">EventBus</a>
  * concept, although no code was shared/imported (even though Guava is Apache 2.0 licensed and could have
  * been used).
  *
+ * This implementation is thread-safe and may be used concurrently.
+ *
  * @since 1.3
  */
 public class DefaultEventBus implements EventBus {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultEventBus.class);
 
+    private static final String EVENT_LISTENER_ERROR_MSG = "Event listener processing failed.  Listeners should " +
+            "generally handle exceptions directly and not propagate to the event bus.";
+
+    //this is stateless, we can retain a static final reference:
+    private static final EventListenerComparator EVENT_LISTENER_COMPARATOR = new EventListenerComparator();
+
     private EventListenerResolver eventListenerResolver;
 
-    private final Map<Object,Subscriber> registry;
+    //We want to preserve registration order to deliver events to objects in the order that they are registered
+    //with the event bus.  This has the nice effect that any Shiro system-level components that are registered first
+    //(likely to happen upon startup) have precedence over those registered by end-user components later.
+    //
+    //One might think that this could have been done by just using a ConcurrentSkipListMap (which is available only on
+    //JDK 6 or later).  However, this approach requires the implementation of a Comparator to sort elements, and this
+    //surfaces a problem: for any given random event listener, there isn't any guaranteed property to exist that can be
+    //inspected to determine order of registration, since registration order is an artifact of this EventBus
+    //implementation, not the listeners themselves.
+    //
+    //Therefore, we use a simple concurrent lock to wrap a LinkedHashMap - the LinkedHashMap retains insertion order
+    //and the lock provides thread-safety in probably a much simpler mechanism than attempting to write a
+    //EventBus-specific Comparator.  This technique is also likely to be faster than a ConcurrentSkipListMap, which
+    //is about 3-5 times slower than a standard ConcurrentMap.
+    private final Map<Object, Subscription> registry;
+    private final Lock registryReadLock;
+    private final Lock registryWriteLock;
 
     public DefaultEventBus() {
-        this.registry = new ConcurrentHashMap<Object, Subscriber>();
+        this.registry = new LinkedHashMap<Object, Subscription>(); //not thread safe, so we need locks:
+        ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
+        this.registryReadLock = rwl.readLock();
+        this.registryWriteLock = rwl.writeLock();
         this.eventListenerResolver = new AnnotationEventListenerResolver();
     }
 
@@ -90,14 +124,32 @@ public class DefaultEventBus implements EventBus {
             return;
         }
 
-        for( Subscriber subscriber : registry.values() ) {
-            subscriber.onEvent(event);
+        registryReadLock.lock();
+        try {
+            //performing the entire iteration within the lock will be a slow operation if the registry has a lot of
+            //contention.  However, it is expected that the very large majority of cases the registry will be
+            //read-mostly with very little writes (registrations or removals) occurring during a typical application
+            //lifetime.
+            //
+            //The alternative would be to copy the registry.values() collection to a new LinkedHashSet within the lock
+            //only and the iteration on this new collection could be outside the lock.  This has the performance penalty
+            //however of always creating a new collection every time an event is published,  which could be more
+            //costly for the majority of applications, especially if the number of listeners is large.
+            //
+            //Finally, the read lock is re-entrant, so multiple publish calls will be
+            //concurrent without penalty since publishing is a read-only operation on the registry.
+
+            for (Subscription subscription : this.registry.values()) {
+                subscription.onEvent(event);
+            }
+        } finally {
+            registryReadLock.unlock();
         }
     }
 
     public void register(Object instance) {
         if (instance == null) {
-            log.info("Received null instance for registration.  Ignoring registration request.");
+            log.info("Received null instance for event listener registration.  Ignoring registration request.");
             return;
         }
 
@@ -106,54 +158,60 @@ public class DefaultEventBus implements EventBus {
         List<EventListener> listeners = getEventListenerResolver().getEventListeners(instance);
 
         if (listeners == null || listeners.isEmpty()) {
-            log.warn("Unable to resolve any event listeners for the subscriber instance [" + instance +
-                    "].  Ignoring registration request.");
+            log.warn("Unable to resolve event listeners for subscriber instance [{}]. Ignoring registration request.",
+                    instance);
             return;
         }
 
-        Subscriber subscriber = new Subscriber(instance, listeners);
+        Subscription subscription = new Subscription(listeners);
 
-        this.registry.put(instance, subscriber);
+        this.registryWriteLock.lock();
+        try {
+            this.registry.put(instance, subscription);
+        } finally {
+            this.registryWriteLock.unlock();
+        }
     }
 
     public void unregister(Object instance) {
         if (instance == null) {
             return;
         }
-        this.registry.remove(instance);
+        this.registryWriteLock.lock();
+        try {
+            this.registry.remove(instance);
+        } finally {
+            this.registryWriteLock.unlock();
+        }
     }
 
-    private class Subscriber {
+    private class Subscription {
 
-        private final Object instance;
-        private final List<EventListener> registeredListeners;
+        private final List<EventListener> listeners;
 
-        public Subscriber(Object instance, List<EventListener> listeners) {
-            this.instance = instance;
+        public Subscription(List<EventListener> listeners) {
             List<EventListener> toSort = new ArrayList<EventListener>(listeners);
-            Collections.sort(toSort, new EventListenerComparator());
-            this.registeredListeners = toSort;
+            Collections.sort(toSort, EVENT_LISTENER_COMPARATOR);
+            this.listeners = toSort;
         }
 
         public void onEvent(Object event) {
 
             Set<Object> delivered = new HashSet<Object>();
 
-            for(EventListener listener : this.registeredListeners) {
+            for (EventListener listener : this.listeners) {
                 Object target = listener;
                 if (listener instanceof SingleArgumentMethodEventListener) {
-                    SingleArgumentMethodEventListener singleArgListener = (SingleArgumentMethodEventListener)listener;
+                    SingleArgumentMethodEventListener singleArgListener = (SingleArgumentMethodEventListener) listener;
                     target = singleArgListener.getTarget();
                 }
                 if (listener.accepts(event) && !delivered.contains(target)) {
                     try {
                         listener.onEvent(event);
                     } catch (Throwable t) {
-                        log.warn("Event listener processing failed.  Listeners should generally " +
-                                "handle exceptions directly and not propagate to the event bus.", t);
-                    } finally {
-                        delivered.add(target);
+                        log.warn(EVENT_LISTENER_ERROR_MSG, t);
                     }
+                    delivered.add(target);
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/event/support/EventClassComparator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/support/EventClassComparator.java b/core/src/main/java/org/apache/shiro/event/support/EventClassComparator.java
new file mode 100644
index 0000000..4a6ba71
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/support/EventClassComparator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.shiro.event.support;
+
+import java.util.Comparator;
+
+/**
+ * Compares two event classes based on their position in a class hierarchy.  Classes higher up in a hierarchy are
+ * 'greater than' (ordered later) than classes lower in a hierarchy (ordered earlier).  Classes in unrelated
+ * hierarchies have the same order priority.
+ * <p/>
+ * Event bus implementations use this comparator to determine which event listener method to invoke when polymorphic
+ * listener methods are defined:
+ * <p/>
+ * If two event classes exist A and B, where A is the parent class of B (and B is a subclass of A) and an event
+ * subscriber listens to both events:
+ * <pre>
+ * &#64;Subscribe
+ * public void onEvent(A a) { ... }
+ *
+ * &#64;Subscribe
+ * public void onEvent(B b) { ... }
+ * </pre>
+ *
+ * The {@code onEvent(B b)} method will be invoked on the subscriber and the
+ * {@code onEvent(A a)} method will <em>not</em> be invoked.  This is to prevent multiple dispatching of a single event
+ * to the same consumer.
+ * <p/>
+ * The EventClassComparator is used to order listener method priority based on their event argument class - methods
+ * handling event subclasses have higher precedence than superclasses.
+ *
+ * @since 1.3
+ */
+public class EventClassComparator implements Comparator<Class> {
+
+    @SuppressWarnings("unchecked")
+    public int compare(Class a, Class b) {
+        if (a == null) {
+            if (b == null) {
+                return 0;
+            } else {
+                return -1;
+            }
+        } else if (b == null) {
+            return 1;
+        } else if (a == b || a.equals(b)) {
+            return 0;
+        } else {
+            if (a.isAssignableFrom(b)) {
+                return 1;
+            } else if (b.isAssignableFrom(a)) {
+                return -1;
+            } else {
+                return 0;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/event/support/EventListenerComparator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/support/EventListenerComparator.java b/core/src/main/java/org/apache/shiro/event/support/EventListenerComparator.java
index 817c805..92bb595 100644
--- a/core/src/main/java/org/apache/shiro/event/support/EventListenerComparator.java
+++ b/core/src/main/java/org/apache/shiro/event/support/EventListenerComparator.java
@@ -29,13 +29,16 @@ import java.util.Comparator;
  * EventListener instances have the same order priority.
  * <p/>
  * When both objects being compared are TypedEventListeners, they are ordered according to the rules of the
- * {@link ClassComparator}, using the TypedEventListeners'
+ * {@link EventClassComparator}, using the TypedEventListeners'
  * {@link TypedEventListener#getEventType() eventType}.
  *
  * @since 1.3
  */
 public class EventListenerComparator implements Comparator<EventListener> {
 
+    //event class comparator is stateless, so we can retain an instance:
+    private static final EventClassComparator EVENT_CLASS_COMPARATOR = new EventClassComparator();
+
     public int compare(EventListener a, EventListener b) {
         if (a == null) {
             if (b == null) {
@@ -52,7 +55,7 @@ public class EventListenerComparator implements Comparator<EventListener> {
                 TypedEventListener ta = (TypedEventListener)a;
                 if (b instanceof TypedEventListener) {
                     TypedEventListener tb = (TypedEventListener)b;
-                    return new ClassComparator().compare(ta.getEventType(), tb.getEventType());
+                    return EVENT_CLASS_COMPARATOR.compare(ta.getEventType(), tb.getEventType());
                 } else {
                     return -1; //TypedEventListeners are 'less than' (higher priority) than non typed
                 }

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/event/support/SingleArgumentMethodEventListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/support/SingleArgumentMethodEventListener.java b/core/src/main/java/org/apache/shiro/event/support/SingleArgumentMethodEventListener.java
index f30c11a..9475a7e 100644
--- a/core/src/main/java/org/apache/shiro/event/support/SingleArgumentMethodEventListener.java
+++ b/core/src/main/java/org/apache/shiro/event/support/SingleArgumentMethodEventListener.java
@@ -19,6 +19,7 @@
 package org.apache.shiro.event.support;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 
 /**
  * A event listener that invokes a target object's method that accepts a single event argument.
@@ -35,6 +36,8 @@ public class SingleArgumentMethodEventListener implements TypedEventListener {
         this.method = method;
         //assert that the method is defined as expected:
         getMethodArgumentType(method);
+
+        assertPublicMethod(method);
     }
 
     public Object getTarget() {
@@ -45,6 +48,13 @@ public class SingleArgumentMethodEventListener implements TypedEventListener {
         return this.method;
     }
 
+    private void assertPublicMethod(Method method) {
+        int modifiers = method.getModifiers();
+        if (!Modifier.isPublic(modifiers)) {
+            throw new IllegalArgumentException("Event handler method [" + method + "] must be public.");
+        }
+    }
+
     public boolean accepts(Object event) {
         return event != null && getEventType().isInstance(event);
     }
@@ -58,7 +68,7 @@ public class SingleArgumentMethodEventListener implements TypedEventListener {
         try {
             method.invoke(getTarget(), event);
         } catch (Exception e) {
-            throw new IllegalStateException("Unable to invoke event handler method [" + method + "]", e);
+            throw new IllegalStateException("Unable to invoke event handler method [" + method + "].", e);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
index 9cfaae9..1001dd8 100644
--- a/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
+++ b/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
@@ -89,12 +89,13 @@ public abstract class CachingSecurityManager implements SecurityManager, Destroy
      * {@link #getCacheManager getCacheManager()} method.
      */
     protected void afterCacheManagerSet() {
+        applyEventBusToCacheManager();
     }
 
     /**
-     * Returns the {@code EventBus} used by this Securitymanager and potentially any of its children components.
+     * Returns the {@code EventBus} used by this SecurityManager and potentially any of its children components.
      *
-     * @return the {@code EventBus} used by this Securitymanager and potentially any of its children components.
+     * @return the {@code EventBus} used by this SecurityManager and potentially any of its children components.
      * @since 1.3
      */
     public EventBus getEventBus() {
@@ -119,12 +120,22 @@ public abstract class CachingSecurityManager implements SecurityManager, Destroy
     }
 
     /**
+     * @since 1.3
+     */
+    protected void applyEventBusToCacheManager() {
+        if (this.eventBus != null && this.cacheManager != null && this.cacheManager instanceof EventBusAware) {
+            ((EventBusAware)this.cacheManager).setEventBus(this.eventBus);
+        }
+    }
+
+    /**
      * Template callback to notify subclasses that an {@link EventBus EventBus} has been set and is available for use
      * via the {@link #getEventBus() getEventBus()} method.
      *
      * @since 1.3
      */
     protected void afterEventBusSet() {
+        applyEventBusToCacheManager();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java
index b63672b..f5392e2 100644
--- a/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java
+++ b/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java
@@ -20,6 +20,8 @@ package org.apache.shiro.mgt;
 
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.event.EventBus;
+import org.apache.shiro.event.EventBusAware;
 import org.apache.shiro.realm.Realm;
 import org.apache.shiro.util.LifecycleUtils;
 
@@ -83,6 +85,7 @@ public abstract class RealmSecurityManager extends CachingSecurityManager {
 
     protected void afterRealmsSet() {
         applyCacheManagerToRealms();
+        applyEventBusToRealms();
     }
 
     /**
@@ -119,14 +122,47 @@ public abstract class RealmSecurityManager extends CachingSecurityManager {
     }
 
     /**
+     * Sets the internal {@link #getEventBus  EventBus} on any internal configured
+     * {@link #getRealms Realms} that implement the {@link EventBusAware} interface.
+     * <p/>
+     * This method is called after setting an eventBus on this securityManager via the
+     * {@link #setEventBus(org.apache.shiro.event.EventBus) setEventBus} method to allow it to be propagated
+     * down to all the internal Realms that would need to use it.
+     * <p/>
+     * It is also called after setting one or more realms via the {@link #setRealm setRealm} or
+     * {@link #setRealms setRealms} methods to allow these newly available realms to be given the EventBus
+     * already in use.
+     *
+     * @since 1.3
+     */
+    protected void applyEventBusToRealms() {
+        EventBus eventBus = getEventBus();
+        Collection<Realm> realms = getRealms();
+        if (eventBus != null && realms != null && !realms.isEmpty()) {
+            for(Realm realm : realms) {
+                if (realm instanceof EventBusAware) {
+                    ((EventBusAware)realm).setEventBus(eventBus);
+                }
+            }
+        }
+    }
+
+    /**
      * Simply calls {@link #applyCacheManagerToRealms() applyCacheManagerToRealms()} to allow the
      * newly set {@link org.apache.shiro.cache.CacheManager CacheManager} to be propagated to the internal collection of <code>Realm</code>
      * that would need to use it.
      */
     protected void afterCacheManagerSet() {
+        super.afterCacheManagerSet();
         applyCacheManagerToRealms();
     }
 
+    @Override
+    protected void afterEventBusSet() {
+        super.afterEventBusSet();
+        applyEventBusToRealms();
+    }
+
     public void destroy() {
         LifecycleUtils.destroy(getRealms());
         this.realms = null;

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java
index 05a7dec..eb00b56 100644
--- a/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java
+++ b/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java
@@ -20,6 +20,8 @@ package org.apache.shiro.mgt;
 
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.event.EventBus;
+import org.apache.shiro.event.EventBusAware;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.SessionException;
 import org.apache.shiro.session.mgt.DefaultSessionManager;
@@ -82,6 +84,7 @@ public abstract class SessionsSecurityManager extends AuthorizingSecurityManager
 
     protected void afterSessionManagerSet() {
         applyCacheManagerToSessionManager();
+        applyEventBusToSessionManager();
     }
 
     /**
@@ -99,12 +102,24 @@ public abstract class SessionsSecurityManager extends AuthorizingSecurityManager
      * {@link #applyCacheManagerToSessionManager() applyCacheManagerToSessionManager()} to ensure the
      * <code>CacheManager</code> is applied to the SessionManager as necessary.
      */
+    @Override
     protected void afterCacheManagerSet() {
         super.afterCacheManagerSet();
         applyCacheManagerToSessionManager();
     }
 
     /**
+     * Sets any configured EventBus on the SessionManager if necessary.
+     *
+     * @since 1.3
+     */
+    @Override
+    protected void afterEventBusSet() {
+        super.afterEventBusSet();
+        applyEventBusToSessionManager();
+    }
+
+    /**
      * Ensures the internal delegate <code>SessionManager</code> is injected with the newly set
      * {@link #setCacheManager CacheManager} so it may use it for its internal caching needs.
      * <p/>
@@ -117,6 +132,22 @@ public abstract class SessionsSecurityManager extends AuthorizingSecurityManager
         }
     }
 
+    /**
+     * Ensures the internal delegate <code>SessionManager</code> is injected with the newly set
+     * {@link #setEventBus EventBus} so it may use it for its internal event needs.
+     * <p/>
+     * Note: This implementation only injects the EventBus into the SessionManager if the SessionManager
+     * instance implements the {@link EventBusAware EventBusAware} interface.
+     *
+     * @since 1.3
+     */
+    protected void applyEventBusToSessionManager() {
+        EventBus eventBus = getEventBus();
+        if (eventBus != null && this.sessionManager instanceof EventBusAware) {
+            ((EventBusAware)this.sessionManager).setEventBus(eventBus);
+        }
+    }
+
     public Session start(SessionContext context) throws AuthorizationException {
         return this.sessionManager.start(context);
     }

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
index e3826d0..86353bd 100644
--- a/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
+++ b/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
@@ -19,7 +19,13 @@
 package org.apache.shiro.session.mgt;
 
 import org.apache.shiro.authz.AuthorizationException;
-import org.apache.shiro.session.*;
+import org.apache.shiro.event.EventBus;
+import org.apache.shiro.event.EventBusAware;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
+import org.apache.shiro.session.SessionListener;
+import org.apache.shiro.session.UnknownSessionException;
 import org.apache.shiro.util.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,10 +42,12 @@ import java.util.Date;
  *
  * @since 1.0
  */
-public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager {
+public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware {
 
     private static final Logger log = LoggerFactory.getLogger(AbstractSessionManager.class);
 
+    private EventBus eventBus;
+
     private Collection<SessionListener> listeners;
 
     public AbstractNativeSessionManager() {
@@ -55,6 +63,38 @@ public abstract class AbstractNativeSessionManager extends AbstractSessionManage
         return this.listeners;
     }
 
+    /**
+     * Returns the EventBus used to publish SessionEvents.
+     *
+     * @return the EventBus used to publish SessionEvents.
+     * @since 1.3
+     */
+    protected EventBus getEventBus() {
+        return eventBus;
+    }
+
+    /**
+     * Sets the EventBus to use to publish SessionEvents.
+     *
+     * @param eventBus the EventBus to use to publish SessionEvents.
+     * @since 1.3
+     */
+    public void setEventBus(EventBus eventBus) {
+        this.eventBus = eventBus;
+    }
+
+    /**
+     * Publishes events on the event bus if the event bus is non-null, otherwise does nothing.
+     *
+     * @param event the event to publish on the event bus if the event bus exists.
+     * @since 1.3
+     */
+    protected void publishEvent(Object event) {
+        if (this.eventBus != null) {
+            this.eventBus.publish(event);
+        }
+    }
+
     public Session start(SessionContext context) {
         Session session = createSession(context);
         applyGlobalSessionTimeout(session);

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy b/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
index e2ed22e..278efa2 100644
--- a/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
+++ b/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
@@ -24,12 +24,17 @@ import org.apache.shiro.codec.Hex
 import org.apache.shiro.config.event.BeanEvent
 import org.apache.shiro.realm.ldap.JndiLdapRealm
 import org.apache.shiro.util.CollectionUtils
+import org.junit.Test
+
+import static org.junit.Assert.*
 
 /**
  * Unit tests for the {@link ReflectionBuilder} implementation.
  */
-class ReflectionBuilderTest extends GroovyTestCase {
+@SuppressWarnings("GrMethodMayBeStatic")
+class ReflectionBuilderTest {
 
+    @Test
     void testStandardPropertyAssignment() {
         ReflectionBuilder builder = new ReflectionBuilder();
 
@@ -44,7 +49,8 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertTrue cBean.intProp == 42
         assertTrue cBean.simpleBean instanceof SimpleBean
     }
-    
+
+    @Test
     void testMapEntryAssignment() {
         ReflectionBuilder builder = new ReflectionBuilder();
 
@@ -56,6 +62,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertTrue cBean.simpleBeanMap['simpleBean2'] instanceof SimpleBean
     }
 
+    @Test
     void testArrayEntryAssignment() {
         ReflectionBuilder builder = new ReflectionBuilder();
 
@@ -67,6 +74,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertTrue cBean.compositeBeanArray[0] instanceof CompositeBean
     }
 
+    @Test
     void testNestedPathAssignment() {
         ReflectionBuilder builder = new ReflectionBuilder();
 
@@ -82,6 +90,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
     }
 
     //asserts SHIRO-305: https://issues.apache.org/jira/browse/SHIRO-305
+    @Test
     void testNestedMapAssignmentWithPeriodDelimitedKeys() {
         def ini = new Ini()
         ini.load('''
@@ -100,6 +109,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals 'plain ssl', ldapRealm.contextFactory.environment['com.sun.jndi.ldap.connect.pool.protocol']
     }
 
+    @Test
     void testSimpleConfig() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
@@ -117,6 +127,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(compositeBean.getIntProp(), 42);
     }
 
+    @Test
     void testWithConfiguredNullValue() {
         Map<String,Object> defaults = new LinkedHashMap<String,Object>();
         CompositeBean cBean = new CompositeBean();
@@ -140,6 +151,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertNull(compositeBean.getSimpleBean());
     }
 
+    @Test
     void testWithConfiguredNullLiteralValue() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
@@ -157,6 +169,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals("null", compositeBean.getStringProp());
     }
 
+    @Test
     void testWithConfiguredEmptyStringValue() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
@@ -174,6 +187,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals("", compositeBean.getStringProp());
     }
 
+    @Test
     void testWithConfiguredEmptyStringLiteralValue() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
@@ -191,6 +205,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals("\"\"", compositeBean.getStringProp());
     }
 
+    @Test
     void testSimpleConfigWithDollarSignStringValue() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
@@ -203,6 +218,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(compositeBean.getStringProp(), '$500');
     }
 
+    @Test
     void testObjectReferenceConfig() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
@@ -224,32 +240,27 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(simpleBean.getIntProp(), 101);
     }
 
+    @Test(expected=ConfigurationException)
     void testObjectReferenceConfigWithTypeMismatch() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
         defs.put("compositeBean.simpleBean", "simpleBean");
         ReflectionBuilder builder = new ReflectionBuilder();
-        try {
-            builder.buildObjects(defs);
-            "Should have encountered an " + ConfigurationException.class.name
-        } catch (ConfigurationException expected) {
-        }
+        builder.buildObjects(defs);
     }
 
+    @Test(expected=UnresolveableReferenceException)
     void testObjectReferenceConfigWithInvalidReference() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
         defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
         defs.put("compositeBean.simpleBean", '$foo');
         ReflectionBuilder builder = new ReflectionBuilder();
-        try {
-            builder.buildObjects(defs);
-            fail "should have encountered an " + UnresolveableReferenceException.class.name
-        } catch (UnresolveableReferenceException expected) {
-        }
+        builder.buildObjects(defs);
     }
 
+    @Test
     void testSetProperty() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
@@ -266,6 +277,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(2, simpleBeans.size());
     }
 
+    @Test
     void testListProperty() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
@@ -282,6 +294,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(3, simpleBeans.size());
     }
 
+    @Test
     void testCollectionProperty() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
@@ -299,6 +312,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(3, simpleBeans.size());
     }
 
+    @Test
     void testByteArrayHexProperty() {
         String source = "Hello, world.";
         byte[] bytes = CodecSupport.toBytes(source);
@@ -319,6 +333,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(source, reconstituted);
     }
 
+    @Test
     void testByteArrayBase64Property() {
         String source = "Hello, world.";
         byte[] bytes = CodecSupport.toBytes(source);
@@ -337,6 +352,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(reconstituted, source);
     }
 
+    @Test
     void testMapProperty() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
@@ -357,6 +373,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertTrue(value instanceof SimpleBean);
     }
 
+    @Test
     void testNestedListProperty() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
@@ -377,6 +394,7 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals(2, children.size());
     }
 
+    @Test
     void testFactoryInstantiation() {
         Map<String, String> defs = new LinkedHashMap<String, String>();
         defs.put("simpleBeanFactory", "org.apache.shiro.config.SimpleBeanFactory");
@@ -395,77 +413,84 @@ class ReflectionBuilderTest extends GroovyTestCase {
         assertEquals("someString", bean.getStringProp());
     }
 
+    @Test
     void testBeanListeners() {
 
-        //currently commented out pending Mailing List discussion:
-        /*
-        Map<String, String> defs = new LinkedHashMap<String, String>();
-        defs.put("listenerOne", RecordingBeanListener.class.getName());
-        defs.put("listenerTwo", RecordingBeanListener.class.getName());
+        def ini = new Ini();
+        ini.load '''
+            loggingListener = org.apache.shiro.config.event.LoggingBeanEventListener
+            listenerOne = org.apache.shiro.config.RecordingBeanListener
+            listenerTwo = org.apache.shiro.config.RecordingBeanListener
 
-        defs.put("simpleBeanFactory", "org.apache.shiro.config.SimpleBeanFactory");
-        defs.put("simpleBeanFactory.factoryInt", "5");
-        defs.put("simpleBeanFactory.factoryString", "someString");
-        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
-        defs.put("compositeBean.simpleBean", '$simpleBeanFactory');
+            simpleBeanFactory = org.apache.shiro.config.SimpleBeanFactory
+            simpleBeanFactory.factoryInt = 5
+            simpleBeanFactory.factoryString = someString
+
+            compositeBean = org.apache.shiro.config.CompositeBean
+            compositeBean.simpleBean = $simpleBeanFactory
+        '''
 
         ReflectionBuilder builder = new ReflectionBuilder();
-        Map<String, ?> objects = builder.buildObjects(defs);
+        Map<String, ?> objects = builder.buildObjects(ini.getSections().iterator().next());
         assertFalse(CollectionUtils.isEmpty(objects));
 
-        assertInstantiatedEvents("listenerOne", objects);
-        assertConfiguredEvents("listenerOne", objects, true);
-        assertInstantiatedEvents("listenerTwo", objects);
-        assertConfiguredEvents("listenerTwo", objects, false);
+        assertInstantiatedEvents("listenerOne", objects, 4) //3 beans following + its own instantiated event
+        assertConfiguredEvents("listenerOne", objects, 4) //3 beans following + its own configured event
+        assertInitializedEvents("listenerOne", objects, 4) //3 beans following + its own initialized event
+
+        assertInstantiatedEvents("listenerTwo", objects, 3) //2 beans following + its own instantiated event
+        assertConfiguredEvents("listenerTwo", objects, 3); //2 beans following + its own configured event
+        assertInitializedEvents("listenerTwo", objects, 3); //2 beans following + its own initialized event
 
         builder.destroy();
 
-        assertDestroyedEvents("listenerOne", objects);
-        assertDestroyedEvents("listenerTwo", objects);
-        */
+        assertDestroyedEvents("listenerOne", objects, 4); //3 beans defined after it + its own destroyed event
+        assertDestroyedEvents("listenerTwo", objects, 3); //2 beans defined after it + its own destroyed event
     }
 
-    void assertInstantiatedEvents(String name, Map<String, ?> objects) {
-        Object bean = objects.get(name);
-        assertTrue("Bean " + name + " is not a " + RecordingBeanListener.class.getSimpleName(),
-                bean instanceof RecordingBeanListener);
-        List<BeanEvent> instantiatedEvents = bean.getInstantiateEvents();
-        assertEquals(2, instantiatedEvents.size())
+    void assertInstantiatedEvents(String name, Map<String, ?> objects, int expected) {
+        def bean = objects.get(name) as RecordingBeanListener
+        def events = bean.getInstantiatedEvents()
+        assertEquals(expected, events.size())
+
+        checkType(name, events, "simpleBeanFactory", SimpleBeanFactory);
+        checkType(name, events, "compositeBean", CompositeBean);
+    }
 
-        checkType(name, instantiatedEvents, "simpleBeanFactory", SimpleBeanFactory.class);
-        checkType(name, instantiatedEvents, "compositeBean", CompositeBean.class);
+    void assertConfiguredEvents(String name, Map<String, ?> objects, int expected) {
+        def bean = objects.get(name) as RecordingBeanListener
+        def events = bean.getConfiguredEvents();
+        assertEquals(expected, events.size())
 
-        // instantiate notifications do not occur for listeners
+        checkType(name, events, "listenerTwo", RecordingBeanListener);
+        checkType(name, events, "simpleBeanFactory", SimpleBeanFactory);
+        checkType(name, events, "compositeBean", CompositeBean);
     }
 
-    void assertConfiguredEvents(String name, Map<String, ?> objects, boolean includeListener) {
-        Object bean = objects.get(name);
-        assertTrue("Bean " + name + " is not a " + RecordingBeanListener.class.getSimpleName(),
-                bean instanceof RecordingBeanListener);
-        List<BeanEvent> configuredEvents = bean.getConfiguredEvents();
-        assertEquals(includeListener ? 3 : 2, configuredEvents.size())
-
-        checkType(name, configuredEvents, "simpleBeanFactory", SimpleBeanFactory.class);
-        checkType(name, configuredEvents, "compositeBean", CompositeBean.class);
-        if(includeListener) {
-            checkType(name, configuredEvents, "listenerTwo", RecordingBeanListener.class);
-        }
+    void assertInitializedEvents(String name, Map<String, ?> objects, int expected) {
+        def bean = objects.get(name) as RecordingBeanListener
+        def events = bean.getInitializedEvents();
+        assertEquals(expected, events.size())
+
+        checkType(name, events, "listenerTwo", RecordingBeanListener);
+        checkType(name, events, "simpleBeanFactory", SimpleBeanFactory);
+        checkType(name, events, "compositeBean", CompositeBean);
     }
 
-    void assertDestroyedEvents(String name, Map<String, ?> objects) {
-        Object bean = objects.get(name);
-        assertTrue("Bean " + name + " is not a " + RecordingBeanListener.class.getSimpleName(),
-                bean instanceof RecordingBeanListener);
-        List<BeanEvent> configuredEvents = bean.getDestroyedEvents();
-        assertEquals(4, configuredEvents.size())
-
-        checkType(name, configuredEvents, "simpleBeanFactory", SimpleBeanFactory.class);
-        checkType(name, configuredEvents, "compositeBean", CompositeBean.class);
-        checkType(name, configuredEvents, "listenerOne", RecordingBeanListener.class);
-        checkType(name, configuredEvents, "listenerTwo", RecordingBeanListener.class);
+    void assertDestroyedEvents(String name, Map<String, ?> objects, int expected) {
+        def bean = objects.get(name) as RecordingBeanListener
+        def events = bean.getDestroyedEvents();
+        assertEquals(expected, events.size())
+
+        if (expected > 3) {
+            checkType(name, events, "listenerOne", RecordingBeanListener);
+        }
+        checkType(name, events, "listenerTwo", RecordingBeanListener);
+        checkType(name, events, "simpleBeanFactory", SimpleBeanFactory);
+        checkType(name, events, "compositeBean", CompositeBean);
     }
 
-    void checkType(String instanceName, List<BeanEvent> events, String name, Class<?> expectedType) {
+    void checkType(String instanceName, List<? extends BeanEvent> events, String name, Class<?> expectedType) {
         for(BeanEvent event: events) {
             if(event.getBeanName().equals(name)) {
                 assertTrue("Notification for bean " + name + " did not provide an instance of " + expectedType

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/config/event/BeanEventTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/config/event/BeanEventTest.groovy b/core/src/test/groovy/org/apache/shiro/config/event/BeanEventTest.groovy
new file mode 100644
index 0000000..f0ca32c
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/config/event/BeanEventTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.shiro.config.event
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertSame
+
+/**
+ * @since 1.3
+ */
+class BeanEventTest {
+
+    @Test
+    void testDefault() {
+
+        def m = [foo: 'bar'] as Map<String,Object>
+        Object o = new Object()
+        BeanEvent evt = new MyBeanEvent('baz', o, m)
+
+        assertEquals 'baz', evt.beanName
+        assertSame o, evt.bean
+        assertSame m, evt.beanContext
+    }
+
+    private class MyBeanEvent extends BeanEvent {
+        MyBeanEvent(String beanName, Object bean, Map<String, Object> beanContext) {
+            super(beanName, bean, beanContext)
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/event/EventTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/EventTest.groovy b/core/src/test/groovy/org/apache/shiro/event/EventTest.groovy
new file mode 100644
index 0000000..5f2282b
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/EventTest.groovy
@@ -0,0 +1,30 @@
+package org.apache.shiro.event
+
+import org.junit.Test
+
+import static org.junit.Assert.assertSame
+import static org.junit.Assert.assertTrue
+
+/**
+ * @since 1.3
+ */
+class EventTest {
+
+    @Test
+    void testDefault() {
+        Object source = new Object()
+        long start = System.currentTimeMillis()
+        Event e = new DummyEvent(source)
+        long stop = System.currentTimeMillis()
+
+        assertSame source, e.source
+        assertTrue start <= e.timestamp
+        assertTrue stop >= e.timestamp
+    }
+
+    private class DummyEvent extends Event {
+        DummyEvent(Object source) {
+            super(source)
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/event/support/ClassComparatorTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/support/ClassComparatorTest.groovy b/core/src/test/groovy/org/apache/shiro/event/support/ClassComparatorTest.groovy
deleted file mode 100644
index 1adb4f5..0000000
--- a/core/src/test/groovy/org/apache/shiro/event/support/ClassComparatorTest.groovy
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.shiro.event.support
-
-/**
- * @since 1.3
- */
-class ClassComparatorTest extends GroovyTestCase {
-
-    ClassComparator comparator
-
-    @Override
-    protected void setUp() {
-        comparator = new ClassComparator()
-    }
-
-    void testANull() {
-        def result = comparator.compare(null, Object)
-        assertEquals(-1, result)
-    }
-
-    void testBNull() {
-        def result = comparator.compare(Object, null)
-        assertEquals 1, result
-    }
-
-    void testBothNull() {
-        assertEquals 0, comparator.compare(null, null)
-    }
-
-    void testBothSame() {
-        assertEquals 0, comparator.compare(Object, Object)
-    }
-
-    void testAParentOfB() {
-        assertEquals 1, comparator.compare(Number, Integer)
-    }
-
-    void testBParentOfA() {
-        assertEquals(-1, comparator.compare(Integer, Number))
-    }
-
-    void testUnrelated() {
-        assertEquals(0, comparator.compare(Integer, Boolean))
-    }
-}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/event/support/DefaultEventBusTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/support/DefaultEventBusTest.groovy b/core/src/test/groovy/org/apache/shiro/event/support/DefaultEventBusTest.groovy
index 1f8d688..9418b21 100644
--- a/core/src/test/groovy/org/apache/shiro/event/support/DefaultEventBusTest.groovy
+++ b/core/src/test/groovy/org/apache/shiro/event/support/DefaultEventBusTest.groovy
@@ -76,6 +76,18 @@ class DefaultEventBusTest extends GroovyTestCase {
         verify(resolver)
     }
 
+    void testSubscribeWithNullResolvedListeners() {
+        def resolver = new EventListenerResolver() {
+            List<EventListener> getEventListeners(Object instance) {
+                return null //dummy implementation
+            }
+        }
+        bus.setEventListenerResolver(resolver)
+        def subscriber = new NotAnnotatedSubscriber()
+        bus.register(subscriber);
+        assertEquals 0, bus.registry.size()
+    }
+
     void testSubscribeWithoutAnnotations() {
         def subscriber = new NotAnnotatedSubscriber()
         bus.register(subscriber)

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/event/support/EventClassComparatorTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/support/EventClassComparatorTest.groovy b/core/src/test/groovy/org/apache/shiro/event/support/EventClassComparatorTest.groovy
new file mode 100644
index 0000000..d39e3c1
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/support/EventClassComparatorTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * 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.shiro.event.support
+
+/**
+ * Tests for the {@link EventClassComparator} implementation.
+ *
+ * @since 1.3
+ */
+class EventClassComparatorTest extends GroovyTestCase {
+
+    EventClassComparator comparator
+
+    @Override
+    protected void setUp() {
+        comparator = new EventClassComparator()
+    }
+
+    void testANull() {
+        def result = comparator.compare(null, Object)
+        assertEquals(-1, result)
+    }
+
+    void testBNull() {
+        def result = comparator.compare(Object, null)
+        assertEquals 1, result
+    }
+
+    void testBothNull() {
+        assertEquals 0, comparator.compare(null, null)
+    }
+
+    void testBothSame() {
+        assertEquals 0, comparator.compare(Object, Object)
+    }
+
+    void testAParentOfB() {
+        assertEquals 1, comparator.compare(Number, Integer)
+    }
+
+    void testBParentOfA() {
+        assertEquals(-1, comparator.compare(Integer, Number))
+    }
+
+    void testUnrelated() {
+        assertEquals(0, comparator.compare(Integer, Boolean))
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/event/support/InvalidMethodModiferSubscriber.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/support/InvalidMethodModiferSubscriber.groovy b/core/src/test/groovy/org/apache/shiro/event/support/InvalidMethodModiferSubscriber.groovy
new file mode 100644
index 0000000..3751669
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/support/InvalidMethodModiferSubscriber.groovy
@@ -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.shiro.event.support
+
+import org.apache.shiro.event.Subscribe
+
+/**
+ * @since 1.3
+ */
+class InvalidMethodModiferSubscriber {
+
+    @Subscribe
+    protected void onEvent(Object event) {
+        throw new IllegalStateException("Should never be called.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/groovy/org/apache/shiro/event/support/SingleArgumentMethodEventListenerTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/support/SingleArgumentMethodEventListenerTest.groovy b/core/src/test/groovy/org/apache/shiro/event/support/SingleArgumentMethodEventListenerTest.groovy
index 56ef608..aa8798a 100644
--- a/core/src/test/groovy/org/apache/shiro/event/support/SingleArgumentMethodEventListenerTest.groovy
+++ b/core/src/test/groovy/org/apache/shiro/event/support/SingleArgumentMethodEventListenerTest.groovy
@@ -18,13 +18,18 @@
  */
 package org.apache.shiro.event.support
 
+import org.junit.Test
+
 import java.lang.reflect.Method
 
+import static org.junit.Assert.*
+
 /**
  * @since 1.3
  */
-class SingleArgumentMethodEventListenerTest extends GroovyTestCase {
+class SingleArgumentMethodEventListenerTest {
 
+    @Test
     void testInvalidConstruction() {
 
         def target = new Object()
@@ -40,6 +45,7 @@ class SingleArgumentMethodEventListenerTest extends GroovyTestCase {
         }
     }
 
+    @Test
     void testValidConstruction() {
 
         def target = new TestSubscriber()
@@ -51,6 +57,7 @@ class SingleArgumentMethodEventListenerTest extends GroovyTestCase {
         assertSame method, listener.getMethod()
     }
 
+    @Test
     void testMethodException() {
 
         def target = new TestSubscriber()
@@ -74,6 +81,7 @@ class SingleArgumentMethodEventListenerTest extends GroovyTestCase {
         }
     }
 
+    @Test
     void testAccepts() {
         def target = new TestSubscriber()
         def method = TestSubscriber.class.getMethods().find { it.name == "onFooEvent" }
@@ -83,4 +91,14 @@ class SingleArgumentMethodEventListenerTest extends GroovyTestCase {
         assertTrue listener.accepts(new FooEvent(this))
     }
 
+    @Test(expected=IllegalArgumentException)
+    void testNonPublicMethodSubscriber() {
+        def target = new InvalidMethodModiferSubscriber()
+        def method = InvalidMethodModiferSubscriber.class.getDeclaredMethods().find { it.name == "onEvent" }
+
+        new SingleArgumentMethodEventListener(target, method)
+    }
+
+
+
 }

http://git-wip-us.apache.org/repos/asf/shiro/blob/4929b4a4/core/src/test/java/org/apache/shiro/config/RecordingBeanListener.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/shiro/config/RecordingBeanListener.java b/core/src/test/java/org/apache/shiro/config/RecordingBeanListener.java
index b09d124..54eb3f7 100644
--- a/core/src/test/java/org/apache/shiro/config/RecordingBeanListener.java
+++ b/core/src/test/java/org/apache/shiro/config/RecordingBeanListener.java
@@ -21,6 +21,7 @@ package org.apache.shiro.config;
 import org.apache.shiro.config.event.BeanEvent;
 import org.apache.shiro.config.event.ConfiguredBeanEvent;
 import org.apache.shiro.config.event.DestroyedBeanEvent;
+import org.apache.shiro.config.event.InitializedBeanEvent;
 import org.apache.shiro.config.event.InstantiatedBeanEvent;
 import org.apache.shiro.event.Subscribe;
 
@@ -34,30 +35,41 @@ public class RecordingBeanListener {
 
     private List<InstantiatedBeanEvent> instantiateEvents = new ArrayList<InstantiatedBeanEvent>();
     private List<ConfiguredBeanEvent> configuredEvents = new ArrayList<ConfiguredBeanEvent>();
+    private List<InitializedBeanEvent> initializedEvents = new ArrayList<InitializedBeanEvent>();
     private List<DestroyedBeanEvent> destroyedEvents = new ArrayList<DestroyedBeanEvent>();
     private List<BeanEvent> unhandledEvents = new ArrayList<BeanEvent>();
 
+    @SuppressWarnings("UnusedDeclaration") //used via reflection
     @Subscribe
-    protected void onUnhandledBeanEvent(BeanEvent beanEvent) {
+    public void onUnhandledBeanEvent(BeanEvent beanEvent) {
         this.unhandledEvents.add(beanEvent);
     }
 
+    @SuppressWarnings("UnusedDeclaration") //used via reflection
     @Subscribe
-    protected void onInstantiatedBeanEvent(InstantiatedBeanEvent beanEvent) {
+    public void onInstantiatedBeanEvent(InstantiatedBeanEvent beanEvent) {
         this.instantiateEvents.add(beanEvent);
     }
 
+    @SuppressWarnings("UnusedDeclaration") //used via reflection
     @Subscribe
-    protected void onConfiguredBeanEvent(ConfiguredBeanEvent beanEvent) {
+    public void onConfiguredBeanEvent(ConfiguredBeanEvent beanEvent) {
         this.configuredEvents.add(beanEvent);
     }
 
+    @SuppressWarnings("UnusedDeclaration") //used via reflection
     @Subscribe
-    protected void onDestroyedBeanEvent(DestroyedBeanEvent beanEvent) {
+    public void onInitializedBeanEvent(InitializedBeanEvent beanEvent) {
+        this.initializedEvents.add(beanEvent);
+    }
+
+    @SuppressWarnings("UnusedDeclaration") //used via reflection
+    @Subscribe
+    public void onDestroyedBeanEvent(DestroyedBeanEvent beanEvent) {
         this.destroyedEvents.add(beanEvent);
     }
 
-    public List<InstantiatedBeanEvent> getInstantiateEvents() {
+    public List<InstantiatedBeanEvent> getInstantiatedEvents() {
         return instantiateEvents;
     }
 
@@ -65,6 +77,10 @@ public class RecordingBeanListener {
         return configuredEvents;
     }
 
+    public List<InitializedBeanEvent> getInitializedEvents() {
+        return initializedEvents;
+    }
+
     public List<DestroyedBeanEvent> getDestroyedEvents() {
         return destroyedEvents;
     }