com.opengamma.integration.copier.portfolio.rowparser.JodaBeanRowParser.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.integration.copier.portfolio.rowparser.JodaBeanRowParser.java

Source

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

}