//Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.resources;
import java.util.Map;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.uima.fit.descriptor.ConfigurationParameter;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.resource.ResourceSpecifier;
import uk.gov.dstl.baleen.exceptions.BaleenException;
import uk.gov.dstl.baleen.uima.BaleenResource;
/**
* <b>Shared resource for accessing ActiveMQ broker</b>
* <p>
* This resource removes the need for individual annotators to establish their
* own connections to ActiveMQ, instead providing a single instance of
* MessageProducer and MessageConsumer for Baleen that can be used. This
* provides benefits such as reduced configuration and reduced repeated code.
* <br/>
* Creation of Connection, Session, MessageProducer, and MessageConsumer
* typically require individual request/response interactions with the broker so
* are best created upfront.<br/>
* Note: MessageProducer can be used with multiple destinations
* </p>
*
* @baleen.javadoc
*/
public class SharedActiveMQResource extends BaleenResource {
public static final String DEFAULT_PROTOCOL = "tcp";
public static final String DEFAULT_HOST = "localhost";
public static final String DEFAULT_PORT_STRING = "61616";
public static final String DEFAULT_BROKER_ARGS = "";
public static final String DEFAULT_TOPIC = "baleen";
public static final String DEFAULT_USER = "";
public static final String DEFAULT_PASS = "";
/**
* The protocol to use for ActiveMQ broker connection
*
* @baleen.config tcp
*/
public static final String PARAM_PROTOCOL = "activemq.protocol";
@ConfigurationParameter(name = PARAM_PROTOCOL, defaultValue = DEFAULT_PROTOCOL)
private String activeMQProtocol;
/**
* The ActiveMQ broker host to connect to
*
* @baleen.config localhost
*/
public static final String PARAM_HOST = "activemq.host";
@ConfigurationParameter(name = PARAM_HOST, defaultValue = DEFAULT_HOST)
private String activeMQHost;
/**
* The ActiveMQ broker port to connect to
*
* @baleen.config 61616
*/
public static final String PARAM_PORT = "activemq.port";
@ConfigurationParameter(name = PARAM_PORT, defaultValue = DEFAULT_PORT_STRING)
private String activeMQPortString;
/**
* The ActiveMQ broker arguments as a single string
*
* @baleen.config
*/
public static final String PARAM_BROKERARGS = "activemq.brokerargs";
@ConfigurationParameter(name = PARAM_BROKERARGS, defaultValue = DEFAULT_BROKER_ARGS)
private String activeMQBrokerArgs;
/**
* The ActiveMQ endpoint to connect to (e.g. queue:foo.bar)
*
* @baleen.config baleen
*/
public static final String PARAM_DB = "activemq.topic";
@ConfigurationParameter(name = PARAM_DB, defaultValue = DEFAULT_TOPIC)
private String activeMQTopic;
/**
* The username to use for authentication. If left blank, then
* authentication will not be used.
*
* @baleen.config
*/
public static final String PARAM_USER = "activemq.user";
@ConfigurationParameter(name = PARAM_USER, defaultValue = DEFAULT_USER)
private String activeMQUser;
/**
* The password to use for authentication. If left blank, then
* authentication will not be used.
*
* @baleen.config
*/
public static final String PARAM_PASS = "activemq.pass";
@ConfigurationParameter(name = PARAM_PASS, defaultValue = DEFAULT_PASS)
private String activeMQPass;
protected Connection connection;
protected Session session;
protected MessageProducer messageProducer;
@Override
protected boolean doInitialize(ResourceSpecifier aSpecifier, Map<String, Object> aAdditionalParams) throws ResourceInitializationException {
try {
connection = createConnection(activeMQProtocol, activeMQHost, activeMQPortString, activeMQBrokerArgs, activeMQUser, activeMQPass);
connection.start();
session = createSession(connection);
messageProducer = createMessageProducer(session);
} catch (JMSException jmsException) {
throw new ResourceInitializationException(new BaleenException("Error connecting to ActiveMQ", jmsException));
}
getMonitor().info("Initialised shared ActiveMQ resource");
return true;
}
/**
* Creates and returns an ActiveMQ session for use in creating
* producers/consumers. This method handles the creation of the connection
* factory and connection
*
* @throws JMSException
*/
private Connection createConnection(String protocol, String host, String port, String brokerArgs, String username, String password) throws JMSException {
String brokerUri = constructBrokerUri(protocol, host, port, brokerArgs);
// Create connection factory
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(brokerUri);
connectionFactory.setUserName(username);
connectionFactory.setPassword(password);
// Create a connection
connection = connectionFactory.createConnection();
return connection;
}
/**
* Create and return a session
*
* @throws JMSException
*/
private Session createSession(Connection connection) throws JMSException {
// Create and return a session
return connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
}
/**
* Create a new message producer, using a null destination so as to allow
* multiple destinations on specification at send time
*
* @throws JMSException
*/
private MessageProducer createMessageProducer(Session session) throws JMSException {
return session.createProducer(null);
}
/**
* creates and returns a MessageConsumer object consuming from the given
* endpoint. Only messages matching the selector are delivered.
*
* @param queueName
* The name of the queue (or VirtualTopic) to consume from
* @param messageSelector
* Only messages matching the selector are delivered
*/
public MessageConsumer createConsumer(String queueName, String messageSelector) throws JMSException {
Destination destination = session.createQueue(queueName);
return session.createConsumer(destination, messageSelector);
}
/**
* returns a MessageProducer for use in sending messages. The producer has
* no preset destination and can be used to send to any destination
* specified at send-time
*/
public MessageProducer getProducer() {
return messageProducer;
}
/**
* returns a Session, mainly for use in creating destinations
*
*/
public Session getSession() {
return session;
}
/**
* creates and returns a QueueBrowser object browser the given endpoint.
* Only messages matching the selector are delivered.
*
* @param queueName
* The name of the queue (or VirtualTopic) to consume from
* @param messageSelector
* Only messages matching the selector are delivered
* @throws JMSException
*/
public QueueBrowser createQueueBrowser(String queueName, String messageSelector) throws JMSException {
Queue queue = session.createQueue(queueName);
return session.createBrowser(queue, messageSelector);
}
@Override
protected void doDestroy() {
getMonitor().debug("Disconnecting from ActiveMQ");
try {
session.close();
connection.close();
} catch (JMSException e) {
getMonitor().error("Could not close connection to ActiveMQ", e);
}
}
private String constructBrokerUri(String protocol, String host, String port, String brokerArgs) {
StringBuilder sb = new StringBuilder();
sb.append(protocol);
sb.append("://");
sb.append(host);
if (!port.isEmpty()) {
sb.append(":");
sb.append(port);
}
sb.append("?");
sb.append(brokerArgs);
return sb.toString();
}
}