Package com.opengamma.livedata.cogda.server

Source Code of com.opengamma.livedata.cogda.server.CogdaClientConnection

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

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeMsgEnvelope;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.user.EntitlementUtils;
import com.opengamma.core.user.OGUser;
import com.opengamma.id.ExternalId;
import com.opengamma.livedata.LiveDataSpecification;
import com.opengamma.livedata.LiveDataValueUpdate;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.livedata.cogda.msg.CogdaCommandResponseResult;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataBuilderUtil;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataCommandResponseMessage;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSnapshotRequestBuilder;
import com.opengamma.livedata.cogda.msg.CogdaLiveDataSnapshotRequestMessage;
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.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.msg.ConnectionResult;
import com.opengamma.livedata.server.LastKnownValueStore;
import com.opengamma.transport.FudgeConnection;
import com.opengamma.transport.FudgeConnectionStateListener;
import com.opengamma.transport.FudgeMessageReceiver;
import com.opengamma.transport.FudgeMessageSender;
import com.opengamma.util.ArgumentChecker;

/**
* The object which holds a particular connection to a Cogda client for
* a particular {@link CogdaLiveDataServer}.
*/
public class CogdaClientConnection implements FudgeConnectionStateListener, FudgeMessageReceiver {
  private static final Logger s_logger = LoggerFactory.getLogger(CogdaClientConnection.class);
  private final FudgeContext _fudgeContext;
  private final CogdaLiveDataServer _server;
  private final FudgeMessageSender _messageSender;
 
  // REVIEW kirk 2013-03-27 -- The only reason why _subscriptions exists is to act as
  // a quick pass on whether the client is subscribed to a specification.
  // This is to avoid going into a locking state waiting for _valuesToSend.
  private final ConcurrentMap<LiveDataSpecification, Boolean> _subscriptions = new ConcurrentHashMap<LiveDataSpecification, Boolean>();
  private final Map<LiveDataSpecification, FudgeMsg> _valuesToSend = new HashMap<LiveDataSpecification, FudgeMsg>();
  private final Lock _writerLock = new ReentrantLock();
  private final Lock _valuesToSendLock = new ReentrantLock();
 
  private UserPrincipal _userPrincipal;
  private OGUser _user;
 
  public CogdaClientConnection(FudgeContext fudgeContext, CogdaLiveDataServer server, FudgeConnection connection) {
    ArgumentChecker.notNull(fudgeContext, "fudgeContext");
    ArgumentChecker.notNull(server, "server");
    ArgumentChecker.notNull(connection, "fudgeConnection");
   
    _fudgeContext = fudgeContext;
    _server = server;
    connection.setConnectionStateListener(this);
    connection.setFudgeMessageReceiver(this);
    _messageSender = connection.getFudgeMessageSender();
  }

  /**
   * Gets the server.
   * @return the server
   */
  public CogdaLiveDataServer getServer() {
    return _server;
  }

  /**
   * Gets the fudgeContext.
   * @return the fudgeContext
   */
  public FudgeContext getFudgeContext() {
    return _fudgeContext;
  }

  /**
   * Gets the messageSender.
   * @return the messageSender
   */
  public FudgeMessageSender getMessageSender() {
    return _messageSender;
  }

  /**
   * Gets the user.
   * @return the user
   */
  public UserPrincipal getUserPrincipal() {
    return _userPrincipal;
  }

  /**
   * Gets the user.
   * @return the user
   */
  public OGUser getUser() {
    return _user;
  }

  /**
   * Sets the user.
   * @param user  the user
   */
  public void setUser(OGUser user) {
    _user = user;
  }

  @Override
  public void connectionReset(FudgeConnection connection) {
    s_logger.warn("Connection Reset");
  }

  @Override
  public void connectionFailed(FudgeConnection connection, Exception cause) {
    // TODO kirk 2012-08-15 -- Fix this so that failed connections result in
    // torn down client connections.
    // Cause may be null.
    s_logger.warn("Connection failed \"{}\"", (cause != null) ? cause.getMessage() : "no cause");
    s_logger.info("Connection failed", cause);
    getServer().removeClient(this);
  }
 
