/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.livedata.server.distribution; import java.util.concurrent.Semaphore; import javax.jms.BytesMessage; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import org.fudgemsg.FudgeContext; import org.fudgemsg.FudgeMsg; import org.fudgemsg.mapping.FudgeSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.core.MessageCreator; import com.opengamma.livedata.LiveDataValueUpdateBean; import com.opengamma.livedata.LiveDataValueUpdateBeanFudgeBuilder; import com.opengamma.livedata.server.DistributionSpecification; import com.opengamma.livedata.server.FieldHistoryStore; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.jms.JmsConnector; /** * This {@link MarketDataSender} sends market data to JMS. * <p> * When the sender loses connection to JMS, it starts building a * cumulative delta of changes. This cumulative delta is published when * the sender reconnects. */ public class JmsSender implements MarketDataSender { /** Logger. */ private static final Logger s_logger = LoggerFactory.getLogger(JmsSender.class); /** * The JMS connector. */ private final JmsConnector _jmsConnector; /** * The Fudge context. */ private final FudgeContext _fudgeContext; /** * The distributor. */ private final MarketDataDistributor _distributor; /** * The field value history. */ private final FieldHistoryStore _cumulativeDelta = new FieldHistoryStore(); /** * The last sequence number. */ private long _lastSequenceNumber; /** * Whether the sender is interrupted. */ private volatile boolean _interrupted; /** * The internal lock. */ private final Semaphore _lock = new Semaphore(1); /** * Creates an instance. * * @param jmsConnector the JMS connector, not null * @param distributor the distributor, not null * @param fudgeContext the Fudge context, not null */ public JmsSender(JmsConnector jmsConnector, MarketDataDistributor distributor, FudgeContext fudgeContext) { ArgumentChecker.notNull(jmsConnector, "jmsConnector"); ArgumentChecker.notNull(distributor, "Market data distributor"); ArgumentChecker.notNull(fudgeContext, "fudgeContext"); _jmsConnector = jmsConnector; _fudgeContext = fudgeContext; _distributor = distributor; } //------------------------------------------------------------------------- @Override public MarketDataDistributor getDistributor() { return _distributor; } //------------------------------------------------------------------------- @Override public void sendMarketData(LiveDataValueUpdateBean data) { _lock.acquireUninterruptibly(); try { _cumulativeDelta.liveDataReceived(data.getFields()); _lastSequenceNumber = data.getSequenceNumber(); if (_interrupted) { s_logger.debug("{}: Interrupted - not sending message", this); return; } send(); } finally { _lock.release(); } } private void send() { DistributionSpecification distributionSpec = getDistributor().getDistributionSpec(); LiveDataValueUpdateBean liveDataValueUpdateBean = new LiveDataValueUpdateBean( _lastSequenceNumber, distributionSpec.getFullyQualifiedLiveDataSpecification(), _cumulativeDelta.getLastKnownValues()); s_logger.debug("{}: Sending Live Data update {}", this, liveDataValueUpdateBean); FudgeMsg fudgeMsg = LiveDataValueUpdateBeanFudgeBuilder.toFudgeMsg(new FudgeSerializer(_fudgeContext), liveDataValueUpdateBean); String destinationName = distributionSpec.getJmsTopic(); final byte[] bytes = _fudgeContext.toByteArray(fudgeMsg); _jmsConnector.getJmsTemplateTopic().send(destinationName, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { // TODO kirk 2009-10-30 -- We want to put stuff in the properties as well I think. BytesMessage bytesMessage = session.createBytesMessage(); bytesMessage.writeBytes(bytes); return bytesMessage; } }); _cumulativeDelta.clear(); } //------------------------------------------------------------------------- /** * Checks if the sender is interrupted. * * @return true if interrupted */ public boolean isInterrupted() { return _interrupted; } /** * Indicates that the transport was interrupted, setting the flag. */ public void transportInterrupted() { s_logger.error("Transport interrupted {}", this); _interrupted = true; } /** * Marks the sender as active again to stop batching up pending messages, and triggers a send * if a send is not already active. Note that it is not safe to call this method from the JMS * invoked transportResume message. For example ActiveMQ holds a lock to do an initial 'send' * and then gains a lock for the failover while another thread holds the lock for the failover * as it calls the notifications so sending will cause deadlock. */ public void transportResumed() { s_logger.info("Transport resumed {}", this); _interrupted = false; // tryAcquire() is used to avoid re-entry to the send method if a sendMarketData is already // active as that will hold the semaphore. if (_lock.tryAcquire()) { try { if (!_cumulativeDelta.isEmpty()) { send(); } } catch (RuntimeException e) { s_logger.error("transportResumed() failed", e); } finally { _lock.release(); } } } @Override public String toString() { return "JmsSender[" + _distributor.getDistributionSpec().toString() + "]"; } }