/** * 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); } }