Package com.opengamma.web.analytics.blotter

Source Code of com.opengamma.web.analytics.blotter.BlotterResource

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

import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.lang.StringUtils;
import org.joda.beans.Bean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.json.JSONException;
import org.json.JSONObject;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZonedDateTime;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.opengamma.DataNotFoundException;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.credit.DebtSeniority;
import com.opengamma.analytics.financial.credit.RestructuringClause;
import com.opengamma.core.security.Security;
import com.opengamma.financial.convention.StubType;
import com.opengamma.financial.convention.businessday.BusinessDayConvention;
import com.opengamma.financial.convention.daycount.DayCount;
import com.opengamma.financial.convention.frequency.Frequency;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.financial.security.LongShort;
import com.opengamma.financial.security.cds.AbstractCreditDefaultSwapSecurity;
import com.opengamma.financial.security.future.InterestRateFutureSecurity;
import com.opengamma.financial.security.option.BarrierDirection;
import com.opengamma.financial.security.option.BarrierType;
import com.opengamma.financial.security.option.CreditDefaultSwapOptionSecurity;
import com.opengamma.financial.security.option.ExerciseType;
import com.opengamma.financial.security.option.IRFutureOptionSecurity;
import com.opengamma.financial.security.option.MonitoringType;
import com.opengamma.financial.security.option.SamplingFrequency;
import com.opengamma.financial.security.option.SwaptionSecurity;
import com.opengamma.financial.security.swap.FloatingRateType;
import com.opengamma.financial.security.swap.InterpolationMethod;
import com.opengamma.financial.security.swap.SwapSecurity;
import com.opengamma.id.ExternalId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.master.portfolio.PortfolioMaster;
import com.opengamma.master.position.ManageablePosition;
import com.opengamma.master.position.ManageableTrade;
import com.opengamma.master.position.PositionDocument;
import com.opengamma.master.position.PositionMaster;
import com.opengamma.master.security.ManageableSecurity;
import com.opengamma.master.security.ManageableSecurityLink;
import com.opengamma.master.security.SecurityDocument;
import com.opengamma.master.security.SecurityMaster;
import com.opengamma.master.security.SecuritySearchRequest;
import com.opengamma.master.security.SecuritySearchResult;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.OpenGammaClock;
import com.opengamma.web.FreemarkerOutputter;
import com.opengamma.web.analytics.OtcSecurityVisitor;

/**
* REST resource for the trade blotter and trade entry forms.
*/
@Path("blotter")
public class BlotterResource {

  /** Map of underlying security types keyed by the owning security type. */
  private static final Map<Class<?>, Class<?>> s_underlyingSecurityTypes = ImmutableMap.<Class<?>, Class<?>>of(
      IRFutureOptionSecurity.class, InterestRateFutureSecurity.class,
      SwaptionSecurity.class, SwapSecurity.class,
      CreditDefaultSwapOptionSecurity.class, AbstractCreditDefaultSwapSecurity.class);

  /** Map of paths to the endpoints for looking up values, keyed by the value class. */
  private static final Map<Class<?>, String> s_endpoints = Maps.newHashMap();
  /** OTC Security types that can be created by the trade entry froms. */
  private static final List<String> s_securityTypeNames = Lists.newArrayList();
  /** Types that can be created by the trade entry forms that aren't securities by are required by them (e.g. legs). */
  private static final List<String> s_otherTypeNames = Lists.newArrayList();
  /** Meta beans for types that can be created by the trade entry forms keyed by type name. */
  private static final Map<String, MetaBean> s_metaBeansByTypeName = Maps.newHashMap();

  static {
    s_endpoints.put(Frequency.class, "frequencies");
    s_endpoints.put(ExerciseType.class, "exercisetypes");
    s_endpoints.put(DayCount.class, "daycountconventions");
    s_endpoints.put(BusinessDayConvention.class, "businessdayconventions");
    s_endpoints.put(BarrierType.class, "barriertypes");
    s_endpoints.put(BarrierDirection.class, "barrierdirections");
    s_endpoints.put(SamplingFrequency.class, "samplingfrequencies");
    s_endpoints.put(FloatingRateType.class, "floatingratetypes");
    s_endpoints.put(LongShort.class, "longshort");
    s_endpoints.put(MonitoringType.class, "monitoringtype");
    s_endpoints.put(DebtSeniority.class, "debtseniority");
    s_endpoints.put(RestructuringClause.class, "restructuringclause");
    s_endpoints.put(StubType.class, "stubtype");
    s_endpoints.put(InterpolationMethod.class, "interpolationmethods");

    for (MetaBean metaBean : BlotterUtils.getMetaBeans()) {
      Class<? extends Bean> beanType = metaBean.beanType();
      String typeName = beanType.getSimpleName();
      s_metaBeansByTypeName.put(typeName, metaBean);
      if (isSecurity(beanType)) {
        s_securityTypeNames.add(typeName);
      } else {
        s_otherTypeNames.add(typeName);
      }
    }
    s_otherTypeNames.add(OtcTradeBuilder.TRADE_TYPE_NAME);
    s_otherTypeNames.add(FungibleTradeBuilder.TRADE_TYPE_NAME);
    Collections.sort(s_otherTypeNames);
    Collections.sort(s_securityTypeNames);
  }

