Package com.opengamma.integration.copier.portfolio.rowparser

Source Code of com.opengamma.integration.copier.portfolio.rowparser.JodaBeanRowParser

/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.integration.copier.portfolio.rowparser;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang.builder.HashCodeBuilder;
import org.fudgemsg.AnnotationReflector;
import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.PropertyReadWrite;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.financial.conversion.JodaBeanConverters;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.financial.security.equity.EquitySecurity;
import com.opengamma.financial.security.future.AgricultureFutureSecurity;
import com.opengamma.financial.security.future.EquityFutureSecurity;
import com.opengamma.financial.security.future.FXFutureSecurity;
import com.opengamma.financial.security.future.InterestRateFutureSecurity;
import com.opengamma.financial.security.future.MetalFutureSecurity;
import com.opengamma.financial.security.option.EquityBarrierOptionSecurity;
import com.opengamma.financial.security.option.EquityOptionSecurity;
import com.opengamma.financial.security.option.IRFutureOptionSecurity;
import com.opengamma.financial.security.option.SwaptionSecurity;
import com.opengamma.financial.security.swap.FixedInterestRateLeg;
import com.opengamma.financial.security.swap.FixedVarianceSwapLeg;
import com.opengamma.financial.security.swap.FloatingGearingIRLeg;
import com.opengamma.financial.security.swap.FloatingInterestRateLeg;
import com.opengamma.financial.security.swap.FloatingSpreadIRLeg;
import com.opengamma.financial.security.swap.FloatingVarianceSwapLeg;
import com.opengamma.financial.security.swap.InterestRateLeg;
import com.opengamma.financial.security.swap.Notional;
import com.opengamma.financial.security.swap.SwapLeg;
import com.opengamma.financial.security.swap.SwapSecurity;
import com.opengamma.financial.security.swap.VarianceSwapLeg;
import com.opengamma.master.position.ManageablePosition;
import com.opengamma.master.position.ManageableTrade;
import com.opengamma.master.security.ManageableSecurity;
import com.opengamma.master.security.ManageableSecurityLink;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;

/**
* A generic row parser for Joda beans that automatically identifies fields to be persisted to rows/populated from rows
*/
public class JodaBeanRowParser extends RowParser {

  private static final Logger s_logger = LoggerFactory.getLogger(JodaBeanRowParser.class);
  
  /**
   * Security properties to ignore when scanning
   */
  private static final String[] IGNORE_METAPROPERTIES = {
    "attributes",
    "uniqueid",
    "objectid",
    "securitylink",
    "trades",
    "attributes",
    "gicscode",
    "parentpositionid",
    "providerid",
    "deal"
  };
 
  /**
   * Column prefixes
   */
  private static final String POSITION_PREFIX = "position";
  private static final String TRADE_PREFIX = "trade";
  private static final String UNDERLYING_PREFIX = "underlying";
 
  /**
   * Every security class name ends with this
   */
  private static final String CLASS_POSTFIX = "Security";

  /**
   * The security class that this parser is adapted to
   */
  private Class<? extends Bean> _securityClass;
 
  /**
   * The underlying security class(es) for the security class above
   */
  private List<Class<?>> _underlyingSecurityClasses;
 
 
  /**
   *  Map from column name to the field's Java type
   */
  private SortedMap<String, Class<?>> _columns = new TreeMap<String, Class<?>>();

  static {
    //Make refections available by calling AnnotationReflector.getDefaultReflector()
    OpenGammaFudgeContext.getInstance();
    // Register the automatic string converters with Joda Beans
    JodaBeanConverters.getInstance();

    // Force registration of various meta beans that might not have been loaded yet
    ManageablePosition.meta();
    ManageableTrade.meta();
    Notional.meta();
    SwapLeg.meta();
    InterestRateLeg.meta();
    FixedInterestRateLeg.meta();
    FloatingInterestRateLeg.meta();
    FloatingGearingIRLeg.meta();
    FloatingSpreadIRLeg.meta();
    VarianceSwapLeg.meta();
    FixedVarianceSwapLeg.meta();
    FloatingVarianceSwapLeg.meta();
    EquitySecurity.meta();
    SwapSecurity.meta();
    InterestRateFutureSecurity.meta();
    MetalFutureSecurity.meta();
    AgricultureFutureSecurity.meta();
    FXFutureSecurity.meta();
    SwaptionSecurity.meta();
  }
 
