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

Java tutorial

Introduction

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

Source

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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.ObjectUtils;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeMsgEnvelope;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.fudgemsg.wire.FudgeMsgReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.Lifecycle;

import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.id.ExternalId;
import com.opengamma.livedata.LiveDataListener;
import com.opengamma.livedata.LiveDataSpecification;
import com.opengamma.livedata.LiveDataValueUpdate;
import com.opengamma.livedata.LiveDataValueUpdateBean;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSnapshotRequestBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSnapshotRequestMessage;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSnapshotResponseBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSnapshotResponseMessage;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSubscriptionRequestBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSubscriptionRequestMessage;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSubscriptionResponseBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSubscriptionResponseMessage;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataUnsubscribeBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataUnsubscribeMessage;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataUpdateBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataUpdateMessage;
import com.opengamma.livedata.cogda.msg.CogdaMessageType;
import com.opengamma.livedata.cogda.msg.ConnectionRequestBuilder;
import com.opengamma.livedata.cogda.msg.ConnectionRequestMessage;
import com.opengamma.livedata.cogda.msg.ConnectionResponseBuilder;
import com.opengamma.livedata.cogda.msg.ConnectionResponseMessage;
import com.opengamma.livedata.cogda.server.CogdaLiveDataServer;
import com.opengamma.livedata.msg.LiveDataSubscriptionResponse;
import com.opengamma.livedata.msg.LiveDataSubscriptionResult;
import com.opengamma.transport.ByteArrayFudgeMessageSender;
import com.opengamma.transport.FudgeMessageReceiver;
import com.opengamma.transport.FudgeMessageSender;
import com.opengamma.transport.InputStreamFudgeMessageDispatcher;
import com.opengamma.transport.OutputStreamByteArrayMessageSender;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;

/**
 * Live data client connecting to a COGDA server.
 * <p>
 * This connects to an instance of {@link CogdaLiveDataServer}.
 */
public class CogdaLiveDataClient extends AbstractLiveDataClient implements Lifecycle, FudgeMessageReceiver {

    /** Logger. */
    private static final Logger s_logger = LoggerFactory.getLogger(CogdaLiveDataClient.class);

    // Injected parameters:
    /**
     * The server name to connect to.
     */
    private String _serverName = "127.0.0.1";
    /**
     * The server port to connect to.
     */
    private int _serverPort = CogdaLiveDataServer.DEFAULT_LISTEN_PORT;
    /**
     * The Fudge context.
     */
    private FudgeContext _fudgeContext = OpenGammaFudgeContext.getInstance();
    /**
     * The user.
     */
    private final UserPrincipal _user;

    // Runtime state:
    /**
     * Holds the actual socket to the server.
     */
    private Socket _socket;
    /**
     * The message sender.
     */
    private FudgeMessageSender _messageSender;
    /**
     * The socket thread.
     */
    @SuppressWarnings("unused")
    private Thread _socketReadThread;
    /**
     * The generator of correlation identifiers.
     */
    private final AtomicLong _nextRequestId = new AtomicLong(1L);
    /**
     * The active subscription requests.
     */
    private final Map<Long, SubscriptionHandle> _activeSubscriptionRequests = new ConcurrentHashMap<Long, SubscriptionHandle>();

    /**
     * Creates an instance.
     * 
     * @param user  the user to connect with, not null
     */
    public CogdaLiveDataClient(UserPrincipal user) {
        ArgumentChecker.notNull(user, "user");
        _user = user;
    }

    //-------------------------------------------------------------------------
    /**
     * Gets the server name.
     * 
     * @return the server name, not null
     */
    public String getServerName() {
        return _serverName;
    }

    /**
     * Sets the server name.
     * 
     * @param serverName  the server name, not null
     */
    public void setServerName(String serverName) {
        _serverName = serverName;
    }

    /**
     * Gets the server port.
     * 
     * @return the server port
     */
    public int getServerPort() {
        return _serverPort;
    }

    /**
     * Sets the server port.
     * 
     * @param serverPort  the server port
     */
    public void setServerPort(int serverPort) {
        _serverPort = serverPort;
    }

    /**
     * Gets the fudge context.
     * 
     * @return the fudge context, not null
     */
    @Override
    public FudgeContext getFudgeContext() {
        return _fudgeContext;
    }

    /**
     * Sets the fudge context.
     * 
     * @param fudgeContext  the fudge context, not null
     */
    @Override
    public void setFudgeContext(FudgeContext fudgeContext) {
        _fudgeContext = fudgeContext;
    }

    //-------------------------------------------------------------------------
    /**
     * Checks whether the specified user matches the user this client is for.
     * 
     * @param user  the user to check, not null
     * @throws IllegalArgumentException if the user is invalid
     */
    protected void checkUserMatches(UserPrincipal user) {
        if (!ObjectUtils.equals(user, _user)) {
            throw new IllegalArgumentException("Specified user " + user + " does not match client user " + _user);
        }
    }

