/* * Copyright 2007 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.springmodules.xt.model.event.filtering; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.log4j.Logger; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.core.task.TaskExecutor; import org.springmodules.xt.model.event.filtering.support.internal.FilteringApplicationListenerAdapter; /** * {@link org.springframework.context.event.ApplicationEventMulticaster} implementation that used in conjunction * with {@link FilteringApplicationListener}s permits to efficiently filter published {@link org.springframework.context.ApplicationEvent}s, * in order to avoid notifying <b>every</b> listener of <b>every</b> published event, and in order to provide a better and clearer * separation of event filtering and processing, by decoupling the filtering logic from the processing one. * <br><br> * Registered {@link FilteringApplicationListener}s are notified only of those events whose class is supported by the listener * (see {@link FilteringApplicationListener#getSupportedEventClasses()}) and that are accepted * by the listener itself (see {@link FilteringApplicationListener#accepts(ApplicationEvent )}).<br> * Every other kind of registered {@link org.springframework.context.ApplicationListener} implementation will be notified of any event, * without applying any filtering logic. * <br><br> * All kind of listeners are by default executed in the calling thread; if you want to execute them in another thread, please * use a proper {@link org.springframework.core.task.TaskExecutor} object, configuring it through the {@link #setTaskExecutor(TaskExecutor)} * method. * <br><br> * This class is completely <b>thread-safe</b>, optimized for concurrent scenarios where event multicasting operations vastly outnumber * listener adding and removal operations.<br> * Moreover, the event multicasting process (see {@link #multicastEvent(ApplicationEvent )}) * is guaranteed to be <i>atomic</i> and <i>isolated</i> in respect to the adding and removal of listeners. * * @see FilteringApplicationListener * @see FilteringApplicationListener#getSupportedEventClasses() * @see FilteringApplicationListener#accepts(ApplicationEvent ) * * @author Sergio Bossa */ public class FilteringApplicationEventMulticaster implements ApplicationEventMulticaster { private static final Logger logger = Logger.getLogger(FilteringApplicationEventMulticaster.class); private final Map<Class, Set<FilteringApplicationListener>> listenersMap = new HashMap<Class, Set<FilteringApplicationListener>>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock readLock = rwl.readLock(); private final Lock writeLock = rwl.writeLock(); private TaskExecutor taskExecutor; public void multicastEvent(final ApplicationEvent event) { this.readLock.lock(); try { if (logger.isDebugEnabled()) { logger.debug(new StringBuilder("Multicasting event: ").append(event)); } for (Map.Entry<Class, Set<FilteringApplicationListener>> entry : this.listenersMap.entrySet()) { Class supportedClass = entry.getKey(); if (supportedClass.isAssignableFrom(event.getClass())) { Set<FilteringApplicationListener> listeners = entry.getValue(); for (final FilteringApplicationListener listener : listeners) { try { if (listener.accepts(event)) { if (this.taskExecutor == null) { listener.onApplicationEvent(event); } else { this.taskExecutor.execute(new Runnable() { public void run() { listener.onApplicationEvent(event); } }); } } } catch (Exception ex) { logger.warn(new StringBuilder("Exception while processing the following event: ").append(event)); logger.warn(ex.getMessage(), ex); } } } } } finally { this.readLock.unlock(); } } public void addApplicationListener(ApplicationListener listener) { this.writeLock.lock(); try { FilteringApplicationListener filteringListener = null; if (listener instanceof FilteringApplicationListener) { filteringListener = (FilteringApplicationListener) listener; } else { filteringListener = new FilteringApplicationListenerAdapter(listener); } Class[] supportedClasses = filteringListener.getSupportedEventClasses(); for (Class clazz : supportedClasses) { if (this.listenersMap.containsKey(clazz)) { this.listenersMap.get(clazz).add(filteringListener); } else { Set<FilteringApplicationListener> set = new LinkedHashSet<FilteringApplicationListener>(); set.add(filteringListener); this.listenersMap.put(clazz, set); } } } finally { this.writeLock.unlock(); } } public void removeApplicationListener(ApplicationListener listener) { this.writeLock.lock(); try { FilteringApplicationListener filteringListener = null; if (listener instanceof FilteringApplicationListener) { filteringListener = (FilteringApplicationListener) listener; } else { filteringListener = new FilteringApplicationListenerAdapter(listener); } Collection<Set<FilteringApplicationListener>> listenersCollection = this.listenersMap.values(); for (Set<FilteringApplicationListener> listeners : listenersCollection) { listeners.remove(filteringListener); } } finally { this.writeLock.unlock(); } } public void removeAllListeners() { this.writeLock.lock(); try { this.listenersMap.clear(); } finally { this.writeLock.unlock(); } } /** * Set the {@link org.springframework.core.task.TaskExecutor} object to use for executing * application listeners */ public TaskExecutor getTaskExecutor() { return this.taskExecutor; } /** * Set the {@link org.springframework.core.task.TaskExecutor} object to use for executing * application listeners in an another thread context, without blocking the current calling thread.<br> * If not set, listeners will be executed in the calling thread. */ public void setTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } }