/**
 *
 * 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 EDU.oswego.cs.dl.util.concurrent.Latch;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.ActiveMQConnectionConsumer;
import org.codehaus.activemq.ActiveMQSession;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ActiveMQQueue;
import org.codehaus.activemq.message.ActiveMQTopic;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.XASession;
import javax.resource.ResourceException;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkEvent;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkListener;
import javax.resource.spi.work.WorkManager;
import javax.transaction.xa.XAResource;

/**
 * @version $Revision: 1.7 $ $Date: 2004/12/01 03:03:11 $
 */
public class ActiveMQPollingEndpointWorker extends ActiveMQBaseEndpointWorker implements Work {

    private static final Log log = LogFactory.getLog(ActiveMQPollingEndpointWorker.class);
    private static final int MAX_WORKERS = 10;

    private SynchronizedBoolean started = new SynchronizedBoolean(false);
    private SynchronizedBoolean stopping = new SynchronizedBoolean(false);
    private Latch stopLatch = new Latch();

    private ActiveMQConnectionConsumer consumer;

    private CircularQueue workers;

    static WorkListener debugingWorkListener = new WorkListener() {
        //The work listener is useful only for debugging...
        public void workAccepted(WorkEvent event) {
        }

        public void workRejected(WorkEvent event) {
            log.warn("Work rejected: " + event, event.getException());
        }

        public void workStarted(WorkEvent event) {
        }

        public void workCompleted(WorkEvent event) {
        }
    };
    private Connection connection;

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

    public void start() throws WorkException, ResourceException {
        ActiveMQActivationSpec activationSpec = endpointActivationKey.getActivationSpec();        
        boolean ok = false;
        try {
            
            connection = adapter.makeConnection(activationSpec);
            connection.start();
            
            workers = new CircularQueue(MAX_WORKERS, stopping);
            for (int i = 0; i < workers.size(); i++) {
                
                
                int acknowledge = (transacted) ? Session.SESSION_TRANSACTED : activationSpec.getAcknowledgeModeForSession();
                Session session = connection.createSession(transacted, acknowledge);
                XAResource xaresource = null;
                if (session instanceof XASession) {
                    if (!transacted) {
                        throw new ResourceException("You cannot use an XA Connection with a non transacted endpoint.");
                    }
                    xaresource = ((XASession) session).getXAResource();
                }

                MessageEndpoint endpoint = endpointFactory.createEndpoint(xaresource);
                workers.returnObject(new InboundEndpointWork((ActiveMQSession) session, endpoint, workers));
            }

            Destination dest = null;
            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());
            }

            if (emptyToNull(activationSpec.getSubscriptionName()) != null) {
                consumer = (ActiveMQConnectionConsumer) connection.createDurableConnectionConsumer((Topic) dest, activationSpec.getSubscriptionName(), emptyToNull(activationSpec.getMessageSelector()), null, 0);
            }
            else {
                consumer = (ActiveMQConnectionConsumer) connection.createConnectionConsumer(dest, emptyToNull(activationSpec.getMessageSelector()), null, 0);
            }

            ok = true;
            log.debug("Started");

            workManager.scheduleWork(this, WorkManager.INDEFINITE, null, debugingWorkListener);
            ok = true;

        }
        catch (JMSException e) {
            throw new ResourceException("Could not start the endpoint.", e);
        }
        finally {

            // We don't want to leak sessions on errors.  Keep them around only if
            // there were no errors.
            if (!ok) {
                safeClose(consumer);
                safeClose(connection);
            }
        }

    }

    private String emptyToNull(String value) {
        if ("".equals(value)) {
            return null;
        }
        return value;
    }

    /**
     *
     */
    public void stop() throws InterruptedException {
        stopping.set(true);
        workers.notifyWaiting();
        if (started.compareTo(true) == 0) {
            stopLatch.acquire();
        }
        safeClose(consumer);
        safeClose(connection);
    }

    /**
     * @see javax.resource.spi.work.Work#release()
     */
    public void release() {
    }

    /**
     * The WorkManager has started up and we now need to pull message off
     * the destination and push them to an endpoint.
     *
     * @see java.lang.Runnable#run()
     */
    public void run() {
        started.set(true);
        try {

            while (!stopping.get()) {
                ActiveMQMessage message = consumer.receive(500);
                if (message != null) {
                    InboundEndpointWork worker = (InboundEndpointWork) workers.get();
                    // Did we get stopped?
                    if (worker == null) {
                        break;
                    }
                    worker.setMessage(message);
                    workManager.scheduleWork(worker, WorkManager.INDEFINITE, null, debugingWorkListener);
                }
            }   
            
            // Try to collect the workers so that none are running.
            workers.drain();

        }
        catch (Throwable e) {
            log.info("dispatcher: ", e);
        }
        finally {
            stopLatch.release();
        }
    }
}