  /** For loading and saving securities. */
  private final SecurityMaster _securityMaster;
  /** For loading and saving positions. */
  private final PositionMaster _positionMaster;
  /** For creating output from Freemarker templates. */
  private FreemarkerOutputter _freemarker;
  /** For building and saving new trades and associated securities. */
  private final OtcTradeBuilder _otcTradeBuilder;
  /** For building trades in fungible securities. */
  private final FungibleTradeBuilder _fungibleTradeBuilder;
  /** For removing positions. */
  private final PortfolioMaster _portfolioMaster;

  public BlotterResource(SecurityMaster securityMaster, PortfolioMaster portfolioMaster, PositionMaster positionMaster) {
    ArgumentChecker.notNull(securityMaster, "securityMaster");
    ArgumentChecker.notNull(portfolioMaster, "portfolioMaster");
    ArgumentChecker.notNull(positionMaster, "positionMaster");
    _securityMaster = securityMaster;
    _positionMaster = positionMaster;
    _portfolioMaster = portfolioMaster;
    _fungibleTradeBuilder = new FungibleTradeBuilder(_positionMaster,
                                                     _portfolioMaster,
                                                     _securityMaster,
                                                     BlotterUtils.getMetaBeans(),
                                                     BlotterUtils.getStringConvert());
    _otcTradeBuilder = new OtcTradeBuilder(_positionMaster,
                                           _portfolioMaster,
                                           _securityMaster,
                                           BlotterUtils.getMetaBeans(),
                                           BlotterUtils.getStringConvert());
  }

  /* package */ static boolean isSecurity(Class<?> type) {
    if (type == null) {
      return false;
    } else if (ManageableSecurity.class.equals(type)) {
      return true;
    } else {
      return isSecurity(type.getSuperclass());
    }
  }

  @Context
  public void setServletContext(final ServletContext servletContext) {
    _freemarker = new FreemarkerOutputter(servletContext);
  }

  @GET
  @Produces(MediaType.TEXT_HTML)
  @Path("types")
  public String getTypes() {
    Map<Object, Object> data = map("title", "Types",
                                   "securityTypeNames", s_securityTypeNames,
                                   "otherTypeNames", s_otherTypeNames);
    return _freemarker.build("blotter/bean-types.ftl", data);
  }

  @SuppressWarnings("unchecked")
  @GET
  @Produces(MediaType.TEXT_HTML)
  @Path("types/{typeName}")
  public String getStructure(@PathParam("typeName") String typeName) {
    Map<String, Object> beanData;
    // TODO tell don't ask
    if (typeName.equals(OtcTradeBuilder.TRADE_TYPE_NAME)) {
      beanData = OtcTradeBuilder.tradeStructure();
    } else if (typeName.equals(FungibleTradeBuilder.TRADE_TYPE_NAME)) {
      beanData = FungibleTradeBuilder.tradeStructure();
    } else {
      MetaBean metaBean = s_metaBeansByTypeName.get(typeName);
      if (metaBean == null) {
        throw new DataNotFoundException("Unknown type name " + typeName);
      }
      BeanStructureBuilder structureBuilder = new BeanStructureBuilder(BlotterUtils.getMetaBeans(),
                                                                       s_underlyingSecurityTypes,
                                                                       s_endpoints,
                                                                       BlotterUtils.getStringConvert());
      BeanTraverser traverser = BlotterUtils.structureBuildingTraverser();
      beanData = (Map<String, Object>) traverser.traverse(metaBean, structureBuilder);
    }
    return _freemarker.build("blotter/bean-structure.ftl", beanData);
  }

