/*
* 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;
}
}