/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.rest;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.support.JmsUtils;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.util.jms.JmsConnector;
/**
* Publishes asynchronous results over JMS.
* <p>
* Always call {@link #stopPublishingResults()} when this result publisher is no longer required to ensure that associated resources are tidied up.
*/
public abstract class AbstractJmsResultPublisher {
/** Logger */
private static final Logger s_logger = LoggerFactory.getLogger(AbstractJmsResultPublisher.class);
private static final String SEQUENCE_NUMBER_FIELD_NAME = "#";
private final FudgeContext _fudgeContext;
private final FudgeSerializer _fudgeSerializationContext;
private final JmsConnector _jmsConnector;
private final ReentrantLock _lock = new ReentrantLock();
private final AtomicLong _sequenceNumber = new AtomicLong();
private final AtomicBoolean _isShutdown = new AtomicBoolean(false);
private BlockingQueue<byte[]> _messageQueue = new LinkedBlockingQueue<byte[]>();
private volatile Connection _connection;
private volatile Session _session;
private volatile MessageProducer _producer;
/**
* Creates an instance.
*
* @param fudgeContext the Fudge context, not null
* @param jmsConnector the JMS connector, may be null
*/
public AbstractJmsResultPublisher(FudgeContext fudgeContext, JmsConnector jmsConnector) {
_fudgeContext = fudgeContext;
_fudgeSerializationContext = new FudgeSerializer(fudgeContext);
_jmsConnector = jmsConnector;
}
//-------------------------------------------------------------------------
/**
* Stops listening to results from the underlying provider.
*/
protected abstract void stopListener();
/**
* Begins listening to results from the underlying provider. When a result occurs, {@code #send(Object)} should be called to publish that result over JMS.
*/
protected abstract void startListener();
/**
* Publishes a result over JMS.
* <p>
* This should only be called once results are required, as indicated by a call to {@link #startListener()}.
*
* @param result the result, not null
*/
protected void send(Object result) {
s_logger.debug("Result received to forward over JMS: {}", result);
MutableFudgeMsg resultMsg;
synchronized (_fudgeSerializationContext) {
resultMsg = _fudgeSerializationContext.objectToFudgeMsg(result);
}
FudgeSerializer.addClassHeader(resultMsg, result.getClass());
long sequenceNumber = _sequenceNumber.getAndIncrement();
resultMsg.add(SEQUENCE_NUMBER_FIELD_NAME, sequenceNumber);
s_logger.debug("Sending result as fudge message with sequence number {}: {}", sequenceNumber, resultMsg);
byte[] resultMsgByteArray = _fudgeContext.toByteArray(resultMsg);
_messageQueue.add(resultMsgByteArray);
}
//-------------------------------------------------------------------------
public void startPublishingResults(String destination) throws Exception {
_lock.lock();
try {
startJmsIfRequired(destination);
sendStartedSignal();
startListener();
} finally {
_lock.unlock();
}
}
private void startJmsIfRequired(String destination) throws Exception {
if (_jmsConnector == null) {
throw new OpenGammaRuntimeException("JMS not configured on server");
}
if (_producer == null) {
try {
_connection = _jmsConnector.getConnectionFactory().createConnection();
_session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
_producer = _session.createProducer(_session.createQueue(destination));
_messageQueue.clear();
startSenderThread();
_connection.start();
} catch (Exception e) {
closeJms();
throw e;
}
}
}
private void sendStartedSignal() {
s_logger.debug("Sending started signal");
// REVIEW jonathan 2012-02-03 -- until we have more than one control signal, it's sufficient to push through an
// empty message.
_messageQueue.add(_fudgeContext.toByteArray(_fudgeContext.newMessage()));
}
private void closeJms() {
if (_connection != null) {
//[PLAT-1809] Need to close all of these
JmsUtils.closeMessageProducer(_producer);
JmsUtils.closeSession(_session);
JmsUtils.closeConnection(_connection);
_connection = null;
_session = null;
_producer = null;
}
}
private void startSenderThread() throws JMSException {
Thread senderThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
byte[] nextMessage;
try {
nextMessage = _messageQueue.take();
if (_isShutdown.get()) {
break;
}
sendSync(nextMessage);
} catch (Exception e) {
s_logger.error("Failed to send message asynchronously", e);
}
}
}
}, String.format("JmsResultPublisher %s", _producer.getDestination()));
senderThread.setDaemon(true);
senderThread.start();
}
private void sendSync(byte[] buffer) {
MessageProducer producer = _producer;
if (producer == null) {
s_logger.debug("Result received after publishing stopped");
return;
}
try {
BytesMessage msg = _session.createBytesMessage();
msg.writeBytes(buffer);
producer.send(msg);
} catch (Exception e) {
s_logger.error("Error while sending result over JMS. This result may never reach the client.", e);
}
}
public void stopPublishingResults() throws JMSException {
_lock.lock();
try {
s_logger.debug("Removing listener {}", this);
stopListener();
_isShutdown.set(true);
_messageQueue.add(new byte[0]);
closeJms();
} finally {
_lock.unlock();
}
}
}