/**
 *
 * Copyright 2004 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.codehaus.activemq.ra;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.message.ActiveMQDestination;
import org.codehaus.activemq.message.ActiveMQQueue;
import org.codehaus.activemq.message.ActiveMQTopic;

import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.Topic;
import javax.resource.ResourceException;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkManager;

/**
 * @version $Revision: 1.11.2.1 $ $Date: 2004/12/14 16:30:52 $
 */
public class ActiveMQAsfEndpointWorker extends ActiveMQBaseEndpointWorker {

    private static final Log log = LogFactory.getLog(ActiveMQAsfEndpointWorker.class);

    private static final int MAX_MSGS_PER_SESSION = 1;
    private static final long INITIAL_RECONNECT_DELAY = 1000; // 1 second.
    private static final long MAX_RECONNECT_DELAY = 1000*30; // 30 seconds.
    private static final ThreadLocal threadLocal = new ThreadLocal();
    
    private ConnectionConsumer consumer;
    private ServerSessionPoolImpl serverSessionPool;
    private ActiveMQDestination dest;
    private boolean running;
    private Work connectWork;
    protected Connection connection;
    
    private long reconnectDelay=INITIAL_RECONNECT_DELAY;

    /**
     * @param adapter
     * @param key
     * @throws ResourceException
     */
    public ActiveMQAsfEndpointWorker(final ActiveMQResourceAdapter adapter, ActiveMQEndpointActivationKey key)
            throws ResourceException {
        super(adapter, key);

        connectWork = new Work() {

            public void release() {
            }

            synchronized public void run() {
                if( !isRunning() )
                    return;
                if( connection!=null )
                    return;
                
                ActiveMQActivationSpec activationSpec = endpointActivationKey.getActivationSpec();
                try {
                    connection = adapter.makeConnection(activationSpec);
                    connection.start();
                    connection.setExceptionListener(new ExceptionListener() {
                        public void onException(JMSException error) {
                            reconnect(error);
                        }
                    });

                    if (activationSpec.isDurableSubscription()) {
                        consumer = connection.createDurableConnectionConsumer((Topic) dest, activationSpec
                                .getSubscriptionId(), emptyToNull(activationSpec.getMessageSelector()),
                                serverSessionPool, MAX_MSGS_PER_SESSION);
                    } else {
                        consumer = connection.createConnectionConsumer(dest, emptyToNull(activationSpec
                                .getMessageSelector()), serverSessionPool, MAX_MSGS_PER_SESSION);
                    }

                } catch (JMSException error) {
                    reconnect(error);
                }
            }
        };

        ActiveMQActivationSpec activationSpec = endpointActivationKey.getActivationSpec();
        if ("javax.jms.Queue".equals(activationSpec.getDestinationType())) {
            dest = new ActiveMQQueue(activationSpec.getDestination());
        } else if ("javax.jms.Topic".equals(activationSpec.getDestinationType())) {
            dest = new ActiveMQTopic(activationSpec.getDestination());
        } else {
            throw new ResourceException("Unknown destination type: " + activationSpec.getDestinationType());
        }
    }

    synchronized public void start() throws WorkException, ResourceException {
        if (running)
            return;
        running = true;

        log.debug("Starting");
        serverSessionPool = new ServerSessionPoolImpl(this);
        connect();
        log.debug("Started");
    }

    /**
     * 
     */
    synchronized public void stop() throws InterruptedException {
        if (!running)
            return;
        running = false;
        serverSessionPool.close();
        disconnect();        
    }

    private boolean isRunning() {
        return running;
    }    

    synchronized private void connect() {
        if (!running)
            return;

        try {
            workManager.scheduleWork(connectWork, WorkManager.INDEFINITE, null, null);
        } catch (WorkException e) {
            running = false;
            log.error("Work Manager did not accept work: ",e);
        }
    }

    /**
     * 
     */
    synchronized private void disconnect() {
        safeClose(consumer);
        consumer=null;
        safeClose(connection);
        connection=null;
    }

    synchronized private void reconnect(JMSException error) {
        log.debug("Reconnect cause: ", error);
        // Only log errors if the server is really down..  And not a temp failure. 
        if( reconnectDelay == MAX_RECONNECT_DELAY ) {
            log.info("Endpoint connection to JMS broker failed: "+error.getMessage());
            log.info("Endpoint will try to reconnect to the JMS broker in "+(MAX_RECONNECT_DELAY/1000)+" seconds");
        }
        try {
            disconnect();
            Thread.sleep(reconnectDelay);
            
            // Use exponential rollback.
            reconnectDelay*=2;
            if( reconnectDelay > MAX_RECONNECT_DELAY )
                reconnectDelay = MAX_RECONNECT_DELAY;
            
            connect();
        } catch (InterruptedException e) {
        }
    }

    protected void registerThreadSession(Session session) {
        threadLocal.set(session);
    }

    protected void unregisterThreadSession(Session session) {
        threadLocal.set(null);
    }

    private String emptyToNull(String value) {
        if (value == null || value.length() == 0) {
            return null;
        }
        return value;
    }

}
