All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.co.real_logic.sbe.xml.Message Maven / Gradle / Ivy

Go to download

FIX/SBE - OSI layer 6 presentation for encoding and decoding application messages in binary format for low-latency applications.

The newest version!
/*
 * Copyright 2013-2024 Real Logic Limited.
 *
 * 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
 *
 * https://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 uk.co.real_logic.sbe.xml;

import org.agrona.collections.IntHashSet;
import org.agrona.collections.ObjectHashSet;
import uk.co.real_logic.sbe.ir.Token;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static javax.xml.xpath.XPathConstants.NODESET;
import static uk.co.real_logic.sbe.xml.XmlSchemaParser.*;

/**
 * An SBE message containing a list of {@link Field} objects and SBE message attributes.
 * 

* What is difference between {@link Message} and the Intermediate Representation (IR)? *

    *
  • IR is intentionally platform, schema, and language independent.
  • *
  • IR is abstract layout and encoding only.
  • *
  • IR is a flat representation without cycles or hierarchy.
  • *
  • Message is FIX/SBE XML Schema specific.
  • *
*/ public class Message { private static final String FIELD_OR_GROUP_OR_DATA_EXPR = "field|group|data"; private final int id; private final String name; private final String description; private final int sinceVersion; private final int deprecated; private final int blockLength; private final List fieldList; private final String semanticType; private final int computedBlockLength; private final Map typeByNameMap; /** * Construct a new message from XML Schema. * * @param messageNode from the XML Schema Parsing * @param typeByNameMap holding type information for message * @throws XPathExpressionException on invalid XPath */ public Message(final Node messageNode, final Map typeByNameMap) throws XPathExpressionException { id = Integer.parseInt(getAttributeValue(messageNode, "id")); // required name = getAttributeValue(messageNode, "name"); // required description = getAttributeValueOrNull(messageNode, "description"); // optional blockLength = Integer.parseInt(getAttributeValue(messageNode, "blockLength", "0")); // 0 means not set sinceVersion = Integer.parseInt(getAttributeValue(messageNode, "sinceVersion", "0")); deprecated = Integer.parseInt(getAttributeValue(messageNode, "deprecated", "0")); semanticType = getAttributeValueOrNull(messageNode, "semanticType"); // optional this.typeByNameMap = typeByNameMap; fieldList = parseMembers(messageNode); computeAndValidateOffsets(messageNode, fieldList, blockLength); computedBlockLength = computeMessageRootBlockLength(fieldList); validateBlockLength(messageNode, blockLength, computedBlockLength); } Message(final Message message, final List newFieldList) { id = message.id; name = message.name; description = message.description; blockLength = message.blockLength; sinceVersion = message.sinceVersion; deprecated = message.deprecated; semanticType = message.semanticType; typeByNameMap = message.typeByNameMap; fieldList = newFieldList; computedBlockLength = computeMessageRootBlockLength(newFieldList); } /** * Return the template schemaId of the message * * @return schemaId of the message */ public int id() { return id; } /** * Return the name of the message * * @return name of the message */ public String name() { return name; } /** * The description of the message (if set) or null * * @return description set by the message or null */ public String description() { return description; } /** * The semanticType of the message (if set) or null * * @return the semanticType of the message (if set) or null */ public String semanticType() { return semanticType; } /** * The version since this was added to the template. * * @return version since this was added to the template. */ public int sinceVersion() { return sinceVersion; } /** * Version in which message was deprecated. Only valid if greater than zero. * * @return version in which the message was deprecated. */ public int deprecated() { return deprecated; } /** * Return the list of fields in the message * * @return {@link java.util.List} of the Field objects in this Message */ public List fields() { return fieldList; } /** * Return the encodedLength of the {@link Message} in bytes including any padding. * * @return the encodedLength of the {@link Message} in bytes including any padding. */ public int blockLength() { return Math.max(blockLength, computedBlockLength); } private List parseMembers(final Node node) throws XPathExpressionException { final XPath xPath = XPathFactory.newInstance().newXPath(); final NodeList list = (NodeList)xPath.compile(FIELD_OR_GROUP_OR_DATA_EXPR).evaluate(node, NODESET); boolean groupEncountered = false, dataEncountered = false; final ObjectHashSet distinctNames = new ObjectHashSet<>(); final IntHashSet distinctIds = new IntHashSet(); final ArrayList fieldList = new ArrayList<>(); for (int i = 0, size = list.getLength(); i < size; i++) { final Field field; final String nodeName = list.item(i).getNodeName(); switch (nodeName) { case "group": if (dataEncountered) { handleError(node, "group node specified after data node"); } field = parseGroupField(list, i); groupEncountered = true; break; case "data": field = parseDataField(list, i); dataEncountered = true; break; case "field": if (groupEncountered || dataEncountered) { handleError(node, "field node specified after group or data node specified"); } field = parseField(list, i); break; default: throw new IllegalStateException("Unknown node name: " + nodeName); } if (!distinctIds.add(field.id())) { handleError(node, "duplicate id found: " + field.id()); } if (!distinctNames.add(field.name())) { handleError(node, "duplicate name found: " + field.name()); } fieldList.add(field); } return fieldList; } private Field parseGroupField(final NodeList nodeList, final int nodeIndex) throws XPathExpressionException { final Node node = nodeList.item(nodeIndex); final String dimensionTypeName = getAttributeValue(node, "dimensionType", "groupSizeEncoding"); Type dimensionType = typeByNameMap.get(dimensionTypeName); if (dimensionType == null) { handleError(node, "could not find dimensionType: " + dimensionTypeName); } else if (!(dimensionType instanceof CompositeType)) { handleError(node, "dimensionType should be a composite type: " + dimensionTypeName); dimensionType = null; } else { ((CompositeType)dimensionType).checkForWellFormedGroupSizeEncoding(node); } final Field field = new Field.Builder() .name(getAttributeValue(node, "name")) .description(getAttributeValueOrNull(node, "description")) .id(Integer.parseInt(getAttributeValue(node, "id"))) .blockLength(Integer.parseInt(getAttributeValue(node, "blockLength", "0"))) .sinceVersion(Integer.parseInt(getAttributeValue(node, "sinceVersion", "0"))) .deprecated(Integer.parseInt(getAttributeValue(node, "deprecated", "0"))) .dimensionType((CompositeType)dimensionType) .build(); XmlSchemaParser.checkForValidName(node, field.name()); field.groupFields(parseMembers(node)); // recursive call return field; } private Field parseField(final NodeList nodeList, final int nodeIndex) { final Node node = nodeList.item(nodeIndex); final String typeName = getAttributeValue(node, "type"); final Type fieldType = typeByNameMap.get(typeName); if (fieldType == null) { handleError(node, "could not find type: " + typeName); } final Field field = new Field.Builder() .name(getAttributeValue(node, "name")) .description(getAttributeValueOrNull(node, "description")) .id(Integer.parseInt(getAttributeValue(node, "id"))) .offset(Integer.parseInt(getAttributeValue(node, "offset", "0"))) .semanticType(getAttributeValueOrNull(node, "semanticType")) .presence(getPresence(node, fieldType)) .valueRef(getAttributeValueOrNull(node, "valueRef")) .sinceVersion(Integer.parseInt(getAttributeValue(node, "sinceVersion", "0"))) .deprecated(Integer.parseInt(getAttributeValue(node, "deprecated", "0"))) .epoch(getAttributeValueOrNull(node, "epoch")) .timeUnit(getAttributeValueOrNull(node, "timeUnit")) .type(fieldType) .build(); field.validate(node, typeByNameMap); return field; } private static Presence getPresence(final Node node, final Type fieldType) { final String presenceStr = getAttributeValueOrNull(node, "presence"); final Presence presence; if (null != presenceStr) { presence = Presence.get(presenceStr); } else if (null != fieldType) { presence = fieldType.presence(); } else { presence = Presence.REQUIRED; } return presence; } private Field parseDataField(final NodeList nodeList, final int nodeIndex) { final Node node = nodeList.item(nodeIndex); final String typeName = getAttributeValue(node, "type"); final Type fieldType = typeByNameMap.get(typeName); if (fieldType == null) { handleError(node, "could not find type: " + typeName); } else if (!(fieldType instanceof CompositeType)) { handleError(node, "data type is not composite type: " + typeName); } else { ((CompositeType)fieldType).checkForWellFormedVariableLengthDataEncoding(node); ((CompositeType)fieldType).makeDataFieldCompositeType(); } final Field field = new Field.Builder() .name(getAttributeValue(node, "name")) .description(getAttributeValueOrNull(node, "description")) .id(Integer.parseInt(getAttributeValue(node, "id"))) .offset(Integer.parseInt(getAttributeValue(node, "offset", "0"))) .semanticType(getAttributeValueOrNull(node, "semanticType")) .presence(Presence.get(getAttributeValue(node, "presence", "required"))) .sinceVersion(Integer.parseInt(getAttributeValue(node, "sinceVersion", "0"))) .deprecated(Integer.parseInt(getAttributeValue(node, "deprecated", "0"))) .epoch(getAttributeValueOrNull(node, "epoch")) .timeUnit(getAttributeValueOrNull(node, "timeUnit")) .type(fieldType) .variableLength(true) .build(); field.validate(node, typeByNameMap); return field; } /* * Compute and validate the offsets of the fields in the list and will set the fields computedOffset. * Will validate the blockLength of the fields encompassing <message> or <group> and recursively * descend into repeated groups. */ private static int computeAndValidateOffsets(final Node node, final List fields, final int blockLength) { boolean variableLengthBlock = false; int offset = 0; for (final Field field : fields) { if (0 != field.offset() && field.offset() < offset) { handleError(node, "Offset provides insufficient space at field: " + field.name()); } if (Token.VARIABLE_LENGTH != offset) { if (0 != field.offset()) { offset = field.offset(); } else if (null != field.dimensionType() && 0 != blockLength) { offset = blockLength; } else if (field.isVariableLength() && 0 != blockLength) { offset = blockLength; } } field.computedOffset(variableLengthBlock ? Token.VARIABLE_LENGTH : offset); if (null != field.groupFields()) { final int groupBlockLength = computeAndValidateOffsets(node, field.groupFields(), 0); validateBlockLength(node, field.blockLength(), groupBlockLength); field.computedBlockLength(Math.max(field.blockLength(), groupBlockLength)); variableLengthBlock = true; } else if (null != field.type() && Presence.CONSTANT != field.presence()) { final int size = field.type().encodedLength(); if (Token.VARIABLE_LENGTH == size) { variableLengthBlock = true; } else { field.computedBlockLength(size); } if (!variableLengthBlock) { offset += size; } } } return offset; } private static int computeMessageRootBlockLength(final List fields) { int blockLength = 0; for (final Field field : fields) { if (field.groupFields() != null) { return blockLength; } else if (field.type() != null) { final int fieldLength = field.type().encodedLength(); if (Token.VARIABLE_LENGTH == fieldLength) { return blockLength; } if (field.presence() == Presence.CONSTANT) { blockLength = field.computedOffset(); } else { blockLength = field.computedOffset() + fieldLength; } } } return blockLength; } private static void validateBlockLength( final Node node, final long specifiedBlockLength, final long computedBlockLength) { if (0 != specifiedBlockLength && computedBlockLength > specifiedBlockLength) { final String msg = "specified blockLength provides insufficient space " + computedBlockLength + " > " + specifiedBlockLength; handleError(node, msg); } } /** * {@inheritDoc} */ public String toString() { return "Message{" + "id=" + id + ", name='" + name + '\'' + ", description='" + description + '\'' + ", sinceVersion=" + sinceVersion + ", deprecated=" + deprecated + ", blockLength=" + blockLength + ", fieldList=" + fieldList + ", semanticType='" + semanticType + '\'' + ", computedBlockLength=" + computedBlockLength + ", typeByNameMap=" + typeByNameMap + '}'; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy