diff --git a/src/main/java/org/symphonyoss/symphony/messageml/MessageMLParser.java b/src/main/java/org/symphonyoss/symphony/messageml/MessageMLParser.java index b93e690d..5c32de45 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/MessageMLParser.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/MessageMLParser.java @@ -14,6 +14,7 @@ import freemarker.template.TemplateExceptionHandler; import org.apache.commons.io.input.ReaderInputStream; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.symphonyoss.symphony.messageml.bi.BiContext; import org.symphonyoss.symphony.messageml.bi.BiFields; @@ -39,6 +40,7 @@ import org.symphonyoss.symphony.messageml.elements.ExpandableCard; import org.symphonyoss.symphony.messageml.elements.ExpandableCardBody; import org.symphonyoss.symphony.messageml.elements.ExpandableCardHeader; +import org.symphonyoss.symphony.messageml.elements.Tag; import org.symphonyoss.symphony.messageml.elements.Form; import org.symphonyoss.symphony.messageml.elements.FormElement; import org.symphonyoss.symphony.messageml.elements.FormatEnum; @@ -83,6 +85,9 @@ import org.symphonyoss.symphony.messageml.util.IDataProvider; import org.symphonyoss.symphony.messageml.util.NoOpEntityResolver; import org.symphonyoss.symphony.messageml.util.NullErrorHandler; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentKind; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentResolution; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -102,6 +107,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -366,8 +372,8 @@ private MessageML parseMessageML(String messageML, String version) throws Invali MessageML result = new MessageML(messageFormat, version); result.buildAll(this, docElement); + result.enhanceFinancialTags(result, dataProvider); result.validate(); - return result; } @@ -629,6 +635,9 @@ public Element createElement(org.w3c.dom.Element element, Element parent) throws case Superscript.MESSAGEML_TAG: return new Superscript(parent); + case Tag.MESSAGEML_TAG: + return new Tag(parent, ++index); + default: throw new InvalidInputException("Invalid MessageML content at element \"" + tag + "\""); } diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java index 840c7614..899db069 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java @@ -436,6 +436,28 @@ public Integer countChildrenOfType(Class type) { return count; } + /** + * This method applies a breadth-first traversal of a tree of elements getting list of + * elements found which + * belong to the class type passed as input + */ + public List getChildrenOfType(Class type) { + List elements = new ArrayList<>(); + Stack stack = new Stack<>(); + Element current = this; + stack.push(current); + while (!stack.isEmpty()) { + current = stack.pop(); + if (current.getClass() == type) { + elements.add(current); + } + for (Element child : current.getChildren()) { + stack.push(child); + } + } + return elements; + } + /** * Return the EntityJSON representation of the node. */ diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/MessageML.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/MessageML.java index 75d0c286..10239a9f 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/MessageML.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/MessageML.java @@ -26,14 +26,24 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.commonmark.node.Document; import org.symphonyoss.symphony.messageml.MessageMLContext; import org.symphonyoss.symphony.messageml.MessageMLParser; import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; +import org.symphonyoss.symphony.messageml.util.IDataProvider; import org.symphonyoss.symphony.messageml.util.XmlPrintStream; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentKind; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentResolution; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.MarketSector; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; import org.w3c.dom.Node; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** @@ -182,4 +192,61 @@ private void validateTargetIdForUIActions() throws InvalidInputException { uiAction.setAttribute(TARGET_ID, dialog.getPresentationMlIdAttribute()); } } + + public void enhanceFinancialTags(MessageML result, IDataProvider dataProvider) + throws InvalidInputException { + + List elements = result.getChildrenOfType(Tag.class) + .stream() + .map(element -> Tag.class.cast(element)) + .collect(Collectors.toList()); + if (elements != null && !elements.isEmpty()) {processFinancialTags(elements, dataProvider);} + } + + private void processFinancialTags(List elements, IDataProvider dataProvider) + throws InvalidInputException { + List> instrumentResolutionMap = + IntStream.range(0, elements.size()) + .mapToObj(index -> buildInstrumentResolutionRequest(elements.get(index), index)) + .collect(Collectors.toList()); + // Build resolver api request + List criteria = + instrumentResolutionMap.stream().map(Pair::getLeft).collect(Collectors.toList()); + ResolutionResults results = dataProvider.getFinTagPresentation(criteria); + // update financial tag element data + instrumentResolutionMap.forEach(entry -> { + String resolutionId = entry.getLeft().getResolutionId(); + if (results != null && results.getInstruments() != null && results.getInstruments() + .containsKey(resolutionId)) { + entry.getRight() + .setInstrument( + results.getInstruments().get(resolutionId).getInstrument()); + } + } + ); + for (Tag element : elements) {element.validateFallBackTicker();} + } + + private Pair buildInstrumentResolutionRequest(Tag tag, + Integer order) { + InstrumentResolution resolution = new InstrumentResolution(); + resolution.setResolutionId(order.toString()); + resolution.setBbgCompTicker(tag.getTagAttributes().getBbgcompticker()); + resolution.setFigi(tag.getTagAttributes().getFigi()); + resolution.setFigiTicker(tag.getTagAttributes().getFigiTicker()); + resolution.setUniqueId(tag.getTagAttributes().getUniqueId()); + resolution.setIsin(tag.getTagAttributes().getIsin()); + resolution.setUsCode(tag.getTagAttributes().getUscode()); + resolution.setFullBbgCompTicker(tag.getTagAttributes().getFullBbgCompTicker()); + resolution.setLocalCode(tag.getTagAttributes().getLocalcode()); + resolution.setOperationalMic(tag.getTagAttributes().getOperationalMic()); + resolution.setInstrumentClass( + InstrumentKind.fromValue(tag.getTagAttributes().getInstrumentclass())); + resolution.setCountryCode(tag.getTagAttributes().getCountrycode()); + resolution.setReturnMainListing(tag.getTagAttributes().getReturnMainListing()); + resolution.setBbgMarketSector( + MarketSector.fromValue(tag.getTagAttributes().getBbgmarketsector())); + return new ImmutablePair<>(resolution, tag); + } + } diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Tag.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Tag.java new file mode 100644 index 00000000..f979e4b6 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Tag.java @@ -0,0 +1,244 @@ +/* + * Copyright 2016-2017 MessageML - Symphony LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.symphonyoss.symphony.messageml.elements; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.commonmark.node.Node; +import org.symphonyoss.symphony.messageml.MessageMLContext; +import org.symphonyoss.symphony.messageml.MessageMLParser; +import org.symphonyoss.symphony.messageml.bi.BiContext; +import org.symphonyoss.symphony.messageml.bi.BiFields; +import org.symphonyoss.symphony.messageml.bi.BiItem; +import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; +import org.symphonyoss.symphony.messageml.markdown.nodes.TagNode; +import org.symphonyoss.symphony.messageml.util.TagAttributes; +import org.symphonyoss.symphony.messageml.util.XmlPrintStream; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.Instrument; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentKind; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.MarketSector; + +import java.util.Collections; + +/** + * Class representing a convenience element for a financial tag . + */ +public class Tag extends Entity { + public static final String MESSAGEML_TAG = "tag"; + public static final String PREFIX = "$"; + public static final String ENTITY_TYPE = "org.symphonyoss.fin.security"; + private static final String ENTITY_SUBTYPE = "org.symphonyoss.fin.security.id.ticker"; + private static final String ENTITY_VERSION = "2.0"; + private static final String LEGACY_ENTITY_VERSION = "1.0"; + @Getter + private TagAttributes tagAttributes = new TagAttributes(); + @Setter + private Instrument instrument; + + + public Tag(Element parent, int entityIndex) { + this(parent, DEFAULT_PRESENTATIONML_TAG, entityIndex, FormatEnum.MESSAGEML); + } + + private Tag(Element parent, String presentationMlTag, Integer entityIndex, FormatEnum format) { + super(parent, MESSAGEML_TAG, presentationMlTag, format); + this.entityId = getEntityId(entityIndex); + } + + @Override + protected void buildAttribute(MessageMLParser parser, + org.w3c.dom.Node item) throws InvalidInputException { + switch (item.getNodeName()) { + case TagAttributes.ATTR_FULLBBGCOMPTICKER: + tagAttributes.setFullBbgCompTicker(getStringAttribute(item)); + break; + case TagAttributes.ATTR_UNIQUEID: + tagAttributes.setUniqueId(getStringAttribute(item)); + break; + case TagAttributes.ATTR_FIGI: + tagAttributes.setFigi(getStringAttribute(item)); + break; + case TagAttributes.ATTR_BBGCOMPTICKER: + tagAttributes.setBbgcompticker(getStringAttribute(item)); + break; + case TagAttributes.ATTR_FIGITICKER: + tagAttributes.setFigiTicker(getStringAttribute(item)); + break; + case TagAttributes.ATTR_USCODE: + tagAttributes.setUscode(getStringAttribute(item)); + break; + case TagAttributes.ATTR_ISIN: + tagAttributes.setIsin(getStringAttribute(item)); + break; + case TagAttributes.ATTR_LOCALCODE: + tagAttributes.setLocalcode(getStringAttribute(item)); + break; + case TagAttributes.ATTR_INSTRUMENTCLASS: + tagAttributes.setInstrumentclass(getStringAttribute(item)); + break; + case TagAttributes.ATTR_BBGMARKETSECTOR: + tagAttributes.setBbgmarketsector(getStringAttribute(item)); + break; + case TagAttributes.ATTR_RETURNMAINLISTING: + tagAttributes.setReturnMainListing(getStringAttribute(item)); + break; + case TagAttributes.ATTR_COUNTRYCODE: + tagAttributes.setCountrycode(getStringAttribute(item)); + break; + case TagAttributes.ATTR_OPERATIONALMIC: + tagAttributes.setOperationalMic(getStringAttribute(item)); + break; + case TagAttributes.ATTR_FALLBACKTICKER: + tagAttributes.setFallbackTicker(getStringAttribute(item)); + break; + default: + super.buildAttribute(parser, item); + } + } + + @Override + public void validate() throws InvalidInputException { + super.validate(); + if (this.tagAttributes.getInstrumentclass() != null + && InstrumentKind.fromValue(this.tagAttributes.getInstrumentclass()) == null) { + throw new InvalidInputException( + "the attribute \"instrument-class\" must be one of those values " + + InstrumentKind.toValues()); + } + if (this.tagAttributes.getBbgmarketsector() != null + && MarketSector.fromValue(this.tagAttributes.getBbgmarketsector()) == null) { + throw new InvalidInputException( + "the attribute \"bbgmarket-sector\" must be one of those values " + + MarketSector.toValues()); + } + } + + @Override + public void asPresentationML(XmlPrintStream out, MessageMLContext context) { + out.printElement(presentationMLTag, asText(), CLASS_ATTR, PRESENTATIONML_CLASS, + ENTITY_ID_ATTR, entityId); + } + + @Override + Node asMarkdown() throws InvalidInputException { + String text = + instrument == null ? tagAttributes.getFallbackTicker() : instrument.getRootBbgCompTicker(); + JsonNode data = + instrument != null ? MAPPER.convertValue(instrument, JsonNode.class) : null; + return new TagNode(PREFIX, text, data); + } + + @Override + public ObjectNode asEntityJson(ObjectNode parent) { + ObjectNode node = super.asEntityJson(parent); + if (instrument != null) { + enhanceInstrumentEntityJson(node); + } + return node; + } + + + private void enhanceInstrumentEntityJson(ObjectNode node) { + ArrayNode idArray = ArrayNode.class.cast(node.get(ID_FIELD)); + idArray.add( + buildNode("org.symphonyoss.fin.security.id.uniqueId", instrument.getUniqueId())); + idArray.add(buildNode("org.symphonyoss.fin.security.id.fullBbgTicker", + instrument.getFullBbgCompTicker())); + idArray.add(buildNode("org.symphonyoss.fin.security.bbgcompticker", + instrument.getBbgCompTicker())); + idArray.add(buildNode("org.symphonyoss.fin.security.id.isin", instrument.getIsin())); + idArray.add(buildNode("org.symphonyoss.fin.security.id.figi", instrument.getFigi())); + idArray.add(buildNode("org.symphonyoss.fin.security.id.figiTicker", + instrument.getFigiTicker())); + idArray.add( + buildNode("org.symphonyoss.fin.security.id.lei", instrument.getFigiTicker())); + idArray.add( + buildNode("org.symphonyoss.fin.security.id.localCode", instrument.getLocalCode())); + idArray.add(buildNode("org.symphonyoss.fin.security.id.operationalMic", + instrument.getOperationalMic())); + idArray.add(buildNode("org.symphonyoss.fin.security.countryCode", instrument.getCountryCode())); + idArray.add(buildNode("org.symphonyoss.fin.security.countryName", instrument.getCountryName())); + idArray.add( + buildNode("org.symphonyoss.fin.security.exchangeName", instrument.getExchangeName())); + idArray.add(buildNode("org.symphonyoss.fin.security.displayName", instrument.getDisplayName())); + idArray.add(buildNode("org.symphonyoss.fin.security.currency", instrument.getCurrency())); + idArray.add(buildNode("org.symphonyoss.fin.security.instrumentTypeCode", + instrument.getInstrumentTypeCode())); + idArray.add(buildNode("org.symphonyoss.fin.security.instrumentTypeName", + instrument.getInstrumentTypeName())); + } + + private ObjectNode buildNode(String type, String value) { + ObjectNode node = new ObjectNode(JsonNodeFactory.instance); + node.put(TYPE_FIELD, type); + node.put(VALUE_FIELD, value); + return node; + } + + @Override + protected String getEntityIdPrefix() { + return MESSAGEML_TAG; + } + + @Override + protected String getEntityValue() { + return instrument == null ? tagAttributes.getFallbackTicker() + : instrument.getRootBbgCompTicker(); + } + + @Override + protected String getEntitySubType() { + return ENTITY_SUBTYPE; + } + + @Override + protected String getEntityVersion() { + return instrument == null ? LEGACY_ENTITY_VERSION : ENTITY_VERSION; + } + + @Override + protected String getEntityType() { + return ENTITY_TYPE; + } + + @Override + public String asText() { + String text = instrument == null ? tagAttributes.getFallbackTicker() + : instrument.getRootBbgCompTicker(); + return PREFIX + text; + } + + public void validateFallBackTicker() throws InvalidInputException { + if (instrument == null && StringUtils.isBlank(tagAttributes.getFallbackTicker())) { + throw new InvalidInputException( + "No instrument found , \"fallback-ticker\" attribute is required"); + } + } + + @Override + public void updateBiContext(BiContext context) { + super.updateBiContext(context); + context.updateItemCount(BiFields.HASHTAGS.getValue()); + context.addItem(new BiItem(BiFields.ENTITY.getValue(), + Collections.singletonMap(BiFields.ENTITY_TYPE.getValue(), this.getEntityType()))); + } +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/markdown/MarkdownRenderer.java b/src/main/java/org/symphonyoss/symphony/messageml/markdown/MarkdownRenderer.java index a0d1df26..366188b0 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/markdown/MarkdownRenderer.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/markdown/MarkdownRenderer.java @@ -31,6 +31,7 @@ import org.symphonyoss.symphony.messageml.markdown.nodes.TableCellNode; import org.symphonyoss.symphony.messageml.markdown.nodes.TableNode; import org.symphonyoss.symphony.messageml.markdown.nodes.TableRowNode; +import org.symphonyoss.symphony.messageml.markdown.nodes.TagNode; import org.symphonyoss.symphony.messageml.markdown.nodes.form.ButtonNode; import org.symphonyoss.symphony.messageml.markdown.nodes.form.FormElementNode; import org.symphonyoss.symphony.messageml.markdown.nodes.form.FormNode; @@ -62,6 +63,7 @@ public class MarkdownRenderer extends AbstractVisitor { private static final String INDEX_START = "indexStart"; private static final String INDEX_END = "indexEnd"; private static final String TYPE = "type"; + private static final String DATA = "data"; private static final String SCREEN_NAME = "screenName"; private static final String PRETTY_NAME = "prettyName"; private static final String USER_TYPE = "userType"; @@ -225,6 +227,8 @@ public void visit(CustomNode node) { visit((EmojiNode) node); } else if (node instanceof MentionNode) { visit((MentionNode) node); + } else if (node instanceof TagNode) { + visit(TagNode.class.cast(node)); } } @@ -342,7 +346,6 @@ private void visit(MentionNode mention) { writer.write(text); } - private void visit(TableNode table) { writer.write(table.getOpeningDelimiter()); visitChildren(table); @@ -372,6 +375,21 @@ private void visit(PreformattedNode pre) { this.removeNewlines = true; } + private void visit(TagNode tag) { + String text = tag.getPrefix() + tag.getText(); + ObjectNode node = new ObjectNode(JsonNodeFactory.instance); + node.put(ID, text); + node.put(TEXT, text); + node.put(INDEX_START, writer.length()); + node.put(INDEX_END, writer.length() + text.length()); + node.put(TYPE, "KEYWORD"); + if (tag.getData() != null) { + node.set(DATA, tag.getData()); + } + putJsonObject(HASHTAGS, node); + writer.write(text); + } + private void visitDelimited(Delimited delimited) { writer.write(delimited.getOpeningDelimiter()); visitChildren((Node) delimited); diff --git a/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableCellNode.java b/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableCellNode.java index e2fa1c10..3cfe7cb3 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableCellNode.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableCellNode.java @@ -30,4 +30,4 @@ public class TableCellNode extends CustomBlock { public String getDelimiter() { return DELIMITER; } -} +} \ No newline at end of file diff --git a/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableNode.java b/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableNode.java index 63b6938d..db845e00 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableNode.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TableNode.java @@ -39,4 +39,4 @@ public String getClosingDelimiter() { return "\n" + DELIMITER + "\n"; } -} +} \ No newline at end of file diff --git a/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TagNode.java b/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TagNode.java new file mode 100644 index 00000000..10705df9 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/markdown/nodes/TagNode.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016-2017 MessageML - Symphony LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.symphonyoss.symphony.messageml.markdown.nodes; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.commonmark.node.CustomNode; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.Instrument; + +/** + * Class representing a Markdown node for financial tags. + * + */ +public class TagNode extends CustomNode { + + @Getter + private String prefix; + @Getter + private String text; + @Getter + private JsonNode data; + + public TagNode(String prefix, String text, JsonNode data) { + this.prefix = prefix; + this.text = text; + this.data = data; + } + +} + diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/IDataProvider.java b/src/main/java/org/symphonyoss/symphony/messageml/util/IDataProvider.java index 4bc0220d..275a0d70 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/util/IDataProvider.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/IDataProvider.java @@ -18,8 +18,11 @@ import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; import org.symphonyoss.symphony.messageml.exceptions.ProcessingException; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentResolution; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; import java.net.URI; +import java.util.List; /** * Used during message parsing to provide external data. @@ -50,4 +53,14 @@ public interface IDataProvider { * @throws ProcessingException thrown on a malformed URI or a backend error */ void validateURI(URI uri) throws InvalidInputException, ProcessingException; + + /** + * Retrieve financial tag information based on list of instrument resolution list + * + * @param criteria List of instrument resolution criteria + * @return Resolution results + * @throws InvalidInputException + */ + ResolutionResults getFinTagPresentation(List criteria) + throws InvalidInputException; } diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/NoOpDataProvider.java b/src/main/java/org/symphonyoss/symphony/messageml/util/NoOpDataProvider.java index b746c8ad..5da2cb75 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/util/NoOpDataProvider.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/NoOpDataProvider.java @@ -2,8 +2,11 @@ import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; import org.symphonyoss.symphony.messageml.exceptions.ProcessingException; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentResolution; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; import java.net.URI; +import java.util.List; /** * A utility {@link IDataProvider} which populates {@link IUserPresentation} with the input (either user email or user ID) @@ -66,4 +69,10 @@ public IUserPresentation getUserPresentation(Long uid) throws InvalidInputExcept public void validateURI(URI uri) throws InvalidInputException, ProcessingException { // no-op } + + @Override + public ResolutionResults getFinTagPresentation(List uid) + throws InvalidInputException { + return null; + } } diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/NullDataProvider.java b/src/main/java/org/symphonyoss/symphony/messageml/util/NullDataProvider.java index dbb77756..a076ab7f 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/util/NullDataProvider.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/NullDataProvider.java @@ -2,8 +2,11 @@ import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; import org.symphonyoss.symphony.messageml.exceptions.ProcessingException; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentResolution; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; import java.net.URI; +import java.util.List; /** * A utility {@link IDataProvider} which populates {@link IUserPresentation} with empty strings and a user ID of 0 for every input @@ -47,4 +50,10 @@ public IUserPresentation getUserPresentation(Long uid) throws InvalidInputExcept public void validateURI(URI uri) throws InvalidInputException, ProcessingException { // no-op } + + @Override + public ResolutionResults getFinTagPresentation(List criteria) + throws InvalidInputException { + return null; + } } diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/TagAttributes.java b/src/main/java/org/symphonyoss/symphony/messageml/util/TagAttributes.java new file mode 100644 index 00000000..dce653f2 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/TagAttributes.java @@ -0,0 +1,42 @@ +package org.symphonyoss.symphony.messageml.util; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TagAttributes { + public static final String ATTR_FULLBBGCOMPTICKER = "fullbbgcompticker"; + public static final String ATTR_UNIQUEID = "unique-id"; + public static final String ATTR_FIGI = "figi"; + public static final String ATTR_BBGCOMPTICKER = "bbgcompticker"; + public static final String ATTR_FIGITICKER = "figi-ticker"; + public static final String ATTR_USCODE = "us-code"; + public static final String ATTR_ISIN = "isin"; + public static final String ATTR_LOCALCODE = "local-code"; + public static final String ATTR_INSTRUMENTCLASS = "instrument-class"; + public static final String ATTR_BBGMARKETSECTOR = "bbgmarket-sector"; + public static final String ATTR_RETURNMAINLISTING = "return-main-listing"; + public static final String ATTR_COUNTRYCODE = "country-code"; + public static final String ATTR_OPERATIONALMIC = "operational-mic"; + public static final String ATTR_FALLBACKTICKER = "fallback-ticker"; + + + // Identifiers + private String fullBbgCompTicker; + private String figi; + private String bbgcompticker; + private String figiTicker; + private String uscode; + private String isin; + private String localcode; + private String uniqueId; + // Filters + private String instrumentclass; + private String bbgmarketsector; + private String returnMainListing; + private String countrycode; + private String operationalMic; + // Other + private String fallbackTicker; +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/Instrument.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/Instrument.java new file mode 100644 index 00000000..a4410001 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/Instrument.java @@ -0,0 +1,72 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Instrument { + @JsonProperty("uniqueId") + private String uniqueId; + @JsonProperty("rootBbgCompTicker") + private String rootBbgCompTicker; + @JsonProperty("fullBbgCompTicker") + private String fullBbgCompTicker; + @JsonProperty("bbgCompTicker") + private String bbgCompTicker; + @JsonProperty("figi") + private String figi; + @JsonProperty("figiTicker") + private String figiTicker; + @JsonProperty("localCode") + private String localCode; + @JsonProperty("instrumentTypeCode") + private String instrumentTypeCode; + @JsonProperty("instrumentTypeName") + private String instrumentTypeName; + @JsonProperty("displayName") + private String displayName; + @JsonProperty("currency") + private String currency; + @JsonProperty("kind") + private InstrumentKind kind; + @JsonProperty("providerId") + private ProviderId providerId; + @JsonProperty("isin") + private String isin; + @JsonProperty("ric") + private String ric; + @JsonProperty("wkn") + private String wkn; + @JsonProperty("ediInstrumentId") + private String ediInstrumentId; + @JsonProperty("bbgMarketSector") + private MarketSector bbgMarketSector; + @JsonProperty("countryCode") + private String countryCode; + @JsonProperty("mainInstrument") + private Boolean mainInstrument; + @JsonProperty("bbgCompId") + private String bbgCompId; + @JsonProperty("usCode") + private String usCode; + @JsonProperty("sedol") + private String sedol; + @JsonProperty("cfi") + private String cfi; + @JsonProperty("lei") + private String lei; + @JsonProperty("countryName") + private String countryName; + @JsonProperty("exchangeName") + private String exchangeName; + @JsonProperty("ediExchangeCode") + private String ediExchangeCode; + @JsonProperty("primaryExchange") + private Boolean primaryExchange; + @JsonProperty("operationalMic") + private String operationalMic; +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/InstrumentKind.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/InstrumentKind.java new file mode 100644 index 00000000..e5288348 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/InstrumentKind.java @@ -0,0 +1,40 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum InstrumentKind { + EQUITY("equity"), + INDEX("index"), + FXCROSS("fxcross"); + @Getter + private String value; + + InstrumentKind(String value) { + this.value = value; + } + + @JsonCreator + public static InstrumentKind fromValue(String text) { + for (InstrumentKind b : InstrumentKind.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + + public static List toValues() { + return Arrays.asList(InstrumentKind.values()) + .stream() + .map(InstrumentKind::getValue) + .collect(Collectors.toList()); + } + + + +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/InstrumentResolution.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/InstrumentResolution.java new file mode 100644 index 00000000..056879bf --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/InstrumentResolution.java @@ -0,0 +1,24 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class InstrumentResolution { + private String resolutionId; + private String bbgCompTicker; + private String figi; + private String figiTicker; + private String uniqueId = null; + private String isin; + private String usCode; + private String fullBbgCompTicker; + private String localCode; + private String operationalMic; + private InstrumentKind instrumentClass; + private String countryCode; + private String returnMainListing; + private MarketSector bbgMarketSector; + +} + diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/MarketSector.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/MarketSector.java new file mode 100644 index 00000000..75d26b05 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/MarketSector.java @@ -0,0 +1,46 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum MarketSector { + EQUITY("Equity"), + COMDTY("Comdty"), + CORP("Corp"), + CURNCY("Curncy"), + GOVT("Govt"), + INDEX("Index"), + MMKT("Mmkt"), + MTGE("Mtge"), + MUNI("Muni"), + PFD("Pfd"); + @Getter + private String value; + + MarketSector(String value) { + this.value = value; + } + + @JsonCreator + public static MarketSector fromValue(String text) { + for (MarketSector b : MarketSector.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + + public static List toValues() { + return Arrays.asList(MarketSector.values()) + .stream() + .map(MarketSector::getValue) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ProviderId.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ProviderId.java new file mode 100644 index 00000000..52b07d61 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ProviderId.java @@ -0,0 +1,25 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + + + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum ProviderId { + EDI("edi"), + MANUAL("manual"); + private String value; + + ProviderId(String value) { + this.value = value; + } + + @JsonCreator + public static ProviderId fromValue(String text) { + for (ProviderId b : ProviderId.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ResolutionResult.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ResolutionResult.java new file mode 100644 index 00000000..d9f3391b --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ResolutionResult.java @@ -0,0 +1,11 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + + + +import lombok.Data; + +@Data +public class ResolutionResult { + private Instrument instrument; + private Integer returnCode; +} diff --git a/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ResolutionResults.java b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ResolutionResults.java new file mode 100644 index 00000000..c4789789 --- /dev/null +++ b/src/main/java/org/symphonyoss/symphony/messageml/util/instrument/resolver/ResolutionResults.java @@ -0,0 +1,14 @@ +package org.symphonyoss.symphony.messageml.util.instrument.resolver; + +import lombok.Data; + +import java.util.Map; + + + +@Data +public class ResolutionResults { + private Map instruments; + +} + diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/TagTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/TagTest.java new file mode 100644 index 00000000..786caf4d --- /dev/null +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/TagTest.java @@ -0,0 +1,146 @@ +package org.symphonyoss.symphony.messageml.elements; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.symphonyoss.symphony.messageml.bi.BiFields; +import org.symphonyoss.symphony.messageml.bi.BiItem; +import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; +import org.symphonyoss.symphony.messageml.exceptions.ProcessingException; +import org.symphonyoss.symphony.messageml.util.TestDataProvider; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class TagTest extends ElementTest { + + @Test + public void testTagWithAllValidAttributes() throws Exception { + ResolutionResults results = retrieveJsonPayload("finref_response"); + TestDataProvider.class.cast(dataProvider).setResolutionResults(results); + String input = + ""; + String expectedMarkdown = "$000930"; + String expectedPresentationML = + "
$000930
"; + String expectedJson = + "{\"tag1\":{\"type\":\"org.symphonyoss.fin.security\",\"version\":\"2.0\"," + + "\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\"," + + "\"value\":\"000930\"},{\"type\":\"org.symphonyoss.fin.security.id.uniqueId\"," + + "\"value\":\"831bb1ae-7ccc-4d48-a5f3-868e197db1ba\"},{\"type\":\"org.symphonyoss" + + ".fin.security.id.fullBbgTicker\",\"value\":\"000930 CH Equity\"},{\"type\":\"org" + + ".symphonyoss.fin.security.bbgcompticker\",\"value\":\"000930 CH\"},{\"type\":\"org" + + ".symphonyoss.fin.security.id.isin\",\"value\":\"CNE000000ZR7\"},{\"type\":\"org" + + ".symphonyoss.fin.security.id.figi\",\"value\":\"BBG000DYGW93\"},{\"type\":\"org" + + ".symphonyoss.fin.security.id.figiTicker\",\"value\":\"000930 CS\"},{\"type\":\"org" + + ".symphonyoss.fin.security.id.lei\",\"value\":\"000930 CS\"},{\"type\":\"org" + + ".symphonyoss.fin.security.id.localCode\",\"value\":\"000930\"},{\"type\":\"org" + + ".symphonyoss.fin.security.id.operationalMic\",\"value\":\"XSHE\"},{\"type\":\"org" + + ".symphonyoss.fin.security.countryCode\",\"value\":\"CN\"},{\"type\":\"org" + + ".symphonyoss.fin.security.countryName\",\"value\":\"China\"},{\"type\":\"org" + + ".symphonyoss.fin.security.exchangeName\",\"value\":\"Shenzhen Stock Exchange\"}," + + "{\"type\":\"org.symphonyoss.fin.security.displayName\",\"value\":\"Cofco " + + "Biotechnology Co Ltd\"},{\"type\":\"org.symphonyoss.fin.security.currency\"," + + "\"value\":\"CNY\"},{\"type\":\"org.symphonyoss.fin.security.instrumentTypeCode\"," + + "\"value\":\"EQS\"},{\"type\":\"org.symphonyoss.fin.security.instrumentTypeName\"," + + "\"value\":\"Equity Shares\"}]}}"; + context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); + Element messageML = context.getMessageML(); + JsonNode node = context.getEntities(); + verifyTag(messageML, results, expectedPresentationML, expectedJson, expectedMarkdown); + } + + @Test + public void testTagWithInstrumentNotFoundAndFallbackTicker() throws Exception { + ResolutionResults results = retrieveJsonPayload("finref_with_instrument_not_found_response"); + TestDataProvider.class.cast(dataProvider).setResolutionResults(results); + String input = + ""; + String expectedMarkdown = "$fallback"; + String expectedPresentationML = + "
$fallback
"; + String expectedJson = + "{\"tag1\":{\"type\":\"org.symphonyoss.fin.security\",\"version\":\"1.0\"," + + "\"id\":[{\"type\":\"org.symphonyoss.fin.security.id.ticker\"," + + "\"value\":\"fallback\"}]}}"; + context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); + Element messageML = context.getMessageML(); + JsonNode node = context.getEntities(); + verifyTag(messageML, results, expectedPresentationML, expectedJson, expectedMarkdown); + } + + + @Test + public void testTagWithInstrumentNotFoundAndNoFallbackTicker() throws Exception { + ResolutionResults results = retrieveJsonPayload("finref_with_instrument_not_found_response"); + TestDataProvider.class.cast(dataProvider).setResolutionResults(results); + String input = ""; + InvalidInputException exception = assertThrows(InvalidInputException.class, () -> + context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION) + ); + assertEquals("Exception Message", + "No instrument found , \"fallback-ticker\" attribute is required", exception.getMessage()); + } + + @Test + public void testBiContextTagEntity() throws InvalidInputException, IOException, + ProcessingException { + ResolutionResults results = retrieveJsonPayload("finref_response"); + TestDataProvider.class.cast(dataProvider).setResolutionResults(results); + String input = ""; + context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); + List items = context.getBiContext().getItems(); + + Map tagExpectedAttributes = + Collections.singletonMap(BiFields.COUNT.getValue(), 1); + Map entityExpectedAttributes = + Collections.singletonMap(BiFields.ENTITY_TYPE.getValue(), "org.symphonyoss.fin.security"); + + BiItem mentionBiItemExpected = + new BiItem(BiFields.HASHTAGS.getValue(), tagExpectedAttributes); + BiItem entityBiItemExpected = new BiItem(BiFields.ENTITY.getValue(), entityExpectedAttributes); + + assertEquals(3, items.size()); + assertSameBiItem(mentionBiItemExpected, items.get(0)); + assertSameBiItem(entityBiItemExpected, items.get(1)); + assertMessageLengthBiItem(items.get(2), input.length()); + } + + private ResolutionResults retrieveJsonPayload(String fileName) throws IOException { + ClassLoader classLoader = getClass().getClassLoader(); + InputStream stream = classLoader.getResourceAsStream("payloads/" + fileName + ".json"); + String response = IOUtils.toString(stream, StandardCharsets.UTF_8); + return MAPPER.readValue(response, ResolutionResults.class); + } + + private void verifyTag(Element messageML, ResolutionResults user, String expectedPresentationML, + String expectedJson, String expectedMarkdown) + throws Exception { + assertEquals("Element children", 1, messageML.getChildren().size()); + Element tag = messageML.getChildren().get(0); + assertEquals("Element class", Tag.class, tag.getClass()); + assertEquals("Element tag name", "tag", tag.getMessageMLTag()); + assertEquals("PresentationML", expectedPresentationML, context.getPresentationML()); + assertEquals("Markdown", expectedMarkdown, context.getMarkdown()); + assertEquals("EntityJSON", expectedJson, MAPPER.writeValueAsString(context.getEntityJson())); + assertEquals("Legacy entities", 1, context.getEntities().size()); + } +} diff --git a/src/test/java/org/symphonyoss/symphony/messageml/util/TestDataProvider.java b/src/test/java/org/symphonyoss/symphony/messageml/util/TestDataProvider.java index 769a1db1..0b914a1d 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/util/TestDataProvider.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/util/TestDataProvider.java @@ -16,10 +16,14 @@ package org.symphonyoss.symphony.messageml.util; +import org.apache.commons.lang3.StringUtils; import org.symphonyoss.symphony.messageml.exceptions.InvalidInputException; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.InstrumentResolution; +import org.symphonyoss.symphony.messageml.util.instrument.resolver.ResolutionResults; import java.net.URI; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -30,6 +34,8 @@ public class TestDataProvider implements IDataProvider { private static final Set STANDARD_URI_SCHEMES = new HashSet<>(); private UserPresentation user; + private ResolutionResults results; + public TestDataProvider() { STANDARD_URI_SCHEMES.add("http"); @@ -62,6 +68,12 @@ public void validateURI(URI uri) throws InvalidInputException { } } + @Override + public ResolutionResults getFinTagPresentation(List criteria) + throws InvalidInputException { + return this.results; + } + public void setUserPresentation(UserPresentation user) { this.user = user; } @@ -69,4 +81,9 @@ public void setUserPresentation(UserPresentation user) { public void setUserPresentation(long id, String screenName, String prettyName, String email) { this.user = new UserPresentation(id, screenName, prettyName, email); } + + public void setResolutionResults(ResolutionResults results) { + this.results = results; + } + } diff --git a/src/test/resources/payloads/finref_response.json b/src/test/resources/payloads/finref_response.json new file mode 100644 index 00000000..f75ad11c --- /dev/null +++ b/src/test/resources/payloads/finref_response.json @@ -0,0 +1,178 @@ +{ + "instruments": { + "0": { + "instrument": { + "uniqueId": "831bb1ae-7ccc-4d48-a5f3-868e197db1ba", + "kind": "equity", + "providerId": "edi", + "isin": "CNE000000ZR7", + "fullBbgCompTicker": "000930 CH Equity", + "bbgCompId": "BBG000DYGVG7", + "rootBbgCompTicker": "000930", + "bbgCompTicker": "000930 CH", + "figi": "BBG000DYGW93", + "figiTicker": "000930 CS", + "usCode": null, + "sedol": null, + "cfi": "ESVUFR", + "ric": null, + "lei": "300300CSR7RNP1P1CP32", + "wkn": null, + "ediInstrumentId": "112066", + "displayName": "Cofco Biotechnology Co Ltd", + "bbgMarketSector": "Equity", + "instrumentTypeCode": "EQS", + "instrumentTypeName": "Equity Shares", + "countryCode": "CN", + "countryName": "China", + "exchangeName": "Shenzhen Stock Exchange", + "ediExchangeCode": "CNSSE", + "primaryExchange": true, + "operationalMic": "XSHE", + "currency": "CNY", + "localCode": "000930" + }, + "returnCode": 0 + }, + "1": { + "instrument": { + "uniqueId": "02d73075-ed52-47c8-8892-10d6741cad23", + "kind": "equity", + "providerId": "edi", + "isin": "HK0226001151", + "fullBbgCompTicker": "226 HK Equity", + "bbgCompId": "BBG000BDYRG4", + "rootBbgCompTicker": "226", + "bbgCompTicker": "226 HK", + "figi": "BBG000BDYRQ3", + "figiTicker": "226 HK", + "usCode": null, + "sedol": null, + "cfi": "ESVUFR", + "ric": null, + "lei": "529900266P1TX5IIS107", + "wkn": null, + "ediInstrumentId": "13633", + "displayName": "Lippo Ltd.", + "bbgMarketSector": "Equity", + "instrumentTypeCode": "EQS", + "instrumentTypeName": "Equity Shares", + "countryCode": "HK", + "countryName": "Hong Kong", + "exchangeName": "Hong Kong Stock Exchange", + "ediExchangeCode": "HKSEHK", + "primaryExchange": true, + "operationalMic": "XHKG", + "currency": "HKD", + "localCode": "00226" + }, + "returnCode": 0 + }, + "2": { + "instrument": { + "uniqueId": "02d73075-ed52-47c8-8892-10d6741cad23", + "kind": "equity", + "providerId": "edi", + "isin": "HK0226001151", + "fullBbgCompTicker": "226 HK Equity", + "bbgCompId": "BBG000BDYRG4", + "rootBbgCompTicker": "226", + "bbgCompTicker": "226 HK", + "figi": "BBG000BDYRQ3", + "figiTicker": "226 HK", + "usCode": null, + "sedol": null, + "cfi": "ESVUFR", + "ric": null, + "lei": "529900266P1TX5IIS107", + "wkn": null, + "ediInstrumentId": "13633", + "displayName": "Lippo Ltd.", + "bbgMarketSector": "Equity", + "instrumentTypeCode": "EQS", + "instrumentTypeName": "Equity Shares", + "countryCode": "HK", + "countryName": "Hong Kong", + "exchangeName": "Hong Kong Stock Exchange", + "ediExchangeCode": "HKSEHK", + "primaryExchange": true, + "operationalMic": "XHKG", + "currency": "HKD", + "localCode": "00226" + }, + "returnCode": 0 + }, + "3": { + "instrument": null, + "returnCode": 1 + }, + "4": { + "instrument": { + "uniqueId": "02d73075-ed52-47c8-8892-10d6741cad23", + "kind": "equity", + "providerId": "edi", + "isin": "HK0226001151", + "fullBbgCompTicker": "226 HK Equity", + "bbgCompId": "BBG000BDYRG4", + "rootBbgCompTicker": "226", + "bbgCompTicker": "226 HK", + "figi": "BBG000BDYRQ3", + "figiTicker": "226 HK", + "usCode": null, + "sedol": null, + "cfi": "ESVUFR", + "ric": null, + "lei": "529900266P1TX5IIS107", + "wkn": null, + "ediInstrumentId": "13633", + "displayName": "Lippo Ltd.", + "bbgMarketSector": "Equity", + "instrumentTypeCode": "EQS", + "instrumentTypeName": "Equity Shares", + "countryCode": "HK", + "countryName": "Hong Kong", + "exchangeName": "Hong Kong Stock Exchange", + "ediExchangeCode": "HKSEHK", + "primaryExchange": true, + "operationalMic": "XHKG", + "currency": "HKD", + "localCode": "00226" + }, + "returnCode": 0 + }, + "5": { + "instrument": { + "uniqueId": "060cc588-32a2-4d66-9c1c-9b2aded6bfb8", + "kind": "equity", + "providerId": "edi", + "isin": "OM0000002168", + "fullBbgCompTicker": "AACT OM Equity", + "bbgCompId": "BBG000BLVK38", + "rootBbgCompTicker": "AACT", + "bbgCompTicker": "AACT OM", + "figi": "BBG000BLVKF5", + "figiTicker": "AACT OM", + "usCode": null, + "sedol": null, + "cfi": "ESVTFR", + "ric": null, + "lei": null, + "wkn": null, + "ediInstrumentId": "14134", + "displayName": "Al Anwar Ceramic Tiles", + "bbgMarketSector": "Equity", + "instrumentTypeCode": "EQS", + "instrumentTypeName": "Equity Shares", + "countryCode": "OM", + "countryName": "Oman", + "exchangeName": "Muscat Securities Market", + "ediExchangeCode": "OMMSM", + "primaryExchange": true, + "operationalMic": "XMUS", + "currency": "OMR", + "localCode": "AACT" + }, + "returnCode": 0 + } + } +} \ No newline at end of file diff --git a/src/test/resources/payloads/finref_with_instrument_not_found_response.json b/src/test/resources/payloads/finref_with_instrument_not_found_response.json new file mode 100644 index 00000000..8b31a554 --- /dev/null +++ b/src/test/resources/payloads/finref_with_instrument_not_found_response.json @@ -0,0 +1,8 @@ +{ + "instruments": { + "0": { + "instrument": null, + "returnCode": 1 + } + } +} \ No newline at end of file