  protected JodaBeanRowParser(String securityName) throws OpenGammaRuntimeException {
   
    ArgumentChecker.notEmpty(securityName, "securityName");
   
    // Find the corresponding security class
    _securityClass = getClass(securityName + CLASS_POSTFIX);

    // Find the underlying(s)
    _underlyingSecurityClasses = getUnderlyingSecurityClasses(_securityClass);
   
    // Set column map
    _columns = recursiveGetColumnMap(_securityClass, "");
    for (Class<?> securityClass : _underlyingSecurityClasses) {
      _columns.putAll(recursiveGetColumnMap(securityClass, UNDERLYING_PREFIX + securityClass.getSimpleName() + ":"));
    }
    _columns.putAll(recursiveGetColumnMap(ManageablePosition.class, POSITION_PREFIX + ":"));
    _columns.putAll(recursiveGetColumnMap(ManageableTrade.class, TRADE_PREFIX + ":"));
  }

  private List<Class<?>> getUnderlyingSecurityClasses(Class<? extends Bean> securityClass) {
   
    List<Class<?>> result = new ArrayList<Class<?>>();
         
    // Futures
    if (EquityFutureSecurity.class.isAssignableFrom(securityClass)) {
      result.add(EquitySecurity.class);
     
    // Options
    } else if (EquityBarrierOptionSecurity.class.isAssignableFrom(securityClass)) {
      result.add(EquitySecurity.class);
    } else if (EquityOptionSecurity.class.isAssignableFrom(securityClass)) {
      result.add(EquitySecurity.class);     
    } else if (IRFutureOptionSecurity.class.isAssignableFrom(securityClass)) {
      result.add(InterestRateFutureSecurity.class);
    } else if (SwaptionSecurity.class.isAssignableFrom(securityClass)) {
      result.add(SwapSecurity.class);
    }

    return result;
  }

