/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * 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.activemq.advisories;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.ActiveMQConnection;
import org.activemq.ActiveMQSession;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQTopic;
import org.activemq.message.ConnectionInfo;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;

/**
 * A helper class for listening for MessageConnection advisories *
 * 
 * @version $Revision: 1.1.1.1 $
 */
public class ConnectionAdvisor implements MessageListener {
    private static final Log log = LogFactory.getLog(ConnectionAdvisor.class);
    private Connection connection;
    private Session session;
    private List listeners = new CopyOnWriteArrayList();
    private Map activeConnections = new HashMap();
    private SynchronizedBoolean started = new SynchronizedBoolean(false);
    private Object lock = new Object();

    /**
     * Construct a ConnectionAdvisor
     * 
     * @param connection
     * @throws JMSException
     */
    public ConnectionAdvisor(Connection connection) throws JMSException {
        this.connection = connection;
    }

    /**
     * start listening for advisories
     * 
     * @throws JMSException
     */
    public void start() throws JMSException {
        if (started.commit(false, true)) {
            
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            
            String advisoryName = ActiveMQDestination.CONNECTION_ADVISORY_PREFIX;
            Destination advisoryDestination = new ActiveMQTopic(advisoryName);
            MessageConsumer consumer = session.createConsumer(advisoryDestination);
            consumer.setMessageListener(this);
        }
    }

    /**
     * stop listening for advisories
     * 
     * @throws JMSException
     */
    public void stop() throws JMSException {
        if (started.commit(true, false)) {
            if (session != null) {
                session.close();
            }
            synchronized (lock) {
                lock.notifyAll();
            }
        }
    }

    /**
     * Add a listener
     * 
     * @param l
     */
    public void addListener(ConnectionAdvisoryEventListener l) {
        listeners.add(l);
    }

    /**
     * Remove a listener
     * 
     * @param l
     */
    public void removeListener(ConnectionAdvisoryEventListener l) {
        listeners.remove(l);
    }

    /**
     * returns true if the connection is active
     * 
     * @param clientId for the connection
     * @return true if the connection is active
     */
    public boolean isActive(String clientId) {
        return activeConnections.containsKey(clientId);
    }

    /**
     * Retrive all current Connections
     * 
     * @return
     */
    public Set getConnections() {
        Set set = new HashSet();
        set.addAll(activeConnections.values());
        return set;
    }

    /**
     * Waits until the number of active connections is equivalent to the number supplied, or the timeout is exceeded
     * 
     * @param number
     * @param timeout
     * @return the number of activeConnections
     */
    public int waitForActiveConnections(int number, long timeout) {
        int result = 0;
        // if timeInMillis is less than zero assume nowait
        long waitTime = timeout;
        long start = (timeout <= 0) ? 0 : System.currentTimeMillis();
        synchronized (lock) {
            while (started.get()) {
                result = numberOfActiveConnections();
                if (result == number || waitTime <= 0) {
                    break;
                }
                else {
                    try {
                        lock.wait(waitTime);
                    }
                    catch (Throwable e) {
                        log.debug("Interrupted", e);
                        e.printStackTrace();
                    }
                    waitTime = timeout - (System.currentTimeMillis() - start);
                }
            }
        }
        return result;
    }

    /**
     * return the current number of active connections
     * 
     * @return
     */
    public int numberOfActiveConnections() {
        return activeConnections.size();
    }

    /**
     * OnMessage() implementation
     * 
     * @param msg
     */
    public void onMessage(Message msg) {
        if (msg instanceof ObjectMessage) {
            try {
                ConnectionInfo info = (ConnectionInfo) ((ObjectMessage) msg).getObject();
                
                ConnectionAdvisoryEvent event = new ConnectionAdvisoryEvent(info);
                if (!event.getInfo().isClosed()) {
                    activeConnections.put(event.getInfo().getClientId(), event.getInfo());
                }
                else {
                    activeConnections.remove(event.getInfo().getClientId());
                }
                synchronized (lock) {
                    lock.notify();
                }
                fireEvent(event);
            }
            catch (Throwable e) {
                log.error("Failed to process message: " + msg);
            }
        }
    }

    private void fireEvent(ConnectionAdvisoryEvent event) {
        for (Iterator i = listeners.iterator();i.hasNext();) {
            ConnectionAdvisoryEventListener l = (ConnectionAdvisoryEventListener) i.next();
            l.onEvent(event);
        }
    }
}