    //-------------------------------------------------------------------------
    @Override
    public boolean isEntitled(UserPrincipal user, LiveDataSpecification requestedSpecification) {
        // TODO kirk 2012-08-23 -- Implement this properly.
        return true;
    }

    @Override
    public Map<LiveDataSpecification, Boolean> isEntitled(UserPrincipal user,
            Collection<LiveDataSpecification> requestedSpecifications) {
        Map<LiveDataSpecification, Boolean> result = new HashMap<LiveDataSpecification, Boolean>();
        for (LiveDataSpecification ldc : requestedSpecifications) {
            result.put(ldc, isEntitled(user, ldc));
        }
        return result;
    }

    @Override
    protected void handleSubscriptionRequest(Collection<SubscriptionHandle> subHandle) {
        // TODO kirk 2012-08-15 -- Batch these up. This is just for testing.
        for (SubscriptionHandle handle : subHandle) {
            long correlationId = _nextRequestId.getAndIncrement();
            switch (handle.getSubscriptionType()) {
            case NON_PERSISTENT:
            case PERSISTENT:
                CogdaLiveDataSubscriptionRequestMessage subRequest = new CogdaLiveDataSubscriptionRequestMessage();
                subRequest.setCorrelationId(correlationId);
                subRequest.setNormalizationScheme(handle.getRequestedSpecification().getNormalizationRuleSetId());
                // REVIEW kirk 2012-08-15 -- The next line is SOOOOO UGLLYYYYY!!!!!
                subRequest.setSubscriptionId(
                        handle.getRequestedSpecification().getIdentifiers().getExternalIds().iterator().next());
                _activeSubscriptionRequests.put(correlationId, handle);
                _messageSender.send(CogdaLiveDataSubscriptionRequestBuilder
                        .buildMessageStatic(new FudgeSerializer(getFudgeContext()), subRequest));
                // Same thing in Cogda.
                break;
            case SNAPSHOT:
                CogdaLiveDataSnapshotRequestMessage snapshotRequest = new CogdaLiveDataSnapshotRequestMessage();
                snapshotRequest.setCorrelationId(correlationId);
                snapshotRequest
                        .setNormalizationScheme(handle.getRequestedSpecification().getNormalizationRuleSetId());
                // REVIEW kirk 2012-08-15 -- The next line is SOOOOO UGLLYYYYY!!!!!
                snapshotRequest.setSubscriptionId(
                        handle.getRequestedSpecification().getIdentifiers().getExternalIds().iterator().next());
                _activeSubscriptionRequests.put(correlationId, handle);
                _messageSender.send(CogdaLiveDataSnapshotRequestBuilder
                        .buildMessageStatic(new FudgeSerializer(getFudgeContext()), snapshotRequest));
                break;
            }
        }
    }

    @Override
    protected void cancelPublication(LiveDataSpecification fullyQualifiedSpecification) {
        CogdaLiveDataUnsubscribeMessage message = new CogdaLiveDataUnsubscribeMessage();
        message.setCorrelationId(_nextRequestId.getAndIncrement());
        message.setNormalizationScheme(fullyQualifiedSpecification.getNormalizationRuleSetId());
        message.setSubscriptionId(fullyQualifiedSpecification.getIdentifiers().iterator().next());
        _messageSender.send(CogdaLiveDataUnsubscribeBuilder
                .buildMessageStatic(new FudgeSerializer(getFudgeContext()), message));
    }

    @Override
    public void messageReceived(FudgeContext fudgeContext, FudgeMsgEnvelope msgEnvelope) {
        s_logger.info("Got message {}", msgEnvelope);
        FudgeMsg msg = msgEnvelope.getMessage();
        CogdaMessageType msgType = CogdaMessageType.getFromMessage(msg);
        switch (msgType) {
        case SUBSCRIPTION_RESPONSE:
        case SNAPSHOT_RESPONSE:
            dispatchCommandResponse(msgType, msg);
            break;
        case LIVE_DATA_UPDATE:
            dispatchLiveDataUpdate(msg);
            break;
        default:
            s_logger.warn("Received message that wasn't understood: {}", msg);
        }
    }

    /**
     * Dispatches a message to the server.
     * 
     * @param msg  the message, not null
     */
    private void dispatchLiveDataUpdate(FudgeMsg msg) {
        CogdaLiveDataUpdateMessage updateMessage = CogdaLiveDataUpdateBuilder
                .buildObjectStatic(new FudgeDeserializer(getFudgeContext()), msg);
        LiveDataSpecification ldspec = new LiveDataSpecification(updateMessage.getNormalizationScheme(),
                updateMessage.getSubscriptionId());
        LiveDataValueUpdateBean valueUpdateBean = new LiveDataValueUpdateBean(0L, ldspec,
                updateMessage.getValues());
        super.valueUpdate(valueUpdateBean);
    }

