Open Source Repository

Home /spring/spring-context-3.0.5 | Repository Home



org/springframework/context/event/AbstractApplicationEventMulticaster.java
/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed 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.springframework.context.event;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.OrderComparator;

/**
 * Abstract implementation of the {@link ApplicationEventMulticaster} interface,
 * providing the basic listener registration facility.
 *
 <p>Doesn't permit multiple instances of the same listener by default,
 * as it keeps listeners in a linked Set. The collection class used to hold
 * ApplicationListener objects can be overridden through the "collectionClass"
 * bean property.
 *
 <p>Implementing ApplicationEventMulticaster's actual {@link #multicastEvent} method
 * is left to subclasses. {@link SimpleApplicationEventMulticaster} simply multicasts
 * all events to all registered listeners, invoking them in the calling thread.
 * Alternative implementations could be more sophisticated in those respects.
 *
 @author Juergen Hoeller
 @since 1.2.3
 @see #getApplicationListeners(ApplicationEvent)
 @see SimpleApplicationEventMulticaster
 */
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {

  private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

  private final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
      new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>();

  private BeanFactory beanFactory;


  public void addApplicationListener(ApplicationListener listener) {
    synchronized (this.defaultRetriever) {
      this.defaultRetriever.applicationListeners.add(listener);
      this.retrieverCache.clear();
    }
  }

  public void addApplicationListenerBean(String listenerBeanName) {
    synchronized (this.defaultRetriever) {
      this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
      this.retrieverCache.clear();
    }
  }

  public void removeApplicationListener(ApplicationListener listener) {
    synchronized (this.defaultRetriever) {
      this.defaultRetriever.applicationListeners.remove(listener);
      this.retrieverCache.clear();
    }
  }

  public void removeApplicationListenerBean(String listenerBeanName) {
    synchronized (this.defaultRetriever) {
      this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName);
      this.retrieverCache.clear();
    }
  }

  public void removeAllListeners() {
    synchronized (this.defaultRetriever) {
      this.defaultRetriever.applicationListeners.clear();
      this.defaultRetriever.applicationListenerBeans.clear();
      this.retrieverCache.clear();
    }
  }

  public final void setBeanFactory(BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }

  private BeanFactory getBeanFactory() {
    if (this.beanFactory == null) {
      throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " +
          "because it is not associated with a BeanFactory");
    }
    return this.beanFactory;
  }


  /**
   * Return a Collection containing all ApplicationListeners.
   @return a Collection of ApplicationListeners
   @see org.springframework.context.ApplicationListener
   */
  protected Collection<ApplicationListener> getApplicationListeners() {
    return this.defaultRetriever.getApplicationListeners();
  }

  /**
   * Return a Collection of ApplicationListeners matching the given
   * event type. Non-matching listeners get excluded early.
   @param event the event to be propagated. Allows for excluding
   * non-matching listeners early, based on cached matching information.
   @return a Collection of ApplicationListeners
   @see org.springframework.context.ApplicationListener
   */
  protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
    Class<? extends ApplicationEvent> eventType = event.getClass();
    Class sourceType = event.getSource().getClass();
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
    ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
    if (retriever != null) {
      return retriever.getApplicationListeners();
    }
    else {
      retriever = new ListenerRetriever(true);
      LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
      synchronized (this.defaultRetriever) {
        for (ApplicationListener listener : this.defaultRetriever.applicationListeners) {
          if (supportsEvent(listener, eventType, sourceType)) {
            retriever.applicationListeners.add(listener);
            allListeners.add(listener);
          }
        }
        if (!this.defaultRetriever.applicationListenerBeans.isEmpty()) {
          BeanFactory beanFactory = getBeanFactory();
          for (String listenerBeanName : this.defaultRetriever.applicationListenerBeans) {
            ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
            if (!allListeners.contains(listener&& supportsEvent(listener, eventType, sourceType)) {
              retriever.applicationListenerBeans.add(listenerBeanName);
              allListeners.add(listener);
            }
          }
        }
        OrderComparator.sort(allListeners);
        this.retrieverCache.put(cacheKey, retriever);
      }
      return allListeners;
    }
  }

  /**
   * Determine whether the given listener supports the given event.
   <p>The default implementation detects the {@link SmartApplicationListener}
   * interface. In case of a standard {@link ApplicationListener}, a
   {@link GenericApplicationListenerAdapter} will be used to introspect
   * the generically declared type of the target listener.
   @param listener the target listener to check
   @param eventType the event type to check against
   @param sourceType the source type to check against
   @return whether the given listener should be included in the
   * candidates for the given event type
   */
  protected boolean supportsEvent(
      ApplicationListener listener, Class<? extends ApplicationEvent> eventType, Class sourceType) {

    SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ?
        (SmartApplicationListenerlistener : new GenericApplicationListenerAdapter(listener));
    return (smartListener.supportsEventType(eventType&& smartListener.supportsSourceType(sourceType));
  }


  /**
   * Cache key for ListenerRetrievers, based on event type and source type.
   */
  private static class ListenerCacheKey {

    private final Class eventType;

    private final Class sourceType;

    public ListenerCacheKey(Class eventType, Class sourceType) {
      this.eventType = eventType;
      this.sourceType = sourceType;
    }

    @Override
    public boolean equals(Object other) {
      if (this == other) {
        return true;
      }
      ListenerCacheKey otherKey = (ListenerCacheKeyother;
      return (this.eventType.equals(otherKey.eventType&& this.sourceType.equals(otherKey.sourceType));
    }

    @Override
    public int hashCode() {
      return this.eventType.hashCode() 29 this.sourceType.hashCode();
    }
  }


  /**
   * Helper class that encapsulates a specific set of target listeners,
   * allowing for efficient retrieval of pre-filtered listeners.
   <p>An instance of this helper gets cached per event type and source type.
   */
  private class ListenerRetriever {

    public final Set<ApplicationListener> applicationListeners;

    public final Set<String> applicationListenerBeans;

    private final boolean preFiltered;

    public ListenerRetriever(boolean preFiltered) {
      this.applicationListeners = new LinkedHashSet<ApplicationListener>();
      this.applicationListenerBeans = new LinkedHashSet<String>();
      this.preFiltered = preFiltered;
    }

    public Collection<ApplicationListener> getApplicationListeners() {
      LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
      for (ApplicationListener listener : this.applicationListeners) {
        allListeners.add(listener);
      }
      if (!this.applicationListenerBeans.isEmpty()) {
        BeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : this.applicationListenerBeans) {
          ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
          if (this.preFiltered || !allListeners.contains(listener)) {
            allListeners.add(listener);
          }
        }
      }
      OrderComparator.sort(allListeners);
      return allListeners;
    }
  }

}