com.opengamma.strata.loader.fpml.FpmlDocumentParser Maven / Gradle / Ivy
Show all versions of strata-loader Show documentation
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.loader.fpml;
import static com.opengamma.strata.collect.Guavate.toImmutableSet;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteSource;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.collect.io.XmlElement;
import com.opengamma.strata.collect.io.XmlFile;
import com.opengamma.strata.collect.named.ExtendedEnum;
import com.opengamma.strata.product.Trade;
/**
* Loader of trade data in FpML format.
*
* This handles the subset of FpML necessary to populate the trade model.
* The standard parsers accept FpML v5.8, which is often the same as earlier versions.
*
* The trade parsers implement {@link FpmlParserPlugin} and are pluggable using
* the {@code FpmlParserPlugin.ini} configuration file.
*/
public final class FpmlDocumentParser {
// Notes: Streaming trades directly from the file is difficult due to the
// need to parse the party element at the root, which is after the trades
/**
* The lookup of trade parsers.
*/
static final ExtendedEnum ENUM_LOOKUP = ExtendedEnum.of(FpmlParserPlugin.class);
/**
* The selector used to find "our" party within the set of parties in the FpML document.
*/
private final FpmlPartySelector ourPartySelector;
/**
* The trade info parser.
*/
private final FpmlTradeInfoParserPlugin tradeInfoParser;
/**
* The trade parsers.
*/
private final Map tradeParsers;
/**
* The reference data.
*/
private final ReferenceData refData;
/**
* Flag indicating whether to be strict about the presence of unsupported elements.
*/
private final boolean strictValidation;
//-------------------------------------------------------------------------
/**
* Obtains an instance of the parser, based on the specified selector.
*
* The FpML parser has a number of plugin points that can be controlled:
*
* - the {@linkplain FpmlPartySelector party selector}
*
- the {@linkplain FpmlTradeInfoParserPlugin trade info parser}
*
- the {@linkplain FpmlParserPlugin trade parsers}
*
- the {@linkplain ReferenceData reference data}
*
* This method uses the {@linkplain FpmlTradeInfoParserPlugin#standard() standard}
* trade info parser, the trade parsers registered in {@link FpmlParserPlugin}
* configuration and the {@linkplain ReferenceData#standard() standard} reference data.
*
* @param ourPartySelector the selector used to find "our" party within the set of parties in the FpML document
* @return the document parser
*/
public static FpmlDocumentParser of(FpmlPartySelector ourPartySelector) {
return of(ourPartySelector, FpmlTradeInfoParserPlugin.standard());
}
/**
* Obtains an instance of the parser, based on the specified selector and trade info plugin.
*
* The FpML parser has a number of plugin points that can be controlled:
*
* - the {@linkplain FpmlPartySelector party selector}
*
- the {@linkplain FpmlTradeInfoParserPlugin trade info parser}
*
- the {@linkplain FpmlParserPlugin trade parsers}
*
- the {@linkplain ReferenceData reference data}
*
* This method uses the trade parsers registered in {@link FpmlParserPlugin} configuration
* and the {@linkplain ReferenceData#standard() standard} reference data.
*
* @param ourPartySelector the selector used to find "our" party within the set of parties in the FpML document
* @param tradeInfoParser the trade info parser
* @return the document parser
*/
public static FpmlDocumentParser of(
FpmlPartySelector ourPartySelector,
FpmlTradeInfoParserPlugin tradeInfoParser) {
return of(ourPartySelector, tradeInfoParser, FpmlParserPlugin.extendedEnum().lookupAllNormalized());
}
/**
* Obtains an instance of the parser, based on the specified selector, trade info plugin and reference data.
*
* The FpML parser has a number of plugin points that can be controlled:
*
* - the {@linkplain FpmlPartySelector party selector}
*
- the {@linkplain FpmlTradeInfoParserPlugin trade info parser}
*
- the {@linkplain FpmlParserPlugin trade parsers}
*
- the {@linkplain ReferenceData reference data}
*
* This method uses the trade parsers registered in {@link FpmlParserPlugin} configuration.
*
* @param ourPartySelector the selector used to find "our" party within the set of parties in the FpML document
* @param tradeInfoParser the trade info parser
* @param refData the reference data to use
* @return the document parser
*/
public static FpmlDocumentParser of(
FpmlPartySelector ourPartySelector,
FpmlTradeInfoParserPlugin tradeInfoParser,
ReferenceData refData) {
return of(ourPartySelector, tradeInfoParser, FpmlParserPlugin.extendedEnum().lookupAllNormalized(), refData);
}
/**
* Obtains an instance of the parser, based on the specified selector and plugins.
*
* The FpML parser has a number of plugin points that can be controlled:
*
* - the {@linkplain FpmlPartySelector party selector}
*
- the {@linkplain FpmlTradeInfoParserPlugin trade info parser}
*
- the {@linkplain FpmlParserPlugin trade parsers}
*
- the {@linkplain ReferenceData reference data}
*
* This method uses the {@linkplain ReferenceData#standard() standard} reference data.
*
* @param ourPartySelector the selector used to find "our" party within the set of parties in the FpML document
* @param tradeInfoParser the trade info parser
* @param tradeParsers the map of trade parsers, keyed by the FpML element name
* @return the document parser
*/
public static FpmlDocumentParser of(
FpmlPartySelector ourPartySelector,
FpmlTradeInfoParserPlugin tradeInfoParser,
Map tradeParsers) {
return of(ourPartySelector, tradeInfoParser, tradeParsers, ReferenceData.standard());
}
/**
* Obtains an instance of the parser, based on the specified selector and plugins.
*
* The FpML parser has a number of plugin points that can be controlled:
*
* - the {@linkplain FpmlPartySelector party selector}
*
- the {@linkplain FpmlTradeInfoParserPlugin trade info parser}
*
- the {@linkplain FpmlParserPlugin trade parsers}
*
- the {@linkplain ReferenceData reference data}
*
*
* @param ourPartySelector the selector used to find "our" party within the set of parties in the FpML document
* @param tradeInfoParser the trade info parser
* @param tradeParsers the map of trade parsers, keyed by the FpML element name
* @param refData the reference data to use
* @return the document parser
*/
public static FpmlDocumentParser of(
FpmlPartySelector ourPartySelector,
FpmlTradeInfoParserPlugin tradeInfoParser,
Map tradeParsers,
ReferenceData refData) {
return new FpmlDocumentParser(ourPartySelector, tradeInfoParser, tradeParsers, refData, true);
}
//-------------------------------------------------------------------------
/**
* Creates an instance, based on the specified element.
*
* @param ourPartySelector the selector used to find "our" party within the set of parties in the FpML document
* @param tradeInfoParser the trade info parser
* @param tradeParsers the map of trade parsers, keyed by the FpML element name
* @param strictValidation flag indicating whether to be strict when validating which elements
* are present in the FpML document
*/
private FpmlDocumentParser(
FpmlPartySelector ourPartySelector,
FpmlTradeInfoParserPlugin tradeInfoParser,
Map tradeParsers,
ReferenceData refData,
boolean strictValidation) {
this.ourPartySelector = ourPartySelector;
this.tradeInfoParser = tradeInfoParser;
this.tradeParsers = tradeParsers;
this.refData = refData;
this.strictValidation = strictValidation;
}
//-------------------------------------------------------------------------
/**
* Obtains a 'lenient' version of this parser instance.
*
* In 'lenient' mode any FpML elements unsupported in Strata will be silently ignored (rather than resulting in errors).
*
* @return the lenient document parser
*/
public FpmlDocumentParser withLenientMode() {
return new FpmlDocumentParser(ourPartySelector, tradeInfoParser, tradeParsers, refData, false);
}
/**
* Basic check to see if the source can probably be parsed as FpML.
*
* This checks the first part of the byte source to see if it appears to be FpML.
* This is determined by looking for "fpml" (case insensitive) within the first 2000 characters.
*
* A more thorough check would involve parsing the XML, which is relatively slow.
* If you want to perform the more thorough check benchmarking indicates you might
* as well just parse the whole document.
*
* @param source the source to check
* @return true if the source appears to be FpML
* @throws UncheckedIOException if an IO error occurred
*/
public boolean isKnownFormat(ByteSource source) {
try {
byte[] array = source.slice(0, 2000).read();
for (int i = 0; i < array.length - 7; i++) {
// the `| 32` converts upper case letters to lower case ones
if ((array[i] | 32) == 'f') {
// UTF-8
if ((array[i + 1] | 32) == 'p' && (array[i + 2] | 32) == 'm' && (array[i + 3] | 32) == 'l') {
return true;
}
// UTF-16
if (array[i + 1] == '\000' && (array[i + 2] | 32) == 'p' &&
array[i + 3] == '\000' && (array[i + 4] | 32) == 'm' &&
array[i + 5] == '\000' && (array[i + 6] | 32) == 'l') {
return true;
}
}
}
return false;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Parses FpML from the specified source, extracting the trades.
*
* This parses the specified byte source which must be an XML document.
*
* Sometimes, the FpML document is embedded in a non-FpML wrapper.
* This method will intelligently find the FpML document at the root, within any children of
* the root, or within any grand-children of the root.
* The FpML root element is the one that contains both {@code } and {@code }.
*
* @param source the source of the FpML XML document
* @return the parsed trades
* @throws RuntimeException if a parse error occurred
*/
public List parseTrades(ByteSource source) {
XmlFile xmlFile = XmlFile.of(source, FpmlDocument.ID);
return parseTrades(xmlFile);
}
/**
* Parses FpML from the specified XML document, extracting the trades.
*
* This parses the specified XmlFile.
*
* Sometimes, the FpML document is embedded in a non-FpML wrapper.
* This method will intelligently find the FpML document at the root, within any children of
* the root, or within any grand-children of the root.
* The FpML root element is the one that contains both {@code } and {@code }.
*
* @param xmlFile the FpML XML document
* @return the parsed trades
* @throws RuntimeException if a parse error occurred
*/
public List parseTrades(XmlFile xmlFile) {
XmlElement root = findFpmlRoot(xmlFile.getRoot());
if (root == null) {
throw new FpmlParseException("Unable to find FpML root element");
}
return parseTrades(root, xmlFile.getReferences());
}
// intelligently finds the FpML root element
private static XmlElement findFpmlRoot(XmlElement root) {
XmlElement fpmlRoot = getFpmlRoot(root);
if (fpmlRoot != null) {
return fpmlRoot;
}
// try children of root element
for (XmlElement el : root.getChildren()) {
fpmlRoot = getFpmlRoot(el);
if (fpmlRoot != null) {
return fpmlRoot;
}
}
// try grandchildren of root element
for (XmlElement el1 : root.getChildren()) {
for (XmlElement el2 : el1.getChildren()) {
fpmlRoot = getFpmlRoot(el2);
if (fpmlRoot != null) {
return fpmlRoot;
}
}
}
return null;
}
// simple check to see if this is an FpML root
private static XmlElement getFpmlRoot(XmlElement el) {
if (el.getChildren("party").size() > 0) {
// party and trade are siblings (the common case)
if (el.getChildren("trade").size() > 0) {
return el;
}
// trade is within a child alongside party (the unusual case, within clearingStatus/clearingStatusItem)
for (XmlElement child : el.getChildren()) {
if (child.getChildren("trade").size() > 0) {
List fakeChildren = new ArrayList<>();
fakeChildren.addAll(el.getChildren("party"));
fakeChildren.addAll(child.getChildren("trade"));
XmlElement fakeRoot = XmlElement.ofChildren(el.getName(), el.getAttributes(), fakeChildren);
return fakeRoot;
}
}
// trade is within a grandchild alongside party (the unusual case, within clearingConfirmed/clearing/cleared)
for (XmlElement child : el.getChildren()) {
for (XmlElement grandchild : child.getChildren()) {
if (grandchild.getChildren("trade").size() > 0) {
List fakeChildren = new ArrayList<>();
fakeChildren.addAll(el.getChildren("party"));
fakeChildren.addAll(grandchild.getChildren("trade"));
XmlElement fakeRoot = XmlElement.ofChildren(el.getName(), el.getAttributes(), fakeChildren);
return fakeRoot;
}
}
}
}
return null;
}
//-------------------------------------------------------------------------
/**
* Parses the FpML document extracting the trades.
*
* This parses the specified FpML root element, using the map of references.
* The FpML specification uses references to link one part of the XML to another.
* For example, if one part of the XML has {@code }, the references
* map will contain an entry mapping "fooId" to the parsed element {@code }.
*
* @param fpmlRootEl the source of the FpML XML document
* @param references the map of id/href to referenced element
* @return the parsed trades
* @throws RuntimeException if a parse error occurred
*/
public List parseTrades(
XmlElement fpmlRootEl,
Map references) {
FpmlDocument document = new FpmlDocument(fpmlRootEl, references, ourPartySelector, tradeInfoParser, refData, strictValidation);
List tradeEls = document.getFpmlRoot().getChildren("trade");
ImmutableList.Builder builder = ImmutableList.builder();
for (XmlElement tradeEl : tradeEls) {
builder.add(parseTrade(document, tradeEl));
}
return builder.build();
}
// parses one trade element
private Trade parseTrade(FpmlDocument document, XmlElement tradeEl) {
// find which trade type it is by comparing children to known parsers
for (Entry entry : tradeParsers.entrySet()) {
Optional productOptEl = tradeEl.findChild(entry.getKey());
if (productOptEl.isPresent()) {
return entry.getValue().parseTrade(document, tradeEl);
}
}
// failed to find a known trade type
ImmutableSet childNames = tradeEl.getChildren().stream().map(XmlElement::getName).collect(toImmutableSet());
throw new FpmlParseException("Unknown product type '{value}'", childNames);
}
}