    /**
     * Dispatches a command response.
     * 
     * @param msgType  the type, not null
     * @param msg  the message, not null
     */
    private void dispatchCommandResponse(CogdaMessageType msgType, FudgeMsg msg) {
        if (!msg.hasField("correlationId")) {
            s_logger.warn("Received subscription response message without correlationId: {}", msg);
            return;
        }
        long correlationId = msg.getLong("correlationId");

        SubscriptionHandle subHandle = _activeSubscriptionRequests.remove(correlationId);
        if (subHandle == null) {
            s_logger.warn("Got subscription result on correlationId {} without active subscription: {}",
                    correlationId, msg);
            return;
        }

        switch (msgType) {
        case SUBSCRIPTION_RESPONSE:
            dispatchSubscriptionResponse(msg, subHandle);
            break;
        case SNAPSHOT_RESPONSE:
            dispatchSnapshotResponse(msg, subHandle);
            break;
        default:
            s_logger.warn("Got unexpected msg type {} as a command response - {}", msgType, msg);
            break;
        }
    }

    /**
     * Dispatches the response to a snapshot.
     * 
     * @param msg  the message, not null
     * @param subHandle  the subscription handle, not null
     */
    private void dispatchSnapshotResponse(FudgeMsg msg, SubscriptionHandle subHandle) {
        CogdaLiveDataSnapshotResponseMessage responseMessage = CogdaLiveDataSnapshotResponseBuilder
                .buildObjectStatic(new FudgeDeserializer(getFudgeContext()), msg);
        LiveDataSpecification ldSpec = new LiveDataSpecification(responseMessage.getNormalizationScheme(),
                responseMessage.getSubscriptionId());

        LiveDataSubscriptionResult ldsResult = responseMessage.getGenericResult().toLiveDataSubscriptionResult();
        LiveDataSubscriptionResponse ldsResponse = new LiveDataSubscriptionResponse(
                subHandle.getRequestedSpecification(), ldsResult);
        ldsResponse.setFullyQualifiedSpecification(ldSpec);
        ldsResponse.setUserMessage(responseMessage.getUserMessage());

        LiveDataValueUpdateBean valueUpdateBean = new LiveDataValueUpdateBean(0L,
                subHandle.getRequestedSpecification(), responseMessage.getValues());
        ldsResponse.setSnapshot(valueUpdateBean);
        subHandle.subscriptionResultReceived(ldsResponse);
    }

    /**
     * Dispatches the response to subscription.
     * 
     * @param msg  the message, not null
     * @param subHandle  the subscription handle, not null
     */
    private void dispatchSubscriptionResponse(FudgeMsg msg, SubscriptionHandle subHandle) {
        CogdaLiveDataSubscriptionResponseMessage responseMessage = CogdaLiveDataSubscriptionResponseBuilder
                .buildObjectStatic(new FudgeDeserializer(getFudgeContext()), msg);
        LiveDataSpecification ldSpec = new LiveDataSpecification(responseMessage.getNormalizationScheme(),
                responseMessage.getSubscriptionId());

        LiveDataSubscriptionResult ldsResult = responseMessage.getGenericResult().toLiveDataSubscriptionResult();
        LiveDataSubscriptionResponse ldsResponse = new LiveDataSubscriptionResponse(
                subHandle.getRequestedSpecification(), ldsResult);
        ldsResponse.setFullyQualifiedSpecification(ldSpec);
        ldsResponse.setUserMessage(responseMessage.getUserMessage());

        LiveDataValueUpdateBean valueUpdateBean = new LiveDataValueUpdateBean(0L,
                subHandle.getRequestedSpecification(), responseMessage.getSnapshot());
        ldsResponse.setSnapshot(valueUpdateBean);

        switch (responseMessage.getGenericResult()) {
        case SUCCESSFUL:
            super.subscriptionRequestSatisfied(subHandle, ldsResponse);
            super.subscriptionStartingToReceiveTicks(subHandle, ldsResponse);
            break;
        default:
            super.subscriptionRequestFailed(subHandle, ldsResponse);
        }
        subHandle.subscriptionResultReceived(ldsResponse);
        subHandle.getListener().valueUpdate(valueUpdateBean);
    }

