/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.servicemix.jms.jca;

import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.MessageExchange;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.TransactionManager;

import org.apache.servicemix.jms.AbstractJmsProcessor;
import org.apache.servicemix.jms.JmsEndpoint;
import org.apache.servicemix.soap.Context;
import org.jencks.SingletonEndpointFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
 */
public class JcaConsumerProcessor extends AbstractJmsProcessor implements MessageListener {

    private final Logger logger = LoggerFactory.getLogger(JcaConsumerProcessor.class);

    protected Map pendingMessages = new ConcurrentHashMap();
    protected ResourceAdapter resourceAdapter;
    protected MessageEndpointFactory endpointFactory;
    protected ActivationSpec activationSpec;
    protected BootstrapContext bootstrapContext;
    protected TransactionManager transactionManager;
    protected ConnectionFactory connectionFactory;
    
    public JcaConsumerProcessor(JmsEndpoint endpoint) throws Exception {
        super(endpoint);
    }

    public void start() throws Exception {
        transactionManager = (TransactionManager) context.getTransactionManager();
        endpointFactory = new SingletonEndpointFactory(this, transactionManager);
        bootstrapContext = endpoint.getBootstrapContext();
        if (bootstrapContext == null) {
            throw new IllegalArgumentException("bootstrapContext not set");
        }
        connectionFactory = endpoint.getConnectionFactory();
        if (connectionFactory == null) {
            throw new IllegalArgumentException("connectionFactory not set");
        }
        activationSpec = endpoint.getActivationSpec();
        if (activationSpec == null) {
            throw new IllegalArgumentException("activationSpec not set");
        }
        resourceAdapter = endpoint.getResourceAdapter();
        if (resourceAdapter == null) {
            resourceAdapter = activationSpec.getResourceAdapter();
            if (resourceAdapter == null) {
                throw new IllegalArgumentException("resourceAdapter not set");
            }
        } else if (activationSpec.getResourceAdapter() == null) {
            activationSpec.setResourceAdapter(resourceAdapter);
        } 
        resourceAdapter.start(bootstrapContext);
        resourceAdapter.endpointActivation(endpointFactory, activationSpec);
    }

    public void stop() throws Exception {
        resourceAdapter.endpointDeactivation(endpointFactory, activationSpec);
        pendingMessages.clear();
    }

    public void onMessage(final Message message) {
        try {
            logger.debug("Received jms message {}", message);
            Context context = createContext();
            MessageExchange exchange = toNMS(message, context);
            if (!(exchange instanceof InOnly)) {
                throw new UnsupportedOperationException("JCA consumer endpoints can only use InOnly MEP");
            }
            exchange.setProperty(MessageExchange.JTA_TRANSACTION_PROPERTY_NAME, transactionManager.getTransaction());
            pendingMessages.put(exchange.getExchangeId(), context);
            if (endpoint.isSynchronous()) {
                channel.sendSync(exchange);
                process(exchange);
            } else {
                endpoint.getServiceUnit().getComponent().prepareExchange(exchange, endpoint);
                channel.send(exchange);
            }
        } catch (Throwable e) {
            logger.error("Error while handling jms message", e);
        }
    }

    public void process(MessageExchange exchange) throws Exception {
        Context context = (Context) pendingMessages.remove(exchange.getExchangeId());
        Message message = (Message) context.getProperty(Message.class.getName());
        Message response = null;
        Connection connection = null;
        try {
            if (exchange.getStatus() == ExchangeStatus.DONE) {
                return;
            } else if (exchange.getStatus() == ExchangeStatus.ERROR) {
                if (endpoint.isRollbackOnError()) {
                    TransactionManager tm = 
                        (TransactionManager) endpoint.getServiceUnit().getComponent().getComponentContext().getTransactionManager();
                    tm.setRollbackOnly();
                    return;
                } else if (exchange instanceof InOnly) {
                    logger.info("Exchange in error: {}", exchange, exchange.getError());
                    return;
                } else {
                    connection = connectionFactory.createConnection();
                    Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
                    Exception error = exchange.getError();
                    if (error == null) {
                        error = new Exception("Exchange in error");
                    }
                    response = session.createObjectMessage(error);
                    MessageProducer producer = session.createProducer(message.getJMSReplyTo());
                    if (endpoint.isUseMsgIdInResponse()) {
                        response.setJMSCorrelationID(message.getJMSMessageID());
                    } else {
                        response.setJMSCorrelationID(message.getJMSCorrelationID());
                    }
                    producer.send(response);
                }
            } else {
                connection = connectionFactory.createConnection();
                Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
                response = fromNMSResponse(exchange, context, session);
                if (response != null) {
                    MessageProducer producer = session.createProducer(message.getJMSReplyTo());
                    if (endpoint.isUseMsgIdInResponse()) {
                        response.setJMSCorrelationID(message.getJMSMessageID());
                    } else {
                        response.setJMSCorrelationID(message.getJMSCorrelationID());
                    }
                    producer.send(response);
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
            if (exchange.getStatus() == ExchangeStatus.ACTIVE) {
                exchange.setStatus(ExchangeStatus.DONE);
                channel.send(exchange);
            }
        }
    }

}
