| <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>DefaultEventBus.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">Apache Shiro :: All (aggregate jar)</a> > <a href="../index.html" class="el_bundle">shiro-event</a> > <a href="index.source.html" class="el_package">org.apache.shiro.event.support</a> > <span class="el_source">DefaultEventBus.java</span></div><h1>DefaultEventBus.java</h1><pre class="source lang-java linenums">/* |
| * 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.EventBus; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| 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 |
| * 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 that is more resilient to change over time. |
| * <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. |
| * |
| * 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 { |
| |
| <span class="fc" id="L77"> private static final Logger log = LoggerFactory.getLogger(DefaultEventBus.class);</span> |
| |
| 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: |
| <span class="fc" id="L83"> private static final EventListenerComparator EVENT_LISTENER_COMPARATOR = new EventListenerComparator();</span> |
| |
| private EventListenerResolver eventListenerResolver; |
| |
| //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; |
| |
| <span class="fc" id="L105"> public DefaultEventBus() {</span> |
| <span class="fc" id="L106"> this.registry = new LinkedHashMap<Object, Subscription>(); //not thread safe, so we need locks:</span> |
| <span class="fc" id="L107"> ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();</span> |
| <span class="fc" id="L108"> this.registryReadLock = rwl.readLock();</span> |
| <span class="fc" id="L109"> this.registryWriteLock = rwl.writeLock();</span> |
| <span class="fc" id="L110"> this.eventListenerResolver = new AnnotationEventListenerResolver();</span> |
| <span class="fc" id="L111"> }</span> |
| |
| public EventListenerResolver getEventListenerResolver() { |
| <span class="fc" id="L114"> return eventListenerResolver;</span> |
| } |
| |
| public void setEventListenerResolver(EventListenerResolver eventListenerResolver) { |
| <span class="fc" id="L118"> this.eventListenerResolver = eventListenerResolver;</span> |
| <span class="fc" id="L119"> }</span> |
| |
| public void publish(Object event) { |
| <span class="fc bfc" id="L122" title="All 2 branches covered."> if (event == null) {</span> |
| <span class="fc" id="L123"> log.info("Received null event for publishing. Ignoring and returning.");</span> |
| <span class="fc" id="L124"> return;</span> |
| } |
| |
| <span class="fc" id="L127"> registryReadLock.lock();</span> |
| 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. |
| |
| <span class="fc bfc" id="L142" title="All 2 branches covered."> for (Subscription subscription : this.registry.values()) {</span> |
| <span class="fc" id="L143"> subscription.onEvent(event);</span> |
| <span class="fc" id="L144"> }</span> |
| } finally { |
| <span class="pc" id="L146"> registryReadLock.unlock();</span> |
| <span class="fc" id="L147"> }</span> |
| <span class="fc" id="L148"> }</span> |
| |
| public void register(Object instance) { |
| <span class="fc bfc" id="L151" title="All 2 branches covered."> if (instance == null) {</span> |
| <span class="fc" id="L152"> log.info("Received null instance for event listener registration. Ignoring registration request.");</span> |
| <span class="fc" id="L153"> return;</span> |
| } |
| |
| <span class="fc" id="L156"> unregister(instance);</span> |
| |
| <span class="fc" id="L158"> List<EventListener> listeners = getEventListenerResolver().getEventListeners(instance);</span> |
| |
| <span class="fc bfc" id="L160" title="All 4 branches covered."> if (listeners == null || listeners.isEmpty()) {</span> |
| <span class="fc" id="L161"> log.warn("Unable to resolve event listeners for subscriber instance [{}]. Ignoring registration request.",</span> |
| instance); |
| <span class="fc" id="L163"> return;</span> |
| } |
| |
| <span class="fc" id="L166"> Subscription subscription = new Subscription(listeners);</span> |
| |
| <span class="fc" id="L168"> this.registryWriteLock.lock();</span> |
| try { |
| <span class="fc" id="L170"> this.registry.put(instance, subscription);</span> |
| } finally { |
| <span class="pc" id="L172"> this.registryWriteLock.unlock();</span> |
| <span class="fc" id="L173"> }</span> |
| <span class="fc" id="L174"> }</span> |
| |
| public void unregister(Object instance) { |
| <span class="fc bfc" id="L177" title="All 2 branches covered."> if (instance == null) {</span> |
| <span class="fc" id="L178"> return;</span> |
| } |
| <span class="fc" id="L180"> this.registryWriteLock.lock();</span> |
| try { |
| <span class="fc" id="L182"> this.registry.remove(instance);</span> |
| } finally { |
| <span class="pc" id="L184"> this.registryWriteLock.unlock();</span> |
| <span class="fc" id="L185"> }</span> |
| <span class="fc" id="L186"> }</span> |
| |
| private class Subscription { |
| |
| private final List<EventListener> listeners; |
| |
| <span class="fc" id="L192"> public Subscription(List<EventListener> listeners) {</span> |
| <span class="fc" id="L193"> List<EventListener> toSort = new ArrayList<EventListener>(listeners);</span> |
| <span class="fc" id="L194"> Collections.sort(toSort, EVENT_LISTENER_COMPARATOR);</span> |
| <span class="fc" id="L195"> this.listeners = toSort;</span> |
| <span class="fc" id="L196"> }</span> |
| |
| public void onEvent(Object event) { |
| |
| <span class="fc" id="L200"> Set<Object> delivered = new HashSet<Object>();</span> |
| |
| <span class="fc bfc" id="L202" title="All 2 branches covered."> for (EventListener listener : this.listeners) {</span> |
| <span class="fc" id="L203"> Object target = listener;</span> |
| <span class="pc bpc" id="L204" title="1 of 2 branches missed."> if (listener instanceof SingleArgumentMethodEventListener) {</span> |
| <span class="fc" id="L205"> SingleArgumentMethodEventListener singleArgListener = (SingleArgumentMethodEventListener) listener;</span> |
| <span class="fc" id="L206"> target = singleArgListener.getTarget();</span> |
| } |
| <span class="fc bfc" id="L208" title="All 4 branches covered."> if (listener.accepts(event) && !delivered.contains(target)) {</span> |
| try { |
| <span class="fc" id="L210"> listener.onEvent(event);</span> |
| <span class="fc" id="L211"> } catch (Throwable t) {</span> |
| <span class="fc" id="L212"> log.warn(EVENT_LISTENER_ERROR_MSG, t);</span> |
| <span class="fc" id="L213"> }</span> |
| <span class="fc" id="L214"> delivered.add(target);</span> |
| } |
| <span class="fc" id="L216"> }</span> |
| <span class="fc" id="L217"> }</span> |
| } |
| } |
| </pre><div class="footer"><span class="right">Created with <a href="http://www.eclemma.org/jacoco">JaCoCo</a> 0.7.7.201606060606</span></div></body></html> |