/**
 * 
 * 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.transport;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.ActiveMQMessageProducer;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQMessage;
import org.activemq.service.Service;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;

/**
 * A NetworkMessageBridge consumes messages from a remote broker and daisy chains them to the local message producer,
 * which will pass them into the local broker for consumption
 * 
 * @version $Revision: 1.1.1.1 $
 */
class NetworkMessageBridge implements Service, MessageListener {
    private String localBrokerName;
    private ActiveMQMessageProducer localProducer;
    private MessageConsumer remoteConsumer;
    private Session localSession;
    private Session remoteSession;
    private boolean stopped;
    private boolean durableTopic;
    private ActiveMQDestination destination;
    private SynchronizedInt referenceCount;
    private static final Log log = LogFactory.getLog(NetworkMessageBridge.class);

    /**
     * Construct the NetworkMessageConsumer
     */
    public NetworkMessageBridge() {
        this.referenceCount = new SynchronizedInt(0);
    }

    /**
     * sett the durable value of the consumer
     * 
     * @param durableTopic
     */
    public void setDurableTopic(boolean durableTopic) {
        this.durableTopic = durableTopic;
    }

    /**
     * @return true if a durable consumer
     */
    public boolean isDurableTopic() {
        return this.durableTopic;
    }

    /**
     * @return Returns the destination.
     */
    public ActiveMQDestination getDestination() {
        return destination;
    }

    /**
     * @param destination The destination to set.
     */
    public void setDestination(ActiveMQDestination destination) {
        this.destination = destination;
    }

    /**
     * @return Returns the localBrokerName.
     */
    public String getLocalBrokerName() {
        return localBrokerName;
    }

    /**
     * @param localBrokerName The localBrokerName to set.
     */
    public void setLocalBrokerName(String localBrokerName) {
        this.localBrokerName = localBrokerName;
    }

    /**
     * @return Returns the localSession.
     */
    public Session getLocalSession() {
        return localSession;
    }

    /**
     * @param localSession The localSession to set.
     */
    public void setLocalSession(Session localSession) {
        this.localSession = localSession;
    }

    /**
     * @return Returns the remoteSession.
     */
    public Session getRemoteSession() {
        return remoteSession;
    }

    /**
     * @param remoteSession The remoteSession to set.
     */
    public void setRemoteSession(Session remoteSession) {
        this.remoteSession = remoteSession;
    }

    /**
     * increment number of references to this consumer
     * 
     * @return the number of references
     */
    public int incrementReferenceCount() {
        return referenceCount.increment();
    }

    /**
     * decrement number of references to this consumer
     * 
     * @return the number of references
     */
    public int decrementReferenceCount() {
        return referenceCount.decrement();
    }

    /**
     * start the bridge
     * 
     * @throws JMSException
     */
    public void start() throws JMSException {
        localProducer = (ActiveMQMessageProducer) localSession.createProducer(null);
        localProducer.setReuseMessageId(true);
        createRemoteConsumer();
    }

    private void createRemoteConsumer() throws JMSException {
        if (isDurableTopic()) {
            String subsName = destination.toString() + "@" + localBrokerName;
            remoteConsumer = remoteSession.createDurableSubscriber((javax.jms.Topic) destination, subsName);
        }
        else {
            remoteConsumer = remoteSession.createConsumer(destination);
        }
        remoteConsumer.setMessageListener(NetworkMessageBridge.this);
    }

    /**
     * stop the bridge
     */
    public void stop() {
        if (!stopped) {
            stopped = true;
            referenceCount.set(0);
            try {
                localSession.close();
                remoteSession.close();
            }
            catch (JMSException jmsEx) {
                log.warn("failure in stopping the message bridge", jmsEx);
            }
        }
    }

    /**
     * Upgrade the remote consumer from a non-durable consumer to a durable consumer
     */
    public void upgrade() throws JMSException {
        try {
            remoteConsumer.close();
        } catch (JMSException e) {
            log.warn("Failure in stopping the remote session for the message bridge", e);
        }
        durableTopic = true;
        createRemoteConsumer();
    }

    /**
     * @param msg consumed message from remote broker
     */
    public void onMessage(Message msg) {
        try {
            if (!stopped) {
                ActiveMQMessage message = (ActiveMQMessage) msg;
                if (message != null) {
                    message = message.shallowCopy();
                    message.addBrokerVisited(localBrokerName);
                    Destination destination = message.getJMSDestination();
                    int deliveryMode = message.getJMSDeliveryMode();
                    int priority = message.getJMSPriority();
                    long timeToLive = message.getJMSExpiration() - msg.getJMSTimestamp();
                    localProducer.send(destination, message, deliveryMode, priority, timeToLive);
                    //acknowledge the original
                    msg.acknowledge();
                }
            }
        }
        catch (JMSException jmsEx) {
            if (!stopped){
                log.error("NetworkMessageConsumer failed", jmsEx);
                stop();
            }
        }
    }

    /**
     * @return hash code for this object
     */
    public int hashCode() {
        return destination.hashCode();
    }

    /**
     * @param obj
     * @return true if the obj and this are equal
     */
    public boolean equals(Object obj) {
        boolean result = false;
        if (obj != null && obj instanceof NetworkMessageBridge) {
            NetworkMessageBridge other = (NetworkMessageBridge) obj;
            result = this.destination.equals(other.destination) && this.isDurableTopic() == other.isDurableTopic();
        }
        return result;
    }
}