  public void handshakeMessage(FudgeContext fudgeContext, FudgeMsgEnvelope msgEnvelope) {
    // REVIEW kirk 2012-07-23 -- If there are multiple versions of the protocol have to check
    // the schema on the envelope.
    FudgeMsg msg = msgEnvelope.getMessage();
    if (CogdaMessageType.getFromMessage(msg) != CogdaMessageType.CONNECTION_REQUEST) {
      // On failure tear down the connection when http://jira.opengamma.com/browse/PLAT-2458 is done.
      throw new OpenGammaRuntimeException("Cannot handle any other message than connection request as first message in COGDA protocol.");
    }
    ConnectionRequestMessage request = ConnectionRequestBuilder.buildObjectStatic(new FudgeDeserializer(fudgeContext), msg);
   
    // Wrap this in synchronized to force the cache flush.
    synchronized (this) {
      _userPrincipal = getServer().authenticate(request.getUserName(), request.getPassword());
      if (_userPrincipal != null) {
        _user = getServer().getOGUser(request.getUserName());
      }
    }
   
    if (getUserPrincipal() == null) {
      ConnectionResponseMessage response = new ConnectionResponseMessage();
      response.setResult(ConnectionResult.NOT_AUTHORIZED);
      sendMessage(ConnectionResponseBuilder.buildMessageStatic(new FudgeSerializer(fudgeContext), response));
      // On failure tear down the connection when http://jira.opengamma.com/browse/PLAT-2458 is done.
      getServer().removeClient(this);
    } else {
      ConnectionResponseMessage response = new ConnectionResponseMessage();
      response.setResult(ConnectionResult.NEW_CONNECTION_SUCCESS);
      response.setAvailableServers(getServer().getAvailableServers());
      response.applyCapabilities(getServer().getCapabilities());
      sendMessage(ConnectionResponseBuilder.buildMessageStatic(new FudgeSerializer(fudgeContext), response));
    }
  }

  @Override
  public void messageReceived(FudgeContext fudgeContext, FudgeMsgEnvelope msgEnvelope) {
    if (getUserPrincipal() == null) {
      throw new OpenGammaRuntimeException("Cannot operate, failed user authentication.");
    }
    FudgeMsg msg = msgEnvelope.getMessage();
   
    CogdaLiveDataCommandResponseMessage response = null;
    switch (CogdaMessageType.getFromMessage(msg)) {
      case SNAPSHOT_REQUEST:
        response = handleSnapshotRequest(fudgeContext, msg);
        break;
      case SUBSCRIPTION_REQUEST:
        response = handleSubscriptionRequest(fudgeContext, msg);
        break;
      case UNSUBSCRIBE:
        handleUnsubscription(fudgeContext, msg);
        break;
      default:
        // Illegal here.
        // Need an "ILLEGAL_COMMAND" message.
        break;
    }
   
    if (response != null) {
      sendMessage(CogdaLiveDataBuilderUtil.buildCommandResponseMessage(fudgeContext, response));
    }
  }
 
  protected boolean isEntitled(String operation, ExternalId subscriptionId, String normalizationScheme) {
    String entitlementDetail = MessageFormat.format("/{0}/{1}[{2}]", subscriptionId.getScheme(), subscriptionId.getValue(), normalizationScheme);
    String entitlementString = EntitlementUtils.generateEntitlementString(true, operation, "cogda", entitlementDetail);
    return EntitlementUtils.userHasEntitlement(getUser(), entitlementString);
  }

  /**
   * @param fudgeContext
   * @param msg
   */
  private CogdaLiveDataCommandResponseMessage handleSnapshotRequest(FudgeContext fudgeContext, FudgeMsg msg) {
    CogdaLiveDataSnapshotRequestMessage request = CogdaLiveDataSnapshotRequestBuilder.buildObjectStatic(new FudgeDeserializer(fudgeContext), msg);
    CogdaLiveDataSnapshotResponseMessage response = new CogdaLiveDataSnapshotResponseMessage();
    response.setCorrelationId(request.getCorrelationId());
    response.setSubscriptionId(request.getSubscriptionId());
    response.setNormalizationScheme(request.getNormalizationScheme());
   
    if (!getServer().isValidLiveData(request.getSubscriptionId(), request.getNormalizationScheme())) {
      response.setGenericResult(CogdaCommandResponseResult.NOT_AVAILABLE);
    } else if (!isEntitled(EntitlementUtils.SNAPSHOT, request.getSubscriptionId(), request.getNormalizationScheme())) {
      response.setGenericResult(CogdaCommandResponseResult.NOT_AUTHORIZED);
    } else {
      LastKnownValueStore lkvStore = getServer().getLastKnownValueStore(request.getSubscriptionId(), request.getNormalizationScheme());
      FudgeMsg fields = null;
      if (lkvStore != null) {
        fields = lkvStore.getFields();
      } else {
        s_logger.warn("Valid live data {} lacks fields in LKV store", request);
        fields = fudgeContext.newMessage();
      }
     
      response.setGenericResult(CogdaCommandResponseResult.SUCCESSFUL);
      response.setValues(fields);
    }
   
    return response;
  }

