com.opengamma.livedata.client.ActiveMQLiveDataClient.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.livedata.client.ActiveMQLiveDataClient.java

Source

/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.livedata.client;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.Topic;

import org.fudgemsg.FudgeContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.support.JmsUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.transport.FudgeRequestSender;
import com.opengamma.transport.jms.JmsByteArrayMessageDispatcher;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.PublicAPI;
import com.opengamma.util.jms.JmsConnector;

/**
 * An ActiveMQ LiveData client. Behaves the same as {@link JmsLiveDataClient} except
 * that subscriptions are made to Composite Destinations in order to reduce
 * the time made to make subscriptions.
 * 
 * Currently subscriptions are only removed when none of the block is in use any more, this could be improved later. 
 */
@PublicAPI
public class ActiveMQLiveDataClient extends JmsLiveDataClient {
    //TODO: Could migrate to individual/smaller subscriptions in background 

    private static final Logger s_logger = LoggerFactory.getLogger(ActiveMQLiveDataClient.class);

    public ActiveMQLiveDataClient(FudgeRequestSender subscriptionRequestSender,
            FudgeRequestSender entitlementRequestSender, JmsConnector jmsConnector) {
        super(subscriptionRequestSender, entitlementRequestSender, jmsConnector);
    }

    public ActiveMQLiveDataClient(FudgeRequestSender subscriptionRequestSender,
            FudgeRequestSender entitlementRequestSender, JmsConnector jmsConnector, FudgeContext fudgeContext,
            int maxSessions) {
        super(subscriptionRequestSender, entitlementRequestSender, jmsConnector, fudgeContext, maxSessions);
    }

    public ActiveMQLiveDataClient(FudgeRequestSender subscriptionRequestSender,
            FudgeRequestSender entitlementRequestSender, JmsConnector jmsConnector, FudgeContext fudgeContext) {
        super(subscriptionRequestSender, entitlementRequestSender, jmsConnector, fudgeContext);
    }

    private final Map<String, ConsumerRecord> _messageConsumersBySpec = new ConcurrentHashMap<String, ConsumerRecord>();

    private class ConsumerRecord {
        private final MessageConsumer _consumer;
        private final Set<String> _allReceiving;
        private final Set<String> _receiving;

        public ConsumerRecord(MessageConsumer consumer, Collection<String> receiving) {
            super();
            _consumer = consumer;
            _receiving = new HashSet<String>(receiving);
            _allReceiving = Collections.unmodifiableSet(new HashSet<String>(receiving));
        }

        public MessageConsumer getConsumer() {
            return _consumer;
        }

        public Set<String> getReceiving() {
            return _receiving;
        }

        public Set<String> getAllReceiving() {
            return _allReceiving;
        }
    }

    @Override
    protected Map<String, Runnable> startReceivingTicks(List<String> specs, Session session,
            JmsByteArrayMessageDispatcher jmsDispatcher) {
        Map<String, Runnable> ret = new HashMap<String, Runnable>();
        if (specs.isEmpty()) {
            return ret;
        }

        for (String spec : specs) {
            ConsumerRecord record = _messageConsumersBySpec.get(spec);
            if (record != null) {
                //NOTE: could be on the wrong session, but we don't touch it
                record.getReceiving().add(spec);
                ret.put(spec, getCloseAction(spec, record));
            }
        }
        SetView<String> remaining = Sets.difference(new HashSet<String>(specs), ret.keySet());
        List<String> remainingList = new ArrayList<String>(remaining);
        for (List<String> partition : Lists.partition(remainingList, 100)) {
            String topicName = getCompositeTopicName(partition);
            try {
                Topic topic = session.createTopic(topicName);

                final MessageConsumer messageConsumer = session.createConsumer(topic);
                messageConsumer.setMessageListener(jmsDispatcher);
                ConsumerRecord record = new ConsumerRecord(messageConsumer, partition);
                for (String tickDistributionSpecification : partition) {
                    _messageConsumersBySpec.put(tickDistributionSpecification, record);
                    ret.put(tickDistributionSpecification, getCloseAction(tickDistributionSpecification, record));
                }
            } catch (JMSException e) {
                throw new OpenGammaRuntimeException("Failed to create subscription to JMS topics " + partition, e);
            }
        }
        return ret;
    }

    private Runnable getCloseAction(final String tickDistributionSpecification, final ConsumerRecord record) {
        return new Runnable() {

            @Override
            public void run() {
                record.getReceiving().remove(tickDistributionSpecification);
                if (record.getReceiving().isEmpty()) {
                    s_logger.debug("Closing connection after last unsubscribe {}", tickDistributionSpecification);
                    JmsUtils.closeMessageConsumer(record.getConsumer());
                    for (String receiving : record.getAllReceiving()) {
                        _messageConsumersBySpec.remove(receiving);
                    }
                } else {
                    //TODO: Should I shrink the subscription? 
                    s_logger.debug("Not closing composite connection remaining subscribtions {}",
                            record.getReceiving());
                }
            }
        };
    }

    private String getCompositeTopicName(Collection<String> specs) {
        ArgumentChecker.notEmpty(specs, "specs");
        StringBuilder topicNameBuilder = new StringBuilder();
        for (String spec : specs) {
            topicNameBuilder.append(spec);
            topicNameBuilder.append(',');
        }
        topicNameBuilder.setLength(topicNameBuilder.length() - 1);
        String topicName = topicNameBuilder.toString();
        return topicName;
    }

    @Override
    public synchronized void close() {
        _messageConsumersBySpec.clear();
        super.close();
    }
}