/**
* Copyright 2008-2009 Dan Pritchett
*
* 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.addsimplicity.anicetus.io.jms;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import org.addsimplicity.anicetus.entity.GlobalInfo;
import org.addsimplicity.anicetus.io.DeliveryAdapter;
import org.addsimplicity.anicetus.io.ExceptionHandler;
import org.addsimplicity.anicetus.io.SystemErrorExceptionHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
/**
* The JMS delivery adapter publishes telemetry to a JMS topic or queue. The
* delivery is done using Spring's JMS templates. Telemetry is published on a
* separate thread, asynchronously to the primary application flows. This is
* done to minimize any overhead that may be introduced into the main
* application processing.
*
* Telemetry artifacts are queued and delivered by worker threads. The
* application can control the number of worker threads as well as the size of
* the queue. Additionally, the behavior if the queue overruns can be
* controlled. Telemetry can either be discarded if there is no further queue
* space or the calling thread will be used to delivery the artifact.
*
* Messages are converted to the desired format using a Spring message converter
* implementation.
*
* @author Dan Pritchett (driveawedge@yahoo.com)
*
*/
public class JMSDeliveryAdapter implements DeliveryAdapter, InitializingBean {
class DeliveryTask implements Runnable {
GlobalInfo m_telemetry;
public DeliveryTask(GlobalInfo telemetry) {
super();
m_telemetry = telemetry;
}
public void run() {
try {
m_template.convertAndSend(m_telemetry);
}
catch (Throwable t) {
m_handler.exceptionCaught(t);
}
}
}
private ConnectionFactory m_connectionFactory;
private Destination m_destination;
private ExceptionHandler m_handler = new SystemErrorExceptionHandler();
private MessageConverter m_messageConverter;
private JmsTemplate m_template;
private int m_maxDeliveryThreads = 2;
private int m_maxDeliveryQueue = Integer.MAX_VALUE;
private RejectedExecutionHandler m_rejectionHandler = new ThreadPoolExecutor.DiscardPolicy();
private ExecutorService m_executor;
/**
* Called by Spring once all properties have been set. This method will
* establish the connection to the JMS broker.
*/
public void afterPropertiesSet() throws Exception {
BlockingQueue<Runnable> q = new LinkedBlockingQueue<Runnable>(m_maxDeliveryQueue);
m_executor = new ThreadPoolExecutor(1, m_maxDeliveryThreads, 60l, TimeUnit.SECONDS, q, new DeliveryThreadFactory(),
m_rejectionHandler);
m_template = new JmsTemplate(m_connectionFactory);
m_template.setDefaultDestination(m_destination);
m_template.setMessageConverter(m_messageConverter);
}
/**
* Get the current connection factory used to connect to the JMS broker.
*
* @return the connection factory.
*/
public ConnectionFactory getConnectionFactory() {
return m_connectionFactory;
}
/**
* Get the current destination.
*
* @return the current destination.
*/
public Destination getDestination() {
return m_destination;
}
/**
* Get the current policy for queue overruns.
*
* @return the current overrun policy.
*/
public boolean getDiscardOverrun() {
return m_rejectionHandler instanceof ThreadPoolExecutor.DiscardPolicy;
}
/**
* Get the maximum size of the delivery queue.
*
* @return the maximum queue size.
*/
public int getMaxDeliveryQueue() {
return m_maxDeliveryQueue;
}
/**
* Get the maximum number of delivery threads.
*
* @return the maximum number of delivery threads
*/
public int getMaxDeliveryThreads() {
return m_maxDeliveryThreads;
}
/**
* Get the current message converter.
*
* @return the message converter.
*/
public MessageConverter getMessageConverter() {
return m_messageConverter;
}
/**
* Send the telemetry to the JMS topic. The telemetry is queued for delivery
* and this method will return immediately unless discarding messages is
* disabled and the queue is full.
*
* @param telemetry
* The telemetry to send.
*
* @seeorg.addsimplicity.aniticus.support.DeliveryAdapter#sendSession(org.
* addsimplicity
* .
* aniticus
* .
* entity
* .
* Session
* )
*/
public void sendTelemetry(GlobalInfo telemetry) {
m_executor.submit(new DeliveryTask(telemetry));
}
/**
* Set the JMS connection factory that will be used to connect to the broker.
*
* @param connectionFactory
* The factory used to connect to the broker.
*/
public void setConnectionFactory(ConnectionFactory connectionFactory) {
m_connectionFactory = connectionFactory;
}
/**
* Set the destination topic or queue for delivering telemetry.
*
* @param destination
* The topic for publishing messages.
*/
public void setDestination(Destination destination) {
m_destination = destination;
}
/**
* Setting discard to true will cause telemetry to be dropped if the delivery
* queue is full. This is the default setting.
*
* @param discard
* True to discard telemetry if the queue is full.
*/
public void setDiscardOverrun(boolean discard) {
if (discard) {
m_rejectionHandler = new ThreadPoolExecutor.DiscardPolicy();
}
else {
m_rejectionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
}
}
/**
* The exception handler that will be invoked if a delivery error occurs. Note
* that the handler may be called on a different thread from the thread that
* sets it.
*
* @param handler
* The exception handler.
*
* @see org.addsimplicity.aniticus.support.DeliveryAdapter#setExceptionHandler(org.addsimplicity.aniticus.support.ExceptionHandler)
*/
public void setExceptionHandler(ExceptionHandler handler) {
m_handler = handler;
}
/**
* The delivery queue holds telemetry to be delivered. By default the queue
* size is unlimited.
*
* @param maxDeliveryQueue
* The maximum number of telemetry events that will be queued.
*/
public void setMaxDeliveryQueue(int maxDeliveryQueue) {
m_maxDeliveryQueue = maxDeliveryQueue;
}
/**
* Delivery threads publish events to the JMS topic. By default a maximum of 2
* threads are used.
*
* @param maxDeliveryThreads
* The maximum threads that will be used for delivering telemetry.
*/
public void setMaxDeliveryThreads(int maxDeliveryThreads) {
m_maxDeliveryThreads = maxDeliveryThreads;
}
/**
* The message converter is responsible for translating the telemetry artifact
* to the JMS message structure.
*
* @param messageConverter
* The message converter.
*/
public void setMessageConverter(MessageConverter messageConverter) {
m_messageConverter = messageConverter;
}
}