    //-------------------------------------------------------------------------
    @Override
    public void start() {
        if (_socket != null) {
            throw new IllegalStateException("Socket is currently established.");
        }
        InetAddress serverAddress = null;
        try {
            serverAddress = InetAddress.getByName(getServerName());
        } catch (UnknownHostException ex) {
            s_logger.error("Illegal host name: " + getServerName(), ex);
            throw new IllegalArgumentException("Cannot identify host " + getServerName());
        }
        try {
            Socket socket = new Socket(serverAddress, getServerPort());
            InputStream is = socket.getInputStream();
            OutputStream os = socket.getOutputStream();
            _messageSender = new ByteArrayFudgeMessageSender(new OutputStreamByteArrayMessageSender(os));

            login(is);

            InputStreamFudgeMessageDispatcher messageDispatcher = new InputStreamFudgeMessageDispatcher(is, this);
            Thread t = new Thread(messageDispatcher, "CogdaLiveDataClient Dispatch Thread");
            t.setDaemon(true);
            t.start();
            _socketReadThread = t;

            _socket = socket;
        } catch (IOException ioe) {
            s_logger.error("Unable to establish connection to" + getServerName() + ":" + getServerPort(), ioe);
            throw new OpenGammaRuntimeException(
                    "Unable to establish connection to" + getServerName() + ":" + getServerPort());
        }

    }

    protected void login(InputStream is) throws IOException {
        ConnectionRequestMessage requestMessage = new ConnectionRequestMessage();
        requestMessage.setUserName(_user.getUserName());
        _messageSender.send(ConnectionRequestBuilder.buildMessageStatic(new FudgeSerializer(getFudgeContext()),
                requestMessage));
        // TODO kirk 2012-08-22 -- This needs a timeout.
        FudgeMsgReader reader = getFudgeContext().createMessageReader(is);
        FudgeMsg msg = reader.nextMessage();
        ConnectionResponseMessage response = ConnectionResponseBuilder
                .buildObjectStatic(new FudgeDeserializer(getFudgeContext()), msg);
        switch (response.getResult()) {
        case NEW_CONNECTION_SUCCESS:
        case EXISTING_CONNECTION_RESTART:
            // We're good to go!
            // TODO kirk 2012-08-15 -- Add logic eventually for connection restart semantics.
            s_logger.warn("Successfully logged into server.");
            break;
        case NOT_AUTHORIZED:
            // REVIEW kirk 2012-08-15 -- Is this the right error?
            throw new OpenGammaRuntimeException("Server says NOT_AUTHORIZED");
        }
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean isRunning() {
        return ((_socket != null) && (_socket.isConnected()));
    }

    //-------------------------------------------------------------------------
    /**
     * A simple test that runs against localhost. Only useful for protocol development.
     * 
     * @param args Command-line args. Ignored.
     * @throws InterruptedException Required to make the compiler happy
     */
    public static void main(final String[] args) throws InterruptedException { // CSIGNORE
        CogdaLiveDataClient client = new CogdaLiveDataClient(UserPrincipal.getLocalUser());
        //client.setServerName("cogdasvr-lx-1.hq.opengamma.com");
        client.start();

        LiveDataSpecification lds = new LiveDataSpecification("OpenGamma", ExternalId.of("SURF", "FV2DBEURUSD12M"));
        LiveDataSubscriptionResponse response = client.snapshot(UserPrincipal.getLocalUser(), lds, 60000L);
        s_logger.warn("Snapshot {}", response);
        List<LiveDataSpecification> subs = new LinkedList<LiveDataSpecification>();
        subs.add(lds);
        subs.add(new LiveDataSpecification("OpenGamma", ExternalId.of("SURF", "ASIRSEUR49Y30A03L")));
        subs.add(new LiveDataSpecification("OpenGamma", ExternalId.of("SURF", "FV1DRUSDBRL06M")));
        subs.add(new LiveDataSpecification("OpenGamma", ExternalId.of("ICAP", "SAUD_9Y")));
        subs.add(new LiveDataSpecification("OpenGamma", ExternalId.of("ICAP", "GBP_5Y")));
        subs.add(new LiveDataSpecification("OpenGamma", ExternalId.of("ICAP", "GBPUSD7M")));
        LiveDataListener ldl = new LiveDataListener() {
            @Override
            public void subscriptionResultReceived(LiveDataSubscriptionResponse subscriptionResult) {
                s_logger.warn("Sub result {}", subscriptionResult);
            }

            @Override
            public void subscriptionResultsReceived(
                    final Collection<LiveDataSubscriptionResponse> subscriptionResults) {
                s_logger.warn("Sub result {}", subscriptionResults);
            }

            @Override
            public void subscriptionStopped(LiveDataSpecification fullyQualifiedSpecification) {
                s_logger.warn("Sub stopped {}", fullyQualifiedSpecification);
            }

            @Override
            public void valueUpdate(LiveDataValueUpdate valueUpdate) {
                s_logger.warn("Data received {}", valueUpdate);
            }

        };
        client.subscribe(UserPrincipal.getLocalUser(), subs, ldl);

        client.subscribe(UserPrincipal.getLocalUser(),
                new LiveDataSpecification("OpenGamma", ExternalId.of("SURF", "NO_SUCH_THING")), ldl);

        Thread.sleep(100000000L);
    }

}