  // for getting the security for fungible trades - the user can change the ID and see the trade details update

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("securities/{securityExternalId}")
  public String getSecurityJSON(@PathParam("securityExternalId") String securityExternalIdStr) {
    if (StringUtils.isEmpty(securityExternalIdStr)) {
      return new JSONObject().toString();
    }
    ExternalId securityExternalId = ExternalId.parse(securityExternalIdStr);
    SecuritySearchResult searchResult = _securityMaster.search(new SecuritySearchRequest(securityExternalId));
    if (searchResult.getSecurities().size() == 0) {
      throw new DataNotFoundException("No security found with ID " + securityExternalId);
    }
    ManageableSecurity security = searchResult.getFirstSecurity();
    BeanVisitor<JSONObject> securityVisitor =
        new BuildingBeanVisitor<>(security, new JsonDataSink(BlotterUtils.getJsonBuildingConverters()));
    BeanTraverser securityTraverser = BlotterUtils.securityJsonBuildingTraverser();
    MetaBean securityMetaBean = JodaBeanUtils.metaBean(security.getClass());
    JSONObject securityJson = (JSONObject) securityTraverser.traverse(securityMetaBean, securityVisitor);
    return securityJson.toString();
  }

  // for getting the trade and security data for an OTC trade/security/position
  // TODO move this logic to trade builder? can it populate a BeanDataSink to build the JSON?
  // TODO refactor this, it's ugly. can the security and trade logic be cleanly moved into classes for OTC & fungible?
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("trades/{tradeId}")
  public String getTradeJSON(@PathParam("tradeId") String tradeIdStr) {
    UniqueId tradeId = UniqueId.parse(tradeIdStr);
    if (!tradeId.isLatest()) {
      throw new IllegalArgumentException("The blotter can only be used to update the latest version of a trade");
    }
    ManageableTrade trade = _positionMaster.getTrade(tradeId);
    ManageableSecurityLink securityLink = trade.getSecurityLink();
    return buildTradeJSON(trade, securityLink);
  }