  /**
   * Creates a new row parser for the specified security type and tool context
   * @param securityName  the type of the security for which a row parser is to be created
   * @return              the RowParser class for the specified security type, or null if unable to identify a suitable parser
   */
  public static JodaBeanRowParser newJodaBeanRowParser(String securityName) {
    // Now using the JodaBean parser
   
    ArgumentChecker.notEmpty(securityName, "securityName");
   
    try {
      return new JodaBeanRowParser(securityName);
    } catch (Throwable e) {
      throw new OpenGammaRuntimeException("Could not create a row parser for security type " + securityName, e);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Import routines: construct security(ies), position, trade
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
  @Override
  public ManageableSecurity[] constructSecurity(Map<String, String> row) {
   
    ArgumentChecker.notNull(row, "row");
   
    ManageableSecurity security = (ManageableSecurity) recursiveConstructBean(row, _securityClass, "");
    if (security != null) {
      ArrayList<ManageableSecurity> securities = new ArrayList<ManageableSecurity>();
      securities.add(security);
      for (Class<?> underlyingClass : _underlyingSecurityClasses) {
        ManageableSecurity underlying = (ManageableSecurity) recursiveConstructBean(row, underlyingClass, UNDERLYING_PREFIX + underlyingClass.getSimpleName().toLowerCase() + ":");
        if (underlying != null) {
          securities.add(underlying);
        } else {
          s_logger.warn("Could not populate underlying security of type " + underlyingClass);
        }
      }
      return securities.toArray(new ManageableSecurity[securities.size()]);
    } else {
      return null;
    }
  }
 
  @Override
  public ManageablePosition constructPosition(Map<String, String> row, ManageableSecurity security) {
   
    ArgumentChecker.notNull(row, "row");
    ArgumentChecker.notNull(security, "security");
   
    ManageablePosition result = (ManageablePosition) recursiveConstructBean(row, ManageablePosition.class, "position:");
    if (result != null) {
      result.setSecurityLink(new ManageableSecurityLink(security.getExternalIdBundle()));
    }
    return result;
  }

  @Override
  public ManageableTrade constructTrade(Map<String, String> row, ManageableSecurity security, ManageablePosition position) {

    ArgumentChecker.notNull(row, "row");
    ArgumentChecker.notNull(security, "security");
    ArgumentChecker.notNull(position, "position");
   
    ManageableTrade result = (ManageableTrade) recursiveConstructBean(row, ManageableTrade.class, "trade:");
    if (result != null) {
      if (result.getTradeDate() == null) {
        return null;
      }
      result.setSecurityLink(new ManageableSecurityLink(security.getExternalIdBundle()));
    }
    return result;
  }
 
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Export routines: construct row from security, position, trade
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  
  @Override
  public Map<String, String> constructRow(ManageableSecurity[] securities) {
    ArgumentChecker.notNull(securities, "securities");
    Map<String, String> result = recursiveConstructRow(securities[0], "");
   
    for (int i = 1; i < securities.length; i++) {
      result.putAll(recursiveConstructRow(securities[i], UNDERLYING_PREFIX + securities[i].getClass().getSimpleName() + ":"));
    }
    return result;
  }
 
  @Override
  public Map<String, String> constructRow(ManageablePosition position) {
    ArgumentChecker.notNull(position, "position");
    return recursiveConstructRow(position, "position:");
  }

  @Override
  public Map<String, String> constructRow(ManageableTrade trade) {
    ArgumentChecker.notNull(trade, "trade");
    return recursiveConstructRow(trade, "trade:");
  }
 
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Utility routines
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  @Override
  public String[] getColumns() {
    return _columns.keySet().toArray(new String[_columns.size()]);
  }

  @Override
  public int getSecurityHashCode() {
    HashCodeBuilder hashCodeBuilder = new HashCodeBuilder();
    for (Entry<String, Class<?>> entry : _columns.entrySet()) {     
      hashCodeBuilder.append(entry.getKey());
      hashCodeBuilder.append(entry.getValue().getCanonicalName());
    }
    return hashCodeBuilder.toHashCode();
  }

  /**
   * Extract a map of column (field) names and types from the properties of the specified direct bean class.
   * Appropriate member classes (such as swap legs) are recursively traversed and their columns also extracted
   * and added to the map.
   * @param clazz   The bean type from which to extract properties
   * @param prefix  The class membership path traced from the top-level bean class to the current class
   * @return        A map of the column names and their types
   */
  private SortedMap<String, Class<?>> recursiveGetColumnMap(Class<?> clazz, String prefix) {
    // Scan through and capture the list of relevant properties and their types
    SortedMap<String, Class<?>> columns = new TreeMap<String, Class<?>>();
   
    for (MetaProperty<?> metaProperty : JodaBeanUtils.metaBean(clazz).metaPropertyIterable()) {
       
      // Skip any undesired properties, process the rest
      if (!ignoreMetaProperty(metaProperty)) {
       
        // Add a column for the property (used either for the actual value
        // or for the class name in the case of a non-convertible bean
        columns.put(prefix + metaProperty.name(), metaProperty.propertyType());

        // If this is a bean without a converter recursively extract all
        // columns for the metabean and all its subclasses
        if (isBean(metaProperty.propertyType()) && !isConvertible(metaProperty.propertyType())) {
         
          // This is the bean (might be an abstract class/subclassed)
          Class<? extends Bean> beanClass = metaProperty.propertyType().asSubclass(Bean.class);
         
          // Recursively extract this bean's properties
          columns.putAll(recursiveGetColumnMap(beanClass, prefix + metaProperty.name() + ":"));

          // Identify ALL subclasses of this bean and extract all their properties
          for (Class<?> subClass : getSubClasses(beanClass)) {
            columns.putAll(recursiveGetColumnMap(subClass, prefix + metaProperty.name() + ":"));           
          }
        }
      }
    }
    return columns;
  }
 
  /**
   * Build a bean of the specified type by extracting property values from the supplied map of field names to
   * values, using recursion to construct the member beans in the same manner.
   * @param row     The map from property (or column, or field) names to values
   * @param clazz   The bean type of which to construct an instance
   * @param prefix  The class membership path traced from the top-level bean class to the current class
   * @return        The constructed security bean
   */
  private Bean recursiveConstructBean(Map<String, String> row, Class<?> clazz, String prefix) {
    try {
      // Get a reference to the meta-bean
      MetaBean metaBean = JodaBeanUtils.metaBean(clazz);

      // Get a new builder from the meta-bean
      BeanBuilder<? extends Bean> builder = metaBean.builder();

      // Populate the bean from the supplied row using the builder
      for (MetaProperty<?> metaProperty : JodaBeanUtils.metaBean(clazz).metaPropertyIterable()) {

        // Skip any undesired properties, process the rest
        if (!ignoreMetaProperty(metaProperty)) {

          // If this property is itself a bean without a converter, recurse to populate relevant fields
          if (isBean(metaProperty.propertyType()) && !isConvertible(metaProperty.propertyType())) {
 
            // Get the actual type of this bean from the relevant column
            String className = row.get((prefix + metaProperty.name()).trim().toLowerCase());
            Class<? extends Bean> beanClass = getClass(className);
           
            // Recursively set properties
            builder.set(metaProperty.name(),
                recursiveConstructBean(row, beanClass, prefix + metaProperty.name() + ":"));
 
          // If not a bean, or it is a bean for which a converter exists, just set value in builder using joda convert
          } else {
            // Convert raw value in row to the target property's type
            String rawValue = row.get((prefix + metaProperty.name()).trim().toLowerCase());
           
            if (isConvertible(metaProperty.propertyType())) {
              // Set property value
              if (rawValue != null && !rawValue.equals("")) {
                builder.set(metaProperty.name(),
                    JodaBeanUtils.stringConverter().convertFromString(metaProperty.propertyType(), rawValue));
              } else {
                s_logger.info("Skipping empty or null value for " + prefix + metaProperty.name());
              }
            } else if (List.class.isAssignableFrom(metaProperty.propertyType()) &&
                isConvertible(JodaBeanUtils.collectionType(metaProperty, metaProperty.propertyType()))) {
              builder.set(metaProperty.name(), stringToList(rawValue, JodaBeanUtils.collectionType(metaProperty, metaProperty.propertyType())));
            } else {
              throw new OpenGammaRuntimeException("Property '" + prefix + metaProperty.name() + "' (" + metaProperty.propertyType() + ") cannot be populated from a string");
            }
          }
        }
      }
     
      // Actually build the bean
      return builder.build();
 
    } catch (Throwable ex) {
      s_logger.info("Not creating a " + clazz.getSimpleName() + ": " + ex);
      return null;
    }
  }
 
  /**
   * Extracts a map of column names to values from a supplied security bean's properties, using recursion to
   * extract properties from any member beans.
   * @param bean    The bean instance from which to extract property values
   * @param prefix  The class membership path traced from the top-level bean class to the current class
   * @return        A map of extracted column names and values
   */
  private Map<String, String> recursiveConstructRow(Bean bean, String prefix) {
    Map<String, String> result = new HashMap<String, String>();
   
    // Populate the row from the bean's properties
    for (MetaProperty<?> metaProperty : bean.metaBean().metaPropertyIterable()) {
     
      // Skip any undesired properties, process the rest
      if (!ignoreMetaProperty(metaProperty)) {
        // If this property is itself a bean without a converter, recurse to populate relevant columns
        if (isBean(metaProperty.propertyType()) && !isConvertible(metaProperty.propertyType())) {
         
          // Store the class name in a separate column (to help identify the correct subclass during loading)
          result.put(prefix + metaProperty.name(), metaProperty.get(bean).getClass().getSimpleName());
         
          // Recursively extract bean's columns       
          result.putAll(recursiveConstructRow((Bean) metaProperty.get(bean), prefix + metaProperty.name() + ":"));
         
        // If not a bean, or it is a bean for which a converter exists, just extract its value using joda convert
        } else {
          // Set the column
          if (_columns.containsKey(prefix + metaProperty.name())) {
            // Can convert
            if (isConvertible(metaProperty.propertyType())) {
              result.put(prefix + metaProperty.name(), metaProperty.getString(bean));
            // Is list, needs to be decomposed
            } else if (List.class.isAssignableFrom(metaProperty.propertyType()) &&
                isConvertible(JodaBeanUtils.collectionType(metaProperty, metaProperty.propertyType()))) {
              result.put(prefix + metaProperty.name(), listToString((List<?>) metaProperty.get(bean)));
            // Cannot convert :(
            } else {
              throw new OpenGammaRuntimeException("Property '" + prefix + metaProperty.name() + "' (" + metaProperty.propertyType() + ") cannot be converted to a string");
            }
          } else {
            s_logger.info("No matching column found for property " + prefix + metaProperty.name());
          }
        }
      }
    }
    return result;
  }

  /**
   * Converts a list of objects to a |-separated string of their JodaConverted string representations.
   *
   * @param list  the list to be converted, not null
   * @return the |-separated string string, not null
   */
  private String listToString(List<?> list) {
    String result = "";
    for (Object o : list) {
      if (isConvertible(o.getClass())) {
        result = result + JodaBeanUtils.stringConverter().convertToString(o) + " | ";
      } else {
        throw new OpenGammaRuntimeException("Cannot convert " + o.getClass() + " contained in list");
      }
    }
    return result.substring(0, result.lastIndexOf('|')).trim();
  }

  /**
   * Converts a |-separated string to a list of objects using JodaConvert.
   *
   * @param rawStr  the string to parse, not null
   * @param cls  the class to convert to, not null
   * @return the list of objects of the specified class, not null
   */
  private List<?> stringToList(String rawStr, Class<?> cls) {
    List<Object> result = new ArrayList<Object>();
    for (String s : rawStr.split("\\|")) {
      result.add(JodaBeanUtils.stringConverter().convertFromString(cls, s.trim()));
    }
    return result;
  }

  /**
   * Given a class name, look for the class in the list of packages specified by CLASS_PACKAGES and return it
   * or throw exception if not found.
   *
   * @param className  the class name to seek, not null
   * @return the corresponding class, not null
   */
  private Class<? extends Bean> getClass(String className) {
    Class<? extends Bean> theClass = null;
    if (className.endsWith(CLASS_POSTFIX)) {
      theClass = getFinancialSecurityClass(className);
    } else {
      theClass = getJodaBeanSubType(className);
    }
   
    if (theClass == null) {
      throw new OpenGammaRuntimeException("Could not load class " + className);
    }
    return theClass;
  }

  private Class<? extends Bean> getJodaBeanSubType(String className) {
    Reflections reflections = AnnotationReflector.getDefaultReflector().getReflector();
    Set<String> directBeanSubTypes = reflections.getStore().getSubTypesOf(Bean.class.getName());
  
    Class<? extends Bean> theClass = null;
    for (String directBeanType : directBeanSubTypes) {
      try {
        if (directBeanType.endsWith("." + className)) {
          theClass = Class.forName(directBeanType).asSubclass(Bean.class);
          break;
        }
      } catch (Throwable ex) {
      }
    }
    return theClass;
  }

  private Class<? extends Bean> getFinancialSecurityClass(String className) {
    Reflections reflections = AnnotationReflector.getDefaultReflector().getReflector();
    Set<String> financialSecuritySubTypes = reflections.getStore().getSubTypesOf(FinancialSecurity.class.getName());
  
    Class<? extends Bean> theClass = null;
    for (String securityType : financialSecuritySubTypes) {
      try {
        if (securityType.endsWith("." + className)) {
          theClass = Class.forName(securityType).asSubclass(Bean.class);
          break;
        }
      } catch (Throwable ex) {
      }
    }
    return theClass;
  }

  /**
   * Given a bean class, find its subclasses.
   *
   * @param beanClass  the bean class
   * @return the collection of subclasses
   */
  private Collection<Class<?>> getSubClasses(Class<? extends Bean> beanClass) {
    Collection<Class<?>> subClasses = new ArrayList<Class<?>>();
   
    Reflections reflections = AnnotationReflector.getDefaultReflector().getReflector();
    Set<String> subTypes = reflections.getStore().getSubTypesOf(beanClass.getName());
    for (String subType : subTypes) {
      try {
        subClasses.add(Class.forName(subType));
      } catch (Throwable ex) {
      }
    }
    return (Collection<Class<?>>) subClasses;
  }

  /**
   * Checks whether the supplied class has a registered Joda string converter
   * @param clazz   the class to check
   * @return        the answer
   */
  private boolean isConvertible(Class<?> clazz) {
    try {
      JodaBeanUtils.stringConverter().findConverter(clazz);
      return true;
    } catch (Throwable ex) {
      return false;
    }
  }

  /**
   * Determines whether the supplied class is a direct bean.
   *
   * @param clazz  the class to check, not null
   * @return true if it is a bean
   */
  private boolean isBean(Class<?> clazz) {
    return Bean.class.isAssignableFrom(clazz);
  }

  /**
   * Checks whether the specified meta-property is to be ignored when extracting fields.
   *
   * @param mp  the meta-property to check, not null
   * @return true if it is to be ignored
   */
  private boolean ignoreMetaProperty(MetaProperty<?> mp) {
    if (mp.readWrite() != PropertyReadWrite.READ_WRITE) {
      return true;
    }
    String s = mp.name().trim().toLowerCase();
    for (String t : IGNORE_METAPROPERTIES) {
      if (s.equals(t.trim().toLowerCase())) {
        return true;
      }
    }
    return false;
  }

}
TOP

Related Classes of com.opengamma.integration.copier.portfolio.rowparser.JodaBeanRowParser

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.