/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/ejb/message/JmsResourceAdapter.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.ejb.message;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.enterprise.inject.spi.Bean;
import javax.inject.Named;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
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 javax.jms.Topic;
import javax.jms.XAConnection;
import javax.jms.XASession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.ResourceAdapterInternalException;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.xa.XAResource;

import com.caucho.config.ConfigException;
import com.caucho.config.Names;
import com.caucho.config.inject.InjectManager;
import com.caucho.ejb.cfg.JmsActivationConfig;
import com.caucho.env.thread.ThreadPool;
import com.caucho.lifecycle.Lifecycle;
import com.caucho.transaction.UserTransactionProxy;
import com.caucho.util.L10N;

public class JmsResourceAdapter implements ResourceAdapter {
  private static final L10N L = new L10N(JmsResourceAdapter.class);
  private static final Logger log
    = Logger.getLogger(JmsResourceAdapter.class.getName());

  private final static Method _onMessageMethod;
  
  private final JmsActivationConfig _config;
  
  private final String _ejbName;
  
  
  private ConnectionFactory _connectionFactory;
  private Destination _destination;
  
  private UserTransactionProxy _ut;

  private int _consumerMax = 5;
  private int _acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
  
  private String _subscriptionName;
  private String _selector;

  private Connection _connection;
  private MessageEndpointFactory _endpointFactory;

  private ArrayList<Consumer> _consumers;
  
  private Lifecycle _lifecycle = new Lifecycle();

  public JmsResourceAdapter(String ejbName,
                            JmsActivationConfig config)
  {
    _ejbName = ejbName;
    
    _config = config;
    /*
    _connectionFactory = factory;
    */
    
    _destination = config.getDestinationObject();
    
    _ut = UserTransactionProxy.getCurrent();
  }

  public void setMessageSelector(String selector)
  {
    _selector = selector;
  }

  public void setSubscriptionName(String subscriptionName)
  {
    _subscriptionName = subscriptionName;
  }
  
  public void setConsumerMax(int consumerMax)
  {
    _consumerMax = consumerMax;
  }
  
  public void setAcknowledgeMode(int acknowledgeMode)
  {
    _acknowledgeMode = acknowledgeMode;
  }
  
  @Override
  public void start(BootstrapContext ctx)
    throws ResourceAdapterInternalException
  {
  }
  
  private void init()
  {
    if (! _lifecycle.toActive())
      return;
    
    _connectionFactory = getResource(ConnectionFactory.class,
                                     _config.getConnectionFactoryName());
    
    if (_connectionFactory == null)
      throw new ConfigException(L.l("connection-factory must be specified for @MessageDriven bean"));
    
    if (_config.getDestinationType() == null)
      throw new ConfigException(L.l("destination-type must be specified for @MessageDriven bean"));

    if (_destination == null) {
      _destination = getResource(_config.getDestinationType(),
                                 _config.getDestinationName());
    }
    
    if (_destination== null)
      throw new ConfigException(L.l("destination must be specified for @MessageDriven bean"));
  }
  
  /**
   * Called when the resource adapter is stopped.
   */
  @Override
  public void stop()
  {
  }
  
  @SuppressWarnings("unchecked")
  private <T> T getResource(Class<T> type, String name)
  {
    if (name == null) {
    }    
    else if (name.startsWith("java:comp")) {
      try {
        Context ic = new InitialContext();
        
        return (T) ic.lookup(name);
      } catch (NamingException e) {
        throw ConfigException.create(L.l("{0} is an unknown JNDI name for {1}\n  {2}",
                                         name, type.getName(), e.toString()),
                                     e);
      }
    }
    else {
      String jndiName = "java:comp/env/" + name;
      
      try {
        Context ic = new InitialContext();
        
        T value = (T) ic.lookup(jndiName);
        
        if (value != null)
          return value;
      } catch (NamingException e) {
        log.log(Level.FINER, e.toString(), e);
      }
    }
    
    InjectManager beanManager = InjectManager.create();
    
    Set<Bean<?>> beans;

    if (name != null) {
      Named named = Names.create(name);

      beans = beanManager.getBeans(type, named);
    }
    else {
      beans = beanManager.getBeans(type);
    }
    
    Bean<?> bean = beanManager.resolve(beans);
    
    if (bean == null) {
      throw new ConfigException(L.l("'{0}' with name='{1}' is an unknown JMS resource",
                                    type.getName(), name));
    }
    
    return (T) beanManager.getReference(bean);
  }

