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:30 UTC
[03/13] shiro git commit: SHIRO-395: added initial implementation -
all org.apache.shiro.event.** additions are at 100% test coverage.
SHIRO-395: added initial implementation - all org.apache.shiro.event.** additions are at 100% test coverage.
git-svn-id: https://svn.apache.org/repos/asf/shiro/trunk@1410707 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/bbc8efac
Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/bbc8efac
Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/bbc8efac
Branch: refs/heads/1.3.x
Commit: bbc8efaccef60939c8786e317966c878a34203ce
Parents: 0a886db
Author: Les Hazlewood <lh...@apache.org>
Authored: Sat Nov 17 08:12:48 2012 +0000
Committer: Brian Demers <bd...@apache.org>
Committed: Fri Jul 8 13:49:37 2016 -0400
----------------------------------------------------------------------
.../java/org/apache/shiro/event/Publisher.java | 34 ++
.../java/org/apache/shiro/event/Subscribe.java | 45 ++
.../bus/AnnotationEventListenerResolver.java | 96 +++++
.../apache/shiro/event/bus/ClassComparator.java | 73 ++++
.../apache/shiro/event/bus/DefaultEventBus.java | 161 ++++++++
.../apache/shiro/event/bus/EventListener.java | 56 +++
.../event/bus/EventListenerComparator.java | 68 ++++
.../shiro/event/bus/EventListenerResolver.java | 50 +++
.../bus/SingleArgumentMethodEventListener.java | 74 ++++
.../shiro/event/bus/SubscriberRegistry.java | 45 ++
.../shiro/event/bus/TypedEventListener.java | 27 ++
.../main/java/org/apache/shiro/util/Assert.java | 407 +++++++++++++++++++
.../java/org/apache/shiro/util/ClassUtils.java | 27 ++
.../AnnotationEventListenerResolverTest.groovy | 45 ++
.../org/apache/shiro/event/bus/BarEvent.groovy | 29 ++
.../org/apache/shiro/event/bus/BazEvent.groovy | 29 ++
.../shiro/event/bus/ClassComparatorTest.groovy | 62 +++
.../shiro/event/bus/DefaultEventBusTest.groovy | 163 ++++++++
.../bus/ErroneouslyAnnotatedSubscriber.groovy | 31 ++
.../shiro/event/bus/ErrorCausingEvent.groovy | 25 ++
.../bus/EventListenerComparatorTest.groovy | 71 ++++
.../bus/ExceptionThrowingSubscriber.groovy | 32 ++
.../org/apache/shiro/event/bus/FooEvent.groovy | 29 ++
.../event/bus/NotAnnotatedSubscriber.groovy | 27 ++
.../apache/shiro/event/bus/SimpleEvent.groovy | 29 ++
.../shiro/event/bus/SimpleSubscriber.groovy | 38 ++
...SingleArgumentMethodEventListenerTest.groovy | 86 ++++
.../event/bus/SubclassTestSubscriber.groovy | 49 +++
.../shiro/event/bus/TestSubscriber.groovy | 50 +++
29 files changed, 1958 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/Publisher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/Publisher.java b/core/src/main/java/org/apache/shiro/event/Publisher.java
new file mode 100644
index 0000000..50c1c8d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/Publisher.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.shiro.event;
+
+/**
+ * Publishes events to an event subsystem that will deliver events to registered {@link Subscribe}rs.
+ *
+ * @since 1.3
+ */
+public interface Publisher {
+
+ /**
+ * Publishes the specified event to an event subsystem that will deliver events to relevant {@link Subscribe}rs.
+ *
+ * @param event The event object to distribute to relevant subscribers.
+ */
+ void publish(Object event);
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/Subscribe.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/Subscribe.java b/core/src/main/java/org/apache/shiro/event/Subscribe.java
new file mode 100644
index 0000000..03bc599
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/Subscribe.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates a method is an event consumer. The method must have a single argument and the argument's type determines
+ * what type of events should be delivered to the method for consumption.
+ * <p/>
+ * For example:
+ * <pre>
+ * @Subscribe
+ * public void onSomeEvent(SomeEvent event) { ... }
+ * </pre>
+ * <p/>
+ * Because the method argument is declared as a {@code SomeEvent} type, the method will be called by the event
+ * dispatcher whenever a {@code SomeEvent} instance (or one of its subclass instances that is not already registered)
+ * is published.
+ *
+ * @since 1.3
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = ElementType.METHOD)
+public @interface Subscribe {
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/AnnotationEventListenerResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/AnnotationEventListenerResolver.java b/core/src/main/java/org/apache/shiro/event/bus/AnnotationEventListenerResolver.java
new file mode 100644
index 0000000..883dbcf
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/AnnotationEventListenerResolver.java
@@ -0,0 +1,96 @@
+/*
+ * 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.bus;
+
+import org.apache.shiro.event.Subscribe;
+import org.apache.shiro.util.ClassUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Inspects an object for annotated methods of interest and creates an {@link EventListener} for each method discovered.
+ * An event bus will call the resulting listeners as relevant events arrive.
+ * <p/>
+ * The default {@link #setAnnotationClass(Class) annotationClass} is {@link Subscribe}, indicating any
+ * {@link Subscribe}-annotated method will be represented as an EventListener.
+ *
+ * @see SingleArgumentMethodEventListener
+ * @since 1.3
+ */
+public class AnnotationEventListenerResolver implements EventListenerResolver {
+
+ private Class<? extends Annotation> annotationClass;
+
+ public AnnotationEventListenerResolver() {
+ this.annotationClass = Subscribe.class;
+ }
+
+ /**
+ * Returns a new collection of {@link EventListener} instances, each instance corresponding to an annotated
+ * method discovered on the specified {@code instance} argument.
+ *
+ * @param instance the instance to inspect for annotated event handler methods.
+ * @return a new collection of {@link EventListener} instances, each instance corresponding to an annotated
+ * method discovered on the specified {@code instance} argument.
+ */
+ public List<EventListener> getEventListeners(Object instance) {
+ if (instance == null) {
+ return Collections.emptyList();
+ }
+
+ List<Method> methods = ClassUtils.getAnnotatedMethods(instance.getClass(), getAnnotationClass());
+ if (methods == null || methods.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<EventListener> listeners = new ArrayList<EventListener>(methods.size());
+
+ for (Method m : methods) {
+ listeners.add(new SingleArgumentMethodEventListener(instance, m));
+ }
+
+ return listeners;
+ }
+
+ /**
+ * Returns the type of annotation that indicates a method that should be represented as an {@link EventListener},
+ * defaults to {@link Subscribe}.
+ *
+ * @return the type of annotation that indicates a method that should be represented as an {@link EventListener},
+ * defaults to {@link Subscribe}.
+ */
+ public Class<? extends Annotation> getAnnotationClass() {
+ return annotationClass;
+ }
+
+ /**
+ * Sets the type of annotation that indicates a method that should be represented as an {@link EventListener}.
+ * The default value is {@link Subscribe}.
+ *
+ * @param annotationClass the type of annotation that indicates a method that should be represented as an
+ * {@link EventListener}. The default value is {@link Subscribe}.
+ */
+ public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
+ this.annotationClass = annotationClass;
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/ClassComparator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/ClassComparator.java b/core/src/main/java/org/apache/shiro/event/bus/ClassComparator.java
new file mode 100644
index 0000000..4d1928f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/ClassComparator.java
@@ -0,0 +1,73 @@
+/*
+ * 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.bus;
+
+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>
+ * @Subscribe
+ * public void onEvent(A a) { ... }
+ *
+ * @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/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/DefaultEventBus.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/DefaultEventBus.java b/core/src/main/java/org/apache/shiro/event/bus/DefaultEventBus.java
new file mode 100644
index 0000000..4b74522
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/DefaultEventBus.java
@@ -0,0 +1,161 @@
+/*
+ * 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.bus;
+
+
+import org.apache.shiro.event.Publisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A default event bus implementation that synchronously publishes events to registered listeners. Listeners can be
+ * registered or unregistered for events as necessary.
+ * <p/>
+ * An event bus enables a publish/subscribe paradigm within Shiro - components can publish or consume events they
+ * find relevant without needing to be tightly coupled to other components. This affords great
+ * flexibility within Shiro by promoting loose coupling and high cohesion between components and a much safer pluggable
+ * architecture.
+ * <h2>Sending Events</h2>
+ * If a component wishes to publish events to other components:
+ * <pre>
+ * MyEvent myEvent = createMyEvent();
+ * eventBus.publish(myEvent);
+ * </pre>
+ * The event bus will determine the type of event and then dispatch the event to components that wish to receive
+ * events of that type.
+ * <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>
+ * eventBus.register(myComponent);
+ * </pre>
+ * </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).
+ *
+ * @since 1.3
+ */
+public class DefaultEventBus implements Publisher, SubscriberRegistry {
+
+ private static final Logger log = LoggerFactory.getLogger(DefaultEventBus.class);
+
+ private EventListenerResolver eventListenerResolver;
+
+ private final Map<Object,Subscriber> registry;
+
+ public DefaultEventBus() {
+ this.registry = new ConcurrentHashMap<Object, Subscriber>();
+ this.eventListenerResolver = new AnnotationEventListenerResolver();
+ }
+
+ public EventListenerResolver getEventListenerResolver() {
+ return eventListenerResolver;
+ }
+
+ public void setEventListenerResolver(EventListenerResolver eventListenerResolver) {
+ this.eventListenerResolver = eventListenerResolver;
+ }
+
+ public void publish(Object event) {
+ if (event == null) {
+ log.info("Received null event for publishing. Ignoring and returning.");
+ return;
+ }
+
+ for( Subscriber subscriber : registry.values() ) {
+ subscriber.onEvent(event);
+ }
+ }
+
+ public void register(Object instance) {
+ if (instance == null) {
+ log.info("Received null instance for registration. Ignoring registration request.");
+ return;
+ }
+
+ unregister(instance);
+
+ 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.");
+ return;
+ }
+
+ Subscriber subscriber = new Subscriber(instance, listeners);
+
+ this.registry.put(instance, subscriber);
+ }
+
+ public void unregister(Object instance) {
+ if (instance == null) {
+ return;
+ }
+ this.registry.remove(instance);
+ }
+
+ private class Subscriber {
+
+ private final Object instance;
+ private final List<EventListener> registeredListeners;
+
+ public Subscriber(Object instance, List<EventListener> listeners) {
+ this.instance = instance;
+ List<EventListener> toSort = new ArrayList<EventListener>(listeners);
+ Collections.sort(toSort, new EventListenerComparator());
+ this.registeredListeners = toSort;
+ }
+
+ public void onEvent(Object event) {
+
+ Set<Object> delivered = new HashSet<Object>();
+
+ for(EventListener listener : this.registeredListeners) {
+ Object target = listener;
+ if (listener instanceof SingleArgumentMethodEventListener) {
+ SingleArgumentMethodEventListener singleArgListener = (SingleArgumentMethodEventListener)listener;
+ target = singleArgListener.getTarget();
+ }
+ if (listener.accepts(event) && !delivered.contains(target)) {
+ try {
+ listener.onEvent(event);
+ delivered.add(target);
+ } catch (Throwable t) {
+ log.warn("Event listener processing failed. Listeners should generally " +
+ "handle exceptions directly and not propagate to the event bus.", t);
+ }
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/EventListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/EventListener.java b/core/src/main/java/org/apache/shiro/event/bus/EventListener.java
new file mode 100644
index 0000000..a653674
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/EventListener.java
@@ -0,0 +1,56 @@
+/*
+ * 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.bus;
+
+/**
+ * An event listener knows how to accept and process events of a particular type (or types).
+ * <p/>
+ * Note that this interface is in the event bus package (and not the event package directly) because it is a supporting
+ * concept for event bus implementations and not something that application
+ * developers using Shiro should implement directly. App developers should instead use the
+ * {@link org.apache.shiro.event.Subscribe Subscribe} annotation on methods they wish to receive events.
+ * <p/>
+ * This interface therefore mainly represents a 'middle man' between the event bus and the actual subscribing
+ * component. As such, event bus implementors (or framework/infrastructural implementors) or those that wish to
+ * customize listener/dispatch functionality might find this concept useful.
+ * <p/>
+ * It is a concept almost always used in conjunction with a {@link EventListenerResolver} implementation.
+ *
+ * @see SingleArgumentMethodEventListener
+ * @see AnnotationEventListenerResolver
+ *
+ * @since 1.3
+ */
+public interface EventListener {
+
+ /**
+ * Returns {@code true} if the listener instance can process the specified event object, {@code false} otherwise.
+ * @param event the event object to test
+ * @return {@code true} if the listener instance can process the specified event object, {@code false} otherwise.
+ */
+ boolean accepts(Object event);
+
+ /**
+ * Handles the specified event. Again, as this interface is an implementation concept, implementations of this
+ * method will likely dispatch the event to a 'real' processor (e.g. method).
+ *
+ * @param event the event to handle.
+ */
+ void onEvent(Object event);
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/EventListenerComparator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/EventListenerComparator.java b/core/src/main/java/org/apache/shiro/event/bus/EventListenerComparator.java
new file mode 100644
index 0000000..d096591
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/EventListenerComparator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.bus;
+
+import java.util.Comparator;
+
+/**
+ * Compares two event listeners to determine the order in which they should be invoked when an event is dispatched.
+ * The lower the order, the sooner it will be invoked (the higher its precedence). The higher the order, the later
+ * it will be invoked (the lower its precedence).
+ * <p/>
+ * TypedEventListeners have a higher precedence (i.e. a lower order) than standard EventListener instances. Standard
+ * 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 org.apache.shiro.event.bus.TypedEventListener#getEventType() eventType}.
+ *
+ * @since 1.3
+ */
+public class EventListenerComparator implements Comparator<EventListener> {
+
+ public int compare(EventListener a, EventListener 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 instanceof TypedEventListener) {
+ TypedEventListener ta = (TypedEventListener)a;
+ if (b instanceof TypedEventListener) {
+ TypedEventListener tb = (TypedEventListener)b;
+ return new ClassComparator().compare(ta.getEventType(), tb.getEventType());
+ } else {
+ return -1; //TypedEventListeners are 'less than' (higher priority) than non typed
+ }
+ } else {
+ if (b instanceof TypedEventListener) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/EventListenerResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/EventListenerResolver.java b/core/src/main/java/org/apache/shiro/event/bus/EventListenerResolver.java
new file mode 100644
index 0000000..b865d89
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/EventListenerResolver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.bus;
+
+import java.util.List;
+
+/**
+ * An {@code EventListenerResolver} knows how to resolve (either create or lookup) {@link EventListener} instances
+ * as a result of inspecting a subscriber object, mostly likely a
+ * {@link org.apache.shiro.event.Subscribe Subscribe}-annotated object instance.
+ * <p/>
+ * This interface exists primarily as a support concept for the {@link DefaultEventBus} implementation. Custom
+ * implementations of this interface can be configured on a {@link DefaultEventBus} instance to determine exactly
+ * how a subscriber receives events.
+ * <p/>
+ * For example, the {@link AnnotationEventListenerResolver AnnotationEventListenerResolver} will inspect a runtime
+ * object for {@link org.apache.shiro.event.Subscribe Subscribe}-annotated methods, and for each method found, return
+ * an {@link EventListener} instance representing the method to invoke.
+ *
+ * @see AnnotationEventListenerResolver
+ * @see SingleArgumentMethodEventListener
+ * @since 1.3
+ */
+public interface EventListenerResolver {
+
+ /**
+ * Returns {@link EventListener} instances as a result of inspecting a subscriber object, mostly likely with
+ * {@link org.apache.shiro.event.Subscribe Subscribe}-annotated methods.
+ *
+ * @param instance the subscriber instance for which EventListener instances should be acquired.
+ * @return {@link EventListener} instances as a result of inspecting a subscriber object.
+ */
+ List<EventListener> getEventListeners(Object instance);
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/SingleArgumentMethodEventListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/SingleArgumentMethodEventListener.java b/core/src/main/java/org/apache/shiro/event/bus/SingleArgumentMethodEventListener.java
new file mode 100644
index 0000000..f5ea9f9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/SingleArgumentMethodEventListener.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.bus;
+
+import java.lang.reflect.Method;
+
+/**
+ * A event listener that invokes a target object's method that accepts a single event argument.
+ *
+ * @since 1.3
+ */
+public class SingleArgumentMethodEventListener implements TypedEventListener {
+
+ private final Object target;
+ private final Method method;
+
+ public SingleArgumentMethodEventListener(Object target, Method method) {
+ this.target = target;
+ this.method = method;
+ //assert that the method is defined as expected:
+ getMethodArgumentType(method);
+ }
+
+ public Object getTarget() {
+ return this.target;
+ }
+
+ public Method getMethod() {
+ return this.method;
+ }
+
+ public boolean accepts(Object event) {
+ return event != null && getEventType().isInstance(event);
+ }
+
+ public Class getEventType() {
+ return getMethodArgumentType(getMethod());
+ }
+
+ public void onEvent(Object event) {
+ Method method = getMethod();
+ try {
+ method.invoke(getTarget(), event);
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable to invoke event handler method [" + method + "]", e);
+ }
+ }
+
+ protected Class getMethodArgumentType(Method method) {
+ Class[] paramTypes = method.getParameterTypes();
+ if (paramTypes.length != 1) {
+ //the default implementation expects a single typed argument and nothing more:
+ String msg = "Event handler methods must accept a single argument.";
+ throw new IllegalArgumentException(msg);
+ }
+ return paramTypes[0];
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/SubscriberRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/SubscriberRegistry.java b/core/src/main/java/org/apache/shiro/event/bus/SubscriberRegistry.java
new file mode 100644
index 0000000..af57d6e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/SubscriberRegistry.java
@@ -0,0 +1,45 @@
+/*
+ * 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.bus;
+
+/**
+ * Allows event subscribers to register or unregister with an event subsystem to receive (or not receive) published
+ * events.
+ *
+ * @since 1.3
+ */
+public interface SubscriberRegistry {
+
+ /**
+ * Registers all event handler methods on the specified instance to receive relevant events. The handler methods
+ * are determined by {@link SubscriberRegistry} implementations, typically by using an
+ * {@link EventListenerResolver} (e.g. {@link AnnotationEventListenerResolver}).
+ *
+ * @param subscriber the object whose event handler methods should be registered to receive events.
+ */
+ void register(Object subscriber);
+
+ /**
+ * Unregisters all previously-registered event handler methods on the specified instance. If the specified object
+ * was not previously registered, calling this method has no effect.
+ *
+ * @param subscriber the previously
+ */
+ void unregister(Object subscriber);
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/event/bus/TypedEventListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/event/bus/TypedEventListener.java b/core/src/main/java/org/apache/shiro/event/bus/TypedEventListener.java
new file mode 100644
index 0000000..9cb51e0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/event/bus/TypedEventListener.java
@@ -0,0 +1,27 @@
+/*
+ * 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.bus;
+
+/**
+ * @since 1.3
+ */
+public interface TypedEventListener extends EventListener {
+
+ Class getEventType();
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/util/Assert.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/util/Assert.java b/core/src/main/java/org/apache/shiro/util/Assert.java
new file mode 100644
index 0000000..d580247
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/Assert.java
@@ -0,0 +1,407 @@
+/*
+ * 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.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Assertion utility class that assists in validating arguments.
+ * Useful for identifying programmer errors early and clearly at runtime.
+ * Usage also reduces a program's
+ * <a href="http://en.wikipedia.org/wiki/Cyclomatic_complexity">cyclomatic complexity</a>.
+ *
+ * <p>For example, if the contract of a public method states it does not
+ * allow <code>null</code> arguments, Assert can be used to validate that
+ * contract. Doing this clearly indicates a contract violation when it
+ * occurs and protects the class's invariants.
+ *
+ * <p>Typically used to validate method arguments rather than configuration
+ * properties, to check for cases that are usually programmer errors rather than
+ * configuration errors. In contrast to config initialization code, there is
+ * usally no point in falling back to defaults in such methods.
+ *
+ * <p>This class is similar to JUnit's assertion library. If an argument value is
+ * deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
+ * For example:
+ *
+ * <pre class="code">
+ * Assert.notNull(clazz, "The class must not be null");
+ * Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
+ *
+ * Mainly for internal use within the framework; consider Jakarta's Commons Lang
+ * >= 2.0 for a more comprehensive suite of assertion utilities.
+ * <p/>
+ * <em>Gratefully borrowed from the Spring Framework, also Apache 2.0 licensed</em>
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @author Rob Harrop
+ * @since 1.3
+ */
+public abstract class Assert {
+
+ /**
+ * Assert a boolean expression, throwing <code>IllegalArgumentException</code>
+ * if the test result is <code>false</code>.
+ * <pre class="code">Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if expression is <code>false</code>
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing <code>IllegalArgumentException</code>
+ * if the test result is <code>false</code>.
+ * <pre class="code">Assert.isTrue(i > 0);</pre>
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if expression is <code>false</code>
+ */
+ public static void isTrue(boolean expression) {
+ isTrue(expression, "[Assertion failed] - this expression must be true");
+ }
+
+ /**
+ * Assert that an object is <code>null</code> .
+ * <pre class="code">Assert.isNull(value, "The value must be null");</pre>
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is not <code>null</code>
+ */
+ public static void isNull(Object object, String message) {
+ if (object != null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is <code>null</code> .
+ * <pre class="code">Assert.isNull(value);</pre>
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is not <code>null</code>
+ */
+ public static void isNull(Object object) {
+ isNull(object, "[Assertion failed] - the object argument must be null");
+ }
+
+ /**
+ * Assert that an object is not <code>null</code> .
+ * <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is <code>null</code>
+ */
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is not <code>null</code> .
+ * <pre class="code">Assert.notNull(clazz);</pre>
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is <code>null</code>
+ */
+ public static void notNull(Object object) {
+ notNull(object, "[Assertion failed] - this argument is required; it must not be null");
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be <code>null</code> and not the empty String.
+ * <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see StringUtils#hasLength
+ */
+ public static void hasLength(String text, String message) {
+ if (!StringUtils.hasLength(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be <code>null</code> and not the empty String.
+ * <pre class="code">Assert.hasLength(name);</pre>
+ * @param text the String to check
+ * @see StringUtils#hasLength
+ */
+ public static void hasLength(String text) {
+ hasLength(text,
+ "[Assertion failed] - this String argument must have length; it must not be null or empty");
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be <code>null</code> and must contain at least one non-whitespace character.
+ * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see StringUtils#hasText
+ */
+ public static void hasText(String text, String message) {
+ if (!StringUtils.hasText(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be <code>null</code> and must contain at least one non-whitespace character.
+ * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+ * @param text the String to check
+ * @see StringUtils#hasText
+ */
+ public static void hasText(String text) {
+ hasText(text,
+ "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ * @param message the exception message to use if the assertion fails
+ */
+ public static void doesNotContain(String textToSearch, String substring, String message) {
+ if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
+ textToSearch.indexOf(substring) != -1) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * <pre class="code">Assert.doesNotContain(name, "rod");</pre>
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ */
+ public static void doesNotContain(String textToSearch, String substring) {
+ doesNotContain(textToSearch, substring,
+ "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
+ }
+
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * <code>null</code> and must have at least one element.
+ * <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
+ */
+ public static void notEmpty(Object[] array, String message) {
+ if (array == null || array.length == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * <code>null</code> and must have at least one element.
+ * <pre class="code">Assert.notEmpty(array);</pre>
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
+ */
+ public static void notEmpty(Object[] array) {
+ notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array contains a <code>null</code> element
+ */
+ public static void noNullElements(Object[] array, String message) {
+ if (array != null) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * <pre class="code">Assert.noNullElements(array);</pre>
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array contains a <code>null</code> element
+ */
+ public static void noNullElements(Object[] array) {
+ noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * <code>null</code> and must have at least one element.
+ * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+ * @param collection the collection to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
+ */
+ public static void notEmpty(Collection collection, String message) {
+ if (CollectionUtils.isEmpty(collection)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * <code>null</code> and must have at least one element.
+ * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+ * @param collection the collection to check
+ * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
+ */
+ public static void notEmpty(Collection collection) {
+ notEmpty(collection,
+ "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be <code>null</code>
+ * and must have at least one entry.
+ * <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
+ * @param map the map to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
+ */
+ public static void notEmpty(Map map, String message) {
+ if (CollectionUtils.isEmpty(map)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be <code>null</code>
+ * and must have at least one entry.
+ * <pre class="code">Assert.notEmpty(map);</pre>
+ * @param map the map to check
+ * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
+ */
+ public static void notEmpty(Map map) {
+ notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
+ }
+
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
+ * @param clazz the required class
+ * @param obj the object to check
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class clazz, Object obj) {
+ isInstanceOf(clazz, obj, "");
+ }
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
+ * @param type the type to check against
+ * @param obj the object to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class type, Object obj, String message) {
+ notNull(type, "Type to check against must not be null");
+ if (!type.isInstance(obj)) {
+ throw new IllegalArgumentException(message +
+ "Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
+ "] must be an instance of " + type);
+ }
+ }
+
+ /**
+ * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
+ * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
+ * @param superType the super type to check
+ * @param subType the sub type to check
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class superType, Class subType) {
+ isAssignable(superType, subType, "");
+ }
+
+ /**
+ * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
+ * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
+ * @param superType the super type to check against
+ * @param subType the sub type to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class superType, Class subType, String message) {
+ notNull(superType, "Type to check against must not be null");
+ if (subType == null || !superType.isAssignableFrom(subType)) {
+ throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
+ }
+ }
+
+
+ /**
+ * Assert a boolean expression, throwing <code>IllegalStateException</code>
+ * if the test result is <code>false</code>. Call isTrue if you wish to
+ * throw IllegalArgumentException on an assertion failure.
+ * <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalStateException if expression is <code>false</code>
+ */
+ public static void state(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing {@link IllegalStateException}
+ * if the test result is <code>false</code>.
+ * <p>Call {@link #isTrue(boolean)} if you wish to
+ * throw {@link IllegalArgumentException} on an assertion failure.
+ * <pre class="code">Assert.state(id == null);</pre>
+ * @param expression a boolean expression
+ * @throws IllegalStateException if the supplied expression is <code>false</code>
+ */
+ public static void state(boolean expression) {
+ state(expression, "[Assertion failed] - this state invariant must be true");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/main/java/org/apache/shiro/util/ClassUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/shiro/util/ClassUtils.java b/core/src/main/java/org/apache/shiro/util/ClassUtils.java
index dd46b06..aa7334b 100644
--- a/core/src/main/java/org/apache/shiro/util/ClassUtils.java
+++ b/core/src/main/java/org/apache/shiro/util/ClassUtils.java
@@ -22,7 +22,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
/**
@@ -208,6 +212,29 @@ public class ClassUtils {
}
/**
+ *
+ * @param type
+ * @param annotation
+ * @return
+ * @since 1.3
+ */
+ public static List<Method> getAnnotatedMethods(final Class<?> type, final Class<? extends Annotation> annotation) {
+ final List<Method> methods = new ArrayList<Method>();
+ Class<?> clazz = type;
+ while (!Object.class.equals(clazz)) {
+ Method[] currentClassMethods = clazz.getDeclaredMethods();
+ for (final Method method : currentClassMethods) {
+ if (annotation == null || method.isAnnotationPresent(annotation)) {
+ methods.add(method);
+ }
+ }
+ // move to the upper class in the hierarchy in search for more methods
+ clazz = clazz.getSuperclass();
+ }
+ return methods;
+ }
+
+ /**
* @since 1.0
*/
private static interface ClassLoaderAccessor {
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/AnnotationEventListenerResolverTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/AnnotationEventListenerResolverTest.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/AnnotationEventListenerResolverTest.groovy
new file mode 100644
index 0000000..de32bca
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/AnnotationEventListenerResolverTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.bus
+
+/**
+ * @since 1.3
+ */
+class AnnotationEventListenerResolverTest extends GroovyTestCase {
+
+ void testSetGetAnnotationClass() {
+ def resolver = new AnnotationEventListenerResolver();
+ resolver.setAnnotationClass(Override.class) //any old annotation will do for the test
+ assertSame Override.class, resolver.getAnnotationClass()
+ }
+
+ void testNullInstanceArgument() {
+ def resolver = new AnnotationEventListenerResolver()
+ def collection = resolver.getEventListeners(null)
+ assertNotNull collection
+ assertTrue collection.isEmpty()
+ }
+
+ void testNoAnnotationsArgument() {
+ def resolver = new AnnotationEventListenerResolver()
+ def collection = resolver.getEventListeners(new NotAnnotatedSubscriber())
+ assertNotNull collection
+ assertTrue collection.isEmpty()
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/BarEvent.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/BarEvent.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/BarEvent.groovy
new file mode 100644
index 0000000..7123bec
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/BarEvent.groovy
@@ -0,0 +1,29 @@
+/*
+ * 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.bus
+
+/**
+ * @since 1.3
+ */
+class BarEvent extends FooEvent {
+
+ BarEvent(Object o) {
+ super(o)
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/BazEvent.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/BazEvent.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/BazEvent.groovy
new file mode 100644
index 0000000..60a90ca
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/BazEvent.groovy
@@ -0,0 +1,29 @@
+/*
+ * 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.bus
+
+/**
+ * @since 1.3
+ */
+class BazEvent extends BarEvent {
+
+ BazEvent(Object o) {
+ super(o)
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/ClassComparatorTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/ClassComparatorTest.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/ClassComparatorTest.groovy
new file mode 100644
index 0000000..6302d98
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/ClassComparatorTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.bus
+
+/**
+ * @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/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/DefaultEventBusTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/DefaultEventBusTest.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/DefaultEventBusTest.groovy
new file mode 100644
index 0000000..c29b69d
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/DefaultEventBusTest.groovy
@@ -0,0 +1,163 @@
+/*
+ * 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.bus
+
+import static org.easymock.EasyMock.*
+
+/**
+ * @since 1.3
+ */
+class DefaultEventBusTest extends GroovyTestCase {
+
+ DefaultEventBus bus;
+
+ @Override
+ protected void setUp() {
+ bus = new DefaultEventBus()
+ }
+
+ void testSetEventListenerResolver() {
+ def resolver = new EventListenerResolver() {
+ List<EventListener> getEventListeners(Object instance) {
+ return null //dummy implementation
+ }
+ }
+ bus.setEventListenerResolver(resolver)
+ assertSame resolver, bus.getEventListenerResolver()
+ }
+
+ void testSimpleSubscribe() {
+ def subscriber = new TestSubscriber();
+
+ bus.register(subscriber);
+
+ def event = new FooEvent(this)
+
+ bus.publish(event)
+
+ assertEquals 1, subscriber.fooCount
+ assertEquals 1, subscriber.count
+ assertSame event, subscriber.lastEvent
+ }
+
+ void testPublishNullEvent() {
+ def subscriber = new TestSubscriber();
+ bus.register(subscriber)
+
+ bus.publish(null)
+
+ assertEquals 0, subscriber.count
+ }
+
+ void testSubscribeNullInstance() {
+ def resolver = createStrictMock(EventListenerResolver) //assert no methods are called on this
+ bus.eventListenerResolver = resolver
+
+ replay(resolver)
+
+ bus.register(null)
+
+ verify(resolver)
+ }
+
+ void testSubscribeWithoutAnnotations() {
+ def subscriber = new NotAnnotatedSubscriber()
+ bus.register(subscriber)
+
+ bus.publish(new FooEvent(this))
+
+ assertEquals 0, bus.registry.size()
+ }
+
+ void testUnsubscribeNullInstance() {
+ bus.unregister(null)
+ }
+
+ void testUnsubscribe() {
+ def subscriber = new TestSubscriber()
+ bus.register(subscriber)
+ assertEquals 1, bus.registry.size()
+
+ def event = new FooEvent(this)
+
+ bus.publish(event)
+
+ assertSame event, subscriber.lastEvent
+ assertEquals 1, subscriber.fooCount
+ assertEquals 1, subscriber.count
+
+ bus.unregister(subscriber)
+
+ assertEquals 0, bus.registry.size()
+ }
+
+ void testPolymorphicSubscribeMethodsOnlyOneInvoked() {
+ def subscriber = new TestSubscriber()
+ bus.register(subscriber)
+
+ def event = new BarEvent(this)
+
+ bus.publish(event)
+
+ assertSame event, subscriber.lastEvent
+ assertEquals 0, subscriber.fooCount
+ assertEquals 1, subscriber.barCount
+ assertEquals 1, subscriber.count
+ }
+
+ void testPolymorphicSubscribeMethodsOnlyOneInvokedWithListenerSubclass() {
+ def subscriber = new SubclassTestSubscriber()
+ bus.register(subscriber)
+
+ def event = new BazEvent(this)
+
+ bus.publish(event)
+
+ assertSame event, subscriber.lastEvent
+ assertEquals 1, subscriber.count
+ assertEquals 1, subscriber.bazCount
+ assertEquals 0, subscriber.fooCount
+ assertEquals 0, subscriber.barCount
+ }
+
+ void testSubscribeWithErroneousAnnotation() {
+ def subscriber = new ErroneouslyAnnotatedSubscriber()
+ //noinspection GroovyUnusedCatchParameter
+ try {
+ bus.register(subscriber)
+ fail("exception expected")
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ void testContinueThroughListenerExceptions() {
+ def ok = new SimpleSubscriber()
+ def error = new ExceptionThrowingSubscriber()
+
+ bus.register(ok)
+ bus.register(error)
+
+ bus.publish(new ErrorCausingEvent())
+ bus.publish(new SimpleEvent())
+
+ assertEquals 1, ok.count
+ assertEquals 0, error.count
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/ErroneouslyAnnotatedSubscriber.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/ErroneouslyAnnotatedSubscriber.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/ErroneouslyAnnotatedSubscriber.groovy
new file mode 100644
index 0000000..82546a6
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/ErroneouslyAnnotatedSubscriber.groovy
@@ -0,0 +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.event.bus
+
+import org.apache.shiro.event.Subscribe
+
+/**
+ * @since 1.3
+ */
+class ErroneouslyAnnotatedSubscriber {
+
+ @Subscribe
+ void onEvent(FooEvent event, Object someOtherArg) {
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/ErrorCausingEvent.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/ErrorCausingEvent.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/ErrorCausingEvent.groovy
new file mode 100644
index 0000000..897399f
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/ErrorCausingEvent.groovy
@@ -0,0 +1,25 @@
+/*
+ * 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.bus
+
+/**
+ * @since 1.3
+ */
+class ErrorCausingEvent {
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/EventListenerComparatorTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/EventListenerComparatorTest.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/EventListenerComparatorTest.groovy
new file mode 100644
index 0000000..1615e46
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/EventListenerComparatorTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * 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.bus
+
+import static org.easymock.EasyMock.createStrictMock
+
+/**
+ * @since 1.3
+ */
+class EventListenerComparatorTest extends GroovyTestCase {
+
+ EventListenerComparator comparator
+
+ @Override
+ protected void setUp() {
+ comparator = new EventListenerComparator()
+ }
+
+ void testANull() {
+ def result = comparator.compare(null, createStrictMock(EventListener))
+ assertEquals(-1, result)
+ }
+
+ void testBNull() {
+ def result = comparator.compare(createStrictMock(EventListener), null)
+ assertEquals 1, result
+ }
+
+ void testBothNull() {
+ assertEquals 0, comparator.compare(null, null)
+ }
+
+ void testBothSame() {
+ def mock = createStrictMock(EventListener)
+ assertEquals 0, comparator.compare(mock, mock)
+ }
+
+ void testBothEventListener() {
+ def a = createStrictMock(EventListener)
+ def b = createStrictMock(EventListener)
+ assertEquals 0, comparator.compare(a, b)
+ }
+
+ void testATypedListenerBNormalListener() {
+ def a = createStrictMock(TypedEventListener)
+ def b = createStrictMock(EventListener)
+ assertEquals(-1, comparator.compare(a, b))
+ }
+
+ void testANormalBTypedListener() {
+ def a = createStrictMock(EventListener)
+ def b = createStrictMock(TypedEventListener)
+ assertEquals 1, comparator.compare(a, b)
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/ExceptionThrowingSubscriber.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/ExceptionThrowingSubscriber.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/ExceptionThrowingSubscriber.groovy
new file mode 100644
index 0000000..b0b63c1
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/ExceptionThrowingSubscriber.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.bus
+
+import org.apache.shiro.event.Subscribe
+
+/**
+ * @since 1.3
+ */
+class ExceptionThrowingSubscriber extends TestSubscriber {
+
+ @Subscribe
+ void onEvent(ErrorCausingEvent event) {
+ throw new UnsupportedOperationException("This throws!")
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/FooEvent.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/FooEvent.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/FooEvent.groovy
new file mode 100644
index 0000000..b594c92
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/FooEvent.groovy
@@ -0,0 +1,29 @@
+/*
+ * 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.bus
+
+/**
+ * @since 1.3
+ */
+class FooEvent extends EventObject {
+
+ FooEvent(Object o) {
+ super(o)
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/NotAnnotatedSubscriber.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/NotAnnotatedSubscriber.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/NotAnnotatedSubscriber.groovy
new file mode 100644
index 0000000..8489185
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/NotAnnotatedSubscriber.groovy
@@ -0,0 +1,27 @@
+/*
+ * 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.bus
+
+/**
+ * @since 1.3
+ */
+class NotAnnotatedSubscriber {
+ //not a subscriber - no methods have been annotated on purpose.
+ void hello() {}
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/SimpleEvent.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/SimpleEvent.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/SimpleEvent.groovy
new file mode 100644
index 0000000..5639f5d
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/SimpleEvent.groovy
@@ -0,0 +1,29 @@
+/*
+ * 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.bus
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: 11/16/12
+ * Time: 11:09 PM
+ * To change this template use File | Settings | File Templates.
+ */
+class SimpleEvent {
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/SimpleSubscriber.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/SimpleSubscriber.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/SimpleSubscriber.groovy
new file mode 100644
index 0000000..0fa44ea
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/SimpleSubscriber.groovy
@@ -0,0 +1,38 @@
+/*
+ * 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.bus
+
+import org.apache.shiro.event.Subscribe
+
+/**
+ * @since 1.3
+ */
+class SimpleSubscriber {
+
+ int count
+
+ SimpleSubscriber() {
+ count = 0
+ }
+
+ @Subscribe
+ void onEvent(SimpleEvent event) {
+ count++
+ }
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/SingleArgumentMethodEventListenerTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/SingleArgumentMethodEventListenerTest.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/SingleArgumentMethodEventListenerTest.groovy
new file mode 100644
index 0000000..bf3c060
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/SingleArgumentMethodEventListenerTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * 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.bus
+
+import java.lang.reflect.Method
+
+/**
+ * @since 1.3
+ */
+class SingleArgumentMethodEventListenerTest extends GroovyTestCase {
+
+ void testInvalidConstruction() {
+
+ def target = new Object()
+
+ def method = Object.class.getMethods()[0] //any old method will do
+
+ try {
+ //noinspection GroovyResultOfObjectAllocationIgnored
+ new SingleArgumentMethodEventListener(target, method)
+ fail("exception expected")
+ } catch (IllegalArgumentException iae) {
+ assertEquals iae.message, "Event handler methods must accept a single argument."
+ }
+ }
+
+ void testValidConstruction() {
+
+ def target = new TestSubscriber()
+ def method = TestSubscriber.class.getMethods().find { it.name == "onFooEvent" }
+
+ def listener = new SingleArgumentMethodEventListener(target, method)
+
+ assertSame target, listener.getTarget()
+ assertSame method, listener.getMethod()
+ }
+
+ void testMethodException() {
+
+ def target = new TestSubscriber()
+ def method = TestSubscriber.class.getMethods().find { it.name == "onFooEvent" }
+
+ def listener = new SingleArgumentMethodEventListener(target, method) {
+ @Override
+ Method getMethod() {
+ //sneakily swap out the valid method with an erroneous one. This wouldn't ever happen normally, we're
+ //just doing this as a test harness:
+ return Object.class.getMethods()[0] //any method will do
+ }
+ }
+
+ //now invoke the erroneous method and ensure we get an exception:
+ try {
+ listener.onEvent(new FooEvent(this))
+ fail("exception expected")
+ } catch (IllegalStateException ise) {
+ assertTrue ise.message.startsWith("Unable to invoke event handler method")
+ }
+ }
+
+ void testAccepts() {
+ def target = new TestSubscriber()
+ def method = TestSubscriber.class.getMethods().find { it.name == "onFooEvent" }
+
+ def listener = new SingleArgumentMethodEventListener(target, method)
+
+ assertTrue listener.accepts(new FooEvent(this))
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/shiro/blob/bbc8efac/core/src/test/groovy/org/apache/shiro/event/bus/SubclassTestSubscriber.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/groovy/org/apache/shiro/event/bus/SubclassTestSubscriber.groovy b/core/src/test/groovy/org/apache/shiro/event/bus/SubclassTestSubscriber.groovy
new file mode 100644
index 0000000..8dc717c
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/event/bus/SubclassTestSubscriber.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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.bus
+
+import org.apache.shiro.event.Subscribe
+
+/**
+ * @since 1.3
+ */
+class SubclassTestSubscriber extends TestSubscriber {
+
+ int bazCount
+
+ SubclassTestSubscriber() {
+ bazCount = 0
+ }
+
+ @Subscribe
+ void onEvent(BazEvent event) {
+ bazCount++
+ lastEvent = event;
+ }
+
+ @Subscribe
+ void onEvent(ErrorCausingEvent event) {
+ throw new UnsupportedOperationException("This throws!")
+ }
+
+ @Override
+ int getCount() {
+ return super.getCount() + bazCount
+ }
+}