/** 
 * 
 * Copyright 2005 Hiram Chirino
 * 
 * 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 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.activemq.message.ActiveMQDestination;
import org.activemq.message.ConsumerInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;

/**
 * A ProducerDemandAdvisor is used to know when a destination is in demand.
 * 
 * Sometimes generating messages to send to a destination is very expensive 
 * and the application would like to avoid producing messages if there are no 
 * active consumers for the destination.  There is a "demand" for messages
 * when a consumer does come active.
 * 
 * This object uses Advisory messages to know when consumer go active and 
 * inactive.
 */
public class ProducerDemandAdvisor {
    
    private static final Log log = LogFactory.getLog(ProducerDemandAdvisor.class);

    private final ActiveMQDestination destination;
    private Connection connection;
    private Session session;
    private SynchronizedBoolean started = new SynchronizedBoolean(false);
    private int consumerCount;
    private ProducerDemandListener demandListener;
    
    public ProducerDemandAdvisor( Connection connection, final Destination destination ) throws JMSException {
        this.connection = connection;
        this.destination = ActiveMQDestination.transformDestination(destination);
    }
     
    /**
     * @param destination
     */
    private void fireDemandEvent() {
        demandListener.onEvent( new ProducerDemandEvent(destination, isInDemand()));
    }

    public boolean isInDemand() {
        return consumerCount>0;
    }
    
    public ProducerDemandListener getDemandListener() {
        return demandListener;
    }

    synchronized public void setDemandListener(ProducerDemandListener demandListener) {
        this.demandListener = demandListener;
        fireDemandEvent();
    }

    public void start() throws JMSException {
        if (started.commit(false, true)) {
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageConsumer consumer = session.createConsumer(destination.getTopicForConsumerAdvisory());
            consumer.setMessageListener(new MessageListener(){
                public void onMessage(Message msg) {
                    process(msg);
                }
            });
        }
    }

    public void stop() throws JMSException {
        if (started.commit(true, false)) {
            if (session != null) {
                session.close();
            }
        }
    }
    
    protected void process(Message msg) {
        if (msg instanceof ObjectMessage) {
            try {
                ConsumerInfo info = (ConsumerInfo) ((ObjectMessage) msg).getObject();
                ConsumerAdvisoryEvent event = new ConsumerAdvisoryEvent(info);
                
                
                boolean inDemand = isInDemand();

                if ( info.isStarted() ) {
                    consumerCount++;
                } else {
                    consumerCount--;
                }
                
                // Notify listener if there was a change in demand.
                if (inDemand ^ isInDemand() && demandListener != null) {
                    fireDemandEvent();
                }
            } catch (JMSException e) {
                log.error("Failed to process message: " + msg);
            }
        }
    }

}
