/*
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see 
* 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. * *
 * Implementing ApplicationEventMulticaster's actual {@link #multicastEvent}
 * method is left to subclasses. {@link org.springframework.context.event.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 org.springframework.context.event.SimpleApplicationEventMulticaster
 */
public class SafeApplicationEventMulticaster implements ApplicationEventMulticaster, ApplicationContextAware
{
    private final Log log = LogFactory.getLog(SafeApplicationEventMulticaster.class);
    private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
    private final Map 
     * Default is a SyncTaskExecutor, executing the listeners synchronously in
     * the calling thread.
     *  
     * Consider specifying an asynchronous TaskExecutor here to not block the
     * caller until all listeners have been executed. However, note that
     * asynchronous execution will not participate in the caller's thread
     * context (class loader, transaction association) unless the TaskExecutor
     * explicitly supports this.
     * 
     */
    public void setTaskExecutor(Executor taskExecutor)
    {
        this.taskExecutor = taskExecutor;
    }
    /**
     * Return the current TaskExecutor for this multicaster.
     */
    protected Executor getTaskExecutor()
    {
        return this.taskExecutor;
    }
    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();
        }
    }
    private BeanFactory getBeanFactory()
    {
        if (this.appContext == null)
        {
            throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans "
                    + "because it is not associated with a BeanFactory");
        }
        return this.appContext;
    }
    @Override
    public void multicastEvent(ApplicationEvent event)
    {
        if (event instanceof ContextRefreshedEvent && event.getSource() == this.appContext)
        {
            this.isApplicationStarted = true;
            for (ApplicationEvent queuedEvent : this.queuedEvents)
            {
                multicastEventInternal(queuedEvent);
            }
            this.queuedEvents.clear();
            multicastEventInternal(event);
        }
        else if (event instanceof ContextClosedEvent && event.getSource() == this.appContext)
        {
            this.isApplicationStarted = false;
            multicastEventInternal(event);
        }
        else if (this.isApplicationStarted)
        {
            multicastEventInternal(event);
        }
        else
        {
            this.queuedEvents.add(event);
        }
    }
    @SuppressWarnings("unchecked")
    protected void multicastEventInternal(final ApplicationEvent event) {
        for (final ApplicationListener listener : getApplicationListeners(event)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    public void run() {
                        listener.onApplicationEvent(event);
                    }
                });
            }
            else {
                listener.onApplicationEvent(event);
            }
        }
    }
    /**
     * Return a Collection containing all ApplicationListeners.
     * 
     * @return a Collection of ApplicationListeners
     * @see org.springframework.context.ApplicationListener
     */
    protected Collection 
     * 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 ? (SmartApplicationListener) listener
                : 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 (other == null)
                return false;
            if (this == other)
            {
                return true;
            }
            ListenerCacheKey otherKey = (ListenerCacheKey) other;
            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.
     *  
     * An instance of this helper gets cached per event type and source type.
     */
    private class ListenerRetriever
    {
        public final Set