  /**
   * Called during activation of a message endpoint.
   */
  @Override
  public void endpointActivation(MessageEndpointFactory endpointFactory,
                                 ActivationSpec spec)
    throws NotSupportedException, ResourceException
  {
    init();
    
    synchronized (this) {
      if (_consumers != null)
        throw new java.lang.IllegalStateException();
      _consumers = new ArrayList<Consumer>();
    }

    try {
      assert(_connectionFactory != null);
      assert(_destination != null);
      assert(_consumerMax > 0);

      _endpointFactory = endpointFactory;
    
      Connection connection = _connectionFactory.createConnection();
      _connection = connection;

      if (_destination instanceof Topic)
        _consumerMax = 1;

      _connection.start();

      for (int i = 0; i < _consumerMax; i++) {
        Consumer consumer = new Consumer(_connection, _destination);

        _consumers.add(consumer);

        consumer.start();
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  
  /**
   * Called during deactivation of a message endpoint.
   */
  @Override
  public void endpointDeactivation(MessageEndpointFactory endpointFactory,
                                   ActivationSpec spec)
  {
    try {
      ArrayList<Consumer> consumers = _consumers;
      _consumers = null;

      if (consumers != null) {
        consumers = new ArrayList<Consumer>(consumers);

        for (Consumer consumer : consumers) {
          consumer.destroy();
        }
      }

      if (_connection != null)
        _connection.close();
    } catch (Exception e) {
      log.log(Level.WARNING, e.toString(), e);
    }
  }
  
  /**
   * Called during crash recovery.
   */
  public XAResource []getXAResources(ActivationSpec []specs)
    throws ResourceException
  {
    return new XAResource[0];
  }

  public String toString()
  {
    return getClass().getName() +  "[" + _ejbName + "," + _destination + "]";
  }

  class Consumer implements Runnable {
    private Session _session;
    
    private XAResource _xaResource;
    private MessageConsumer _consumer;
    
    private MessageEndpoint _endpoint;
    private MessageListener _listener;
    
    private boolean _isActive;

    Consumer(Connection conn, Destination destination)
      throws Exception
    {
      if (conn instanceof XAConnection) {
        XASession xaSession = ((XAConnection) conn).createXASession();
        _session = xaSession;
        _xaResource = xaSession.getXAResource();

        /*
        // ejb/09a0 - needs to be auto-ack because if processing throws
        // an exception because of a bug, can't just replay
        boolean transacted = true;
        _session = conn.createSession(transacted, Session.AUTO_ACKNOWLEDGE);
        */
      }
      else {
        boolean transacted = false;
        _session = conn.createSession(transacted, _acknowledgeMode);
      }

      _endpoint = _endpointFactory.createEndpoint(_xaResource);
      
      _listener = (MessageListener) _endpoint;
    }

    /**
     * Creates the session.
     */
    void start()
      throws Exception
    {
      if (_subscriptionName != null) {
        Topic topic = (Topic) _destination;

        _consumer = _session.createDurableSubscriber(topic,
                                                     _subscriptionName,
                                                     _selector,
                                                     true);
      }
      else {
        _consumer = _session.createConsumer(_destination, _selector);
      }
      
      _isActive = true;
      ThreadPool.getCurrent().start(this);
      
      // _consumer.setMessageListener(_listener);
    }
    
    @Override
    public void run()
    {
      while (_isActive) {
        try {
          _endpoint.beforeDelivery(_onMessageMethod);
          try {
            handleMessage();
          } finally {
            _endpoint.afterDelivery();
          }
        } catch (Exception e) {
          log.log(Level.WARNING, e.toString(), e);
        }
      }
    }
    
    private void handleMessage()
    {
      try {
        Message msg = _consumer.receive();
        
        if (msg != null) {
          _listener.onMessage(msg);
        }
      } catch (Exception e) {
        log.log(Level.WARNING, e.toString(), e);
      }
    }

    /**
     * Returns the session.
     */
    public Session getSession()
      throws JMSException
    {
      return _session;
    }

    /**
     * Destroys the listener.
     */
    private void destroy()
      throws JMSException
    {
      _isActive = false;
      
      _endpoint.release();
    }
  }
  
  static {
    Method method = null;
    
    try {
      method = MessageListener.class.getMethod("onMessage", Message.class);
    } catch (Exception e) {
      log.log(Level.WARNING, e.toString(), e);
    }
    
    _onMessageMethod = method;
  }
}