  /**
   * @param fudgeContext
   * @param msg
   */
  private CogdaLiveDataCommandResponseMessage handleSubscriptionRequest(FudgeContext fudgeContext, FudgeMsg msg) {
    CogdaLiveDataSubscriptionRequestMessage request = CogdaLiveDataSubscriptionRequestBuilder.buildObjectStatic(new FudgeDeserializer(fudgeContext), msg);
    CogdaLiveDataSubscriptionResponseMessage response = new CogdaLiveDataSubscriptionResponseMessage();
    response.setCorrelationId(request.getCorrelationId());
    response.setSubscriptionId(request.getSubscriptionId());
    response.setNormalizationScheme(request.getNormalizationScheme());
   
    // TODO kirk 2012-07-23 -- Check entitlements.
    if (!getServer().isValidLiveData(request.getSubscriptionId(), request.getNormalizationScheme())) {
      response.setGenericResult(CogdaCommandResponseResult.NOT_AVAILABLE);
    } else if (!isEntitled(EntitlementUtils.SUBSCRIBE, request.getSubscriptionId(), request.getNormalizationScheme())) {
      response.setGenericResult(CogdaCommandResponseResult.NOT_AUTHORIZED);
    } else {
      LastKnownValueStore lkvStore = getServer().getLastKnownValueStore(request.getSubscriptionId(), request.getNormalizationScheme());
      FudgeMsg fields = null;
      if (lkvStore != null) {
        fields = lkvStore.getFields();
      } else {
        s_logger.warn("Valid live data {} lacks fields in LKV store", request);
        fields = fudgeContext.newMessage();
      }
     
      response.setGenericResult(CogdaCommandResponseResult.SUCCESSFUL);
      response.setSnapshot(fields);
     
      _subscriptions.putIfAbsent(new LiveDataSpecification(request.getNormalizationScheme(), request.getSubscriptionId()), Boolean.TRUE);
    }
    return response;
  }
 
  private void handleUnsubscription(FudgeContext fudgeContext, FudgeMsg msg) {
    CogdaLiveDataUnsubscribeMessage request = CogdaLiveDataUnsubscribeBuilder.buildObjectStatic(new FudgeDeserializer(fudgeContext), msg);
   
    _subscriptions.remove(new LiveDataSpecification(request.getNormalizationScheme(), request.getSubscriptionId()));
  }
 
  private void sendMessage(FudgeMsg msg) {
    _writerLock.lock();
    try {
      getMessageSender().send(msg);
    } finally {
      _writerLock.unlock();
    }
  }
 
  public boolean liveDataReceived(LiveDataValueUpdate valueUpdate) {
    if (!_subscriptions.containsKey(valueUpdate.getSpecification())) {
      return false;
    }
    _valuesToSendLock.lock();
    try {
      _valuesToSend.put(valueUpdate.getSpecification(), valueUpdate.getFields());
    } finally {
      _valuesToSendLock.unlock();
    }
    return true;
  }
 
  public void sendAllUpdates() {
    _writerLock.lock();
    try {
      _valuesToSendLock.lock();
      try {
        for (Map.Entry<LiveDataSpecification, FudgeMsg> entry : _valuesToSend.entrySet()) {
          sendValueUpdate(entry.getKey(), entry.getValue());
        }
        _valuesToSend.clear();
      } finally {
        _valuesToSendLock.unlock();
      }
    } finally {
      _writerLock.unlock();
    }
  }

  /**
   * @param key
   * @param values
   */
  private void sendValueUpdate(LiveDataSpecification key, FudgeMsg values) {
    CogdaLiveDataUpdateMessage message = new CogdaLiveDataUpdateMessage();
    // REVIEW kirk 2012-07-23 -- This is a terrible terrible idea performance wise, this next line.
    message.setSubscriptionId(key.getIdentifiers().getExternalIds().iterator().next());
    message.setNormalizationScheme(key.getNormalizationRuleSetId());
    message.setValues(values);
    FudgeMsg msg = CogdaLiveDataUpdateBuilder.buildMessageStatic(new FudgeSerializer(getFudgeContext()), message);
    try {
      getMessageSender().send(msg);
    } catch (Exception e) {
      s_logger.info("Exception thrown; assuming socket closed and tearing down client.");
      // Note that the actual connection state will be handled by the FudgeConnectionStateListener callback.
    }
  }

}
TOP

Related Classes of com.opengamma.livedata.cogda.server.CogdaClientConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.