  // TODO does it matter if the trade is not the most recent?
  @DELETE
  @Produces(MediaType.APPLICATION_JSON)
  @Path("trades/{tradeId}")
  public Response deleteTrade(@PathParam("tradeId") String tradeIdStr) {
    UniqueId tradeId = UniqueId.parse(tradeIdStr);
    ManageableTrade trade = _positionMaster.getTrade(tradeId);
    PositionDocument positionDoc = _positionMaster.get(trade.getParentPositionId());
    positionDoc.getPosition().removeTrade(trade);
    _positionMaster.update(positionDoc);
    return Response.ok().build();
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("positions/{positionId}")
  public String getPositionJSON(@PathParam("positionId") String positionIdStr) {
    UniqueId positionId = UniqueId.parse(positionIdStr);
    if (!positionId.isLatest()) {
      throw new IllegalArgumentException("The blotter can only be used to update the latest version of a trade");
    }
    ManageablePosition position = _positionMaster.get(positionId).getPosition();
    ManageableSecurityLink securityLink = position.getSecurityLink();
    ManageableTrade trade = new ManageableTrade();
    trade.setSecurityLink(securityLink);
    if (position.getTrades().size() == 0) {
      trade.setTradeDate(LocalDate.now());
      trade.setCounterpartyExternalId(ExternalId.of(AbstractTradeBuilder.CPTY_SCHEME, AbstractTradeBuilder.DEFAULT_COUNTERPARTY));
      trade.setQuantity(position.getQuantity());
    }
    return buildTradeJSON(trade, securityLink);
  }

  private String buildTradeJSON(ManageableTrade trade, ManageableSecurityLink securityLink) {
    ManageableSecurity security = findSecurity(securityLink);
    JSONObject root = new JSONObject();
    try {
      JsonDataSink tradeSink = new JsonDataSink(BlotterUtils.getJsonBuildingConverters());
      if (isOtc(security)) {
        _otcTradeBuilder.extractTradeData(trade, tradeSink);
        MetaBean securityMetaBean = s_metaBeansByTypeName.get(security.getClass().getSimpleName());
        if (securityMetaBean == null) {
          throw new DataNotFoundException("No MetaBean is registered for security type " + security.getClass().getName());
        }
        BeanVisitor<JSONObject> securityVisitor =
            new BuildingBeanVisitor<>(security, new JsonDataSink(BlotterUtils.getJsonBuildingConverters()));
        BeanTraverser securityTraverser = BlotterUtils.securityJsonBuildingTraverser();
        JSONObject securityJson = (JSONObject) securityTraverser.traverse(securityMetaBean, securityVisitor);
        if (security instanceof FinancialSecurity) {
          UnderlyingSecurityVisitor visitor = new UnderlyingSecurityVisitor(VersionCorrection.LATEST, _securityMaster);
          ManageableSecurity underlying = ((FinancialSecurity) security).accept(visitor);
          if (underlying != null) {
            BeanVisitor<JSONObject> underlyingVisitor =
                new BuildingBeanVisitor<>(underlying, new JsonDataSink(BlotterUtils.getJsonBuildingConverters()));
            MetaBean underlyingMetaBean = s_metaBeansByTypeName.get(underlying.getClass().getSimpleName());
            JSONObject underlyingJson = (JSONObject) securityTraverser.traverse(underlyingMetaBean, underlyingVisitor);
            root.put("underlying", underlyingJson);
          }
        }
        root.put("security", securityJson);
      } else {
        _fungibleTradeBuilder.extractTradeData(trade, tradeSink, BlotterUtils.getStringConvert());
      }
      JSONObject tradeJson = tradeSink.finish();
      root.put("trade", tradeJson);
    } catch (JSONException e) {
      throw new OpenGammaRuntimeException("Failed to build JSON", e);
    }
    return root.toString();
  }

  private static boolean isOtc(ManageableSecurity security) {
    if (security instanceof FinancialSecurity) {
      return ((FinancialSecurity) security).accept(new OtcSecurityVisitor());
    } else {
      return false;
    }
  }

  /**
   * Finds the security referred to by securityLink. This basically does the same thing as resolving the link but
   * it returns a {@link ManageableSecurity} instead of a {@link Security}.
   * @param securityLink Contains the security ID(s)
   * @return The security, not null
   * @throws DataNotFoundException If a matching security can't be found
   */
  private ManageableSecurity findSecurity(ManageableSecurityLink securityLink) {
    SecurityDocument securityDocument;
    if (securityLink.getObjectId() != null) {
      // TODO do we definitely want the LATEST?
      securityDocument = _securityMaster.get(securityLink.getObjectId(), VersionCorrection.LATEST);
    } else {
      SecuritySearchRequest searchRequest = new SecuritySearchRequest(securityLink.getExternalId());
      SecuritySearchResult searchResult = _securityMaster.search(searchRequest);
      securityDocument = searchResult.getFirstDocument();
      if (securityDocument == null) {
        throw new DataNotFoundException("No security found with external IDs " + securityLink.getExternalId());
      }
    }
    return securityDocument.getSecurity();
  }

  @POST
  @Path("trades")
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Produces(MediaType.APPLICATION_JSON)
  public Response createTrade(@Context UriInfo uriInfo, @FormParam("trade") String jsonStr) {
    // TODO need to make sure this can't be used for updating existing trades, only for creating new ones
    try {
      JSONObject json = new JSONObject(jsonStr);
      JSONObject tradeJson = json.getJSONObject("trade");
      UniqueId nodeId = UniqueId.parse(json.getString("nodeId"));
      String tradeTypeName = tradeJson.getString("type");
      // TODO tell don't ask - it is an option to ask each of the trade builders? but their interfaces are different
      // they would have to know how to extract the subsections of the JSON
      UniqueId tradeId;
      if (tradeTypeName.equals(OtcTradeBuilder.TRADE_TYPE_NAME)) {
        tradeId =  createOtcTrade(json, tradeJson, nodeId);
      } else if (tradeTypeName.equals(FungibleTradeBuilder.TRADE_TYPE_NAME)) {
        tradeId = _fungibleTradeBuilder.addTrade(new JsonBeanDataSource(tradeJson), nodeId);
      } else {
        throw new IllegalArgumentException("Unknown trade type " + tradeTypeName);
      }
      URI createdTradeUri = uriInfo.getAbsolutePathBuilder().path(tradeId.getObjectId().toString()).build();
      return Response.status(Response.Status.CREATED).header("Location", createdTradeUri).build();
    } catch (JSONException e) {
      throw new IllegalArgumentException("Failed to parse JSON", e);
    }
  }

  /* TODO endpoint for updating positions that don't have any trades?
  create a dummy trade for the position amount?
  what about
    * positions with trades whose position and trade amounts are inconsistent
    * adding trade to positions with no trades but a position amount? create 2 trades?
    * editing a position but not by adding a trade? leave empty and update the amount?
  should editing a position always leave it with a consistent set of trades? even if it's empty?
  */

  @PUT
  @Path("positions/{positionIdStr}")
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Produces(MediaType.APPLICATION_JSON)
  public void updatePosition(@FormParam("trade") String jsonStr, @PathParam("positionIdStr") String positionIdStr) {
    try {
      UniqueId positionId = UniqueId.parse(positionIdStr);
      JSONObject json = new JSONObject(jsonStr);
      JSONObject tradeJson = json.getJSONObject("trade");
      String tradeTypeName = tradeJson.getString("type");
      // TODO tell don't ask - ask each of the existing trade builders until one of them can handle it?
      if (tradeTypeName.equals(OtcTradeBuilder.TRADE_TYPE_NAME)) {
        updateOtcPosition(positionId, json, tradeJson);
      } else if (tradeTypeName.equals(FungibleTradeBuilder.TRADE_TYPE_NAME)) {
        _fungibleTradeBuilder.updatePosition(new JsonBeanDataSource(tradeJson), positionId);
      } else {
        throw new IllegalArgumentException("Unknown trade type " + tradeTypeName);
      }
    } catch (JSONException e) {
      throw new IllegalArgumentException("Failed to parse JSON", e);
    }
  }

  @PUT
  @Path("trades/{tradeIdStr}")
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Produces(MediaType.APPLICATION_JSON)
  public void updateTrade(@FormParam("trade") String jsonStr, @PathParam("tradeIdStr") String tradeIdStr) {
    try {
      // TODO what should happen to this? the ID is also in the JSON. check they match?
      @SuppressWarnings("unused")
      UniqueId tradeId = UniqueId.parse(tradeIdStr);
      JSONObject json = new JSONObject(jsonStr);
      JSONObject tradeJson = json.getJSONObject("trade");
      String tradeTypeName = tradeJson.getString("type");
      // TODO tell don't ask - ask each of the existing trade builders until one of them can handle it?
      if (tradeTypeName.equals(OtcTradeBuilder.TRADE_TYPE_NAME)) {
        createOtcTrade(json, tradeJson, null);
      } else if (tradeTypeName.equals(FungibleTradeBuilder.TRADE_TYPE_NAME)) {
        _fungibleTradeBuilder.updateTrade(new JsonBeanDataSource(tradeJson));
      } else {
        throw new IllegalArgumentException("Unknown trade type " + tradeTypeName);
      }
    } catch (JSONException e) {
      throw new IllegalArgumentException("Failed to parse JSON", e);
    }
  }

  // TODO this could move inside the builder
  private UniqueId createOtcTrade(JSONObject json, JSONObject tradeJson, UniqueId nodeId) {
    try {
      JSONObject securityJson = json.getJSONObject("security");
      JSONObject underlyingJson = json.optJSONObject("underlying");
      BeanDataSource tradeData = new JsonBeanDataSource(tradeJson);
      BeanDataSource securityData = new JsonBeanDataSource(securityJson);
      BeanDataSource underlyingData;
      if (underlyingJson != null) {
        underlyingData = new JsonBeanDataSource(underlyingJson);
      } else {
        underlyingData = null;
      }
      if (nodeId == null) {
        return _otcTradeBuilder.updateTrade(tradeData, securityData, underlyingData);
      } else {
        return _otcTradeBuilder.addTrade(tradeData, securityData, underlyingData, nodeId);
      }
    } catch (JSONException e) {
      throw new IllegalArgumentException("Failed to parse JSON", e);
    }
  }

  private UniqueId updateOtcPosition(UniqueId positionId, JSONObject json, JSONObject tradeJson) {
    try {
      JSONObject securityJson = json.getJSONObject("security");
      JSONObject underlyingJson = json.optJSONObject("underlying");
      BeanDataSource tradeData = new JsonBeanDataSource(tradeJson);
      BeanDataSource securityData = new JsonBeanDataSource(securityJson);
      BeanDataSource underlyingData;
      if (underlyingJson != null) {
        underlyingData = new JsonBeanDataSource(underlyingJson);
      } else {
        underlyingData = null;
      }
      return _otcTradeBuilder.updatePosition(positionId, tradeData, securityData, underlyingData);
    } catch (JSONException e) {
      throw new IllegalArgumentException("Failed to parse JSON", e);
    }
  }

  private static Map<Object, Object> map(Object... values) {
    final Map<Object, Object> result = Maps.newHashMap();
    for (int i = 0; i < values.length / 2; i++) {
      result.put(values[i * 2], values[(i * 2) + 1]);
    }
    result.put("now", ZonedDateTime.now(OpenGammaClock.getInstance()));
    return result;
  }

  @Path("lookup")
  public BlotterLookupResource getLookupResource() {
    return new BlotterLookupResource(BlotterUtils.getStringConvert());
  }
}
TOP

Related Classes of com.opengamma.web.analytics.blotter.BlotterResource

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.