uk.co.real_logic.sbe.xml.MessageSchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sbe-all Show documentation
Show all versions of sbe-all Show documentation
FIX/SBE - OSI layer 6 presentation for encoding and decoding application messages in binary format for low-latency applications.
/*
* 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.Verify;
import org.w3c.dom.Node;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import static uk.co.real_logic.sbe.xml.XmlSchemaParser.getAttributeValue;
import static uk.co.real_logic.sbe.xml.XmlSchemaParser.getAttributeValueOrNull;
import static uk.co.real_logic.sbe.xml.XmlSchemaParser.getByteOrder;
/**
* Message schema aggregate for schema attributes, messageHeader, and reference for multiple {@link Message} objects.
*/
public class MessageSchema
{
/**
* Default message header type name for the SBE spec.
*/
public static final String HEADER_TYPE_DEFAULT = "messageHeader";
private final String packageName; // package (required)
private final String description; // description (optional)
private final int id; // identifier for the schema (required)
private final int version; // version (optional - default is 0)
private final String semanticVersion; // semanticVersion (optional)
private final ByteOrder byteOrder; // byteOrder (optional - default is littleEndian)
private final String headerType; // headerType (optional - default to messageHeader)
private final Map typeByNameMap;
private final Map messageByIdMap;
MessageSchema(final Node schemaNode, final Map typeByNameMap, final Map messageByIdMap)
{
this.packageName = getAttributeValue(schemaNode, "package");
this.description = getAttributeValueOrNull(schemaNode, "description");
this.id = Integer.parseInt(getAttributeValue(schemaNode, "id"));
this.version = Integer.parseInt(getAttributeValue(schemaNode, "version", "0"));
this.semanticVersion = getAttributeValueOrNull(schemaNode, "semanticVersion");
this.byteOrder = getByteOrder(getAttributeValue(schemaNode, "byteOrder", "littleEndian"));
this.typeByNameMap = typeByNameMap;
this.messageByIdMap = messageByIdMap;
final String configuredHeaderType = getAttributeValueOrNull(schemaNode, "headerType");
headerType = null == configuredHeaderType ? HEADER_TYPE_DEFAULT : configuredHeaderType;
Verify.present(typeByNameMap, this.headerType, "Message header");
final Node messageHeaderNode = findNode(schemaNode, "types/composite[@name='" + this.headerType + "']");
((CompositeType)typeByNameMap.get(this.headerType)).checkForWellFormedMessageHeader(messageHeaderNode);
}
MessageSchema(
final String packageName,
final String description,
final int id,
final int version,
final String semanticVersion,
final ByteOrder byteOrder,
final String headerType,
final Map typeByNameMap,
final Map messageByIdMap)
{
this.packageName = packageName;
this.description = description;
this.id = id;
this.version = version;
this.semanticVersion = semanticVersion;
this.byteOrder = byteOrder;
this.headerType = headerType;
this.typeByNameMap = typeByNameMap;
this.messageByIdMap = messageByIdMap;
}
/**
* The Schema headerType for message headers. This should be a {@link CompositeType}.
*
* @return the Schema headerType for message headers
*/
public CompositeType messageHeader()
{
return (CompositeType)typeByNameMap.get(headerType);
}
/**
* The package name for the schema.
*
* @return he package name for the schema.
*/
public String packageName()
{
return packageName;
}
/**
* The description of the schema.
*
* @return the description of the schema.
*/
public String description()
{
return description;
}
/**
* The id number of the schema.
*
* @return the id number of the schema.
*/
public int id()
{
return id;
}
/**
* The version number of the schema.
*
* @return the version number of the schema.
*/
public int version()
{
return version;
}
/**
* The semantic version number of the schema. Typically, used to reference a third party standard such as FIX.
*
* @return the semantic version number of the schema.
*/
public String semanticVersion()
{
return semanticVersion;
}
/**
* Return a given {@link Message} object with the given messageId.
*
* @param messageId of the message to return.
* @return a given {@link Message} for the messageId.
*/
public Message getMessage(final long messageId)
{
return messageByIdMap.get(messageId);
}
/**
* Get the {@link Type} for a given name.
*
* @param typeName to lookup.
* @return the type if found otherwise null.
*/
public Type getType(final String typeName)
{
return typeByNameMap.get(typeName);
}
/**
* Get the {@link Collection} of {@link Message}s for this Schema.
*
* @return the {@link Collection} of {@link Message}s for this Schema.
*/
public Collection messages()
{
return messageByIdMap.values();
}
/**
* Get the {@link Collection} of {@link Type}s for this Schema.
*
* @return the {@link Collection} of {@link Type}s for this Schema.
*/
public Collection types()
{
return typeByNameMap.values();
}
/**
* Return the byte order specified by the messageSchema
*
* @return {@link ByteOrder} of the message encoding.
*/
public ByteOrder byteOrder()
{
return byteOrder;
}
/**
* Validate the message schema and delegate warnings and errors to the supplied {@link ErrorHandler}.
*
* @param errorHandler for delegating warnings and errors.
*/
public void validate(final ErrorHandler errorHandler)
{
final Deque path = new ArrayDeque<>();
for (final Type type : typeByNameMap.values())
{
validateType(errorHandler, path, type);
}
for (final Message message : messageByIdMap.values())
{
if (message.sinceVersion() > version)
{
errorHandler.error(message.name() + ".sinceVersion=" + message.sinceVersion() +
" > messageSchema.version=" + version);
}
path.addLast(message.name());
for (final Field field : message.fields())
{
validateField(errorHandler, path, field);
}
path.removeLast();
}
}
private void validateType(final ErrorHandler errorHandler, final Deque path, final Type type)
{
if (type instanceof EncodedDataType)
{
validateEncodedType(errorHandler, path, (EncodedDataType)type);
}
else if (type instanceof EnumType)
{
validateEnumType(errorHandler, path, (EnumType)type);
}
else if (type instanceof SetType)
{
validateSetType(errorHandler, path, (SetType)type);
}
else if (type instanceof CompositeType)
{
validateCompositeType(errorHandler, path, (CompositeType)type);
}
}
private void validateEncodedType(
final ErrorHandler errorHandler, final Deque path, final EncodedDataType type)
{
if (type.sinceVersion() > version)
{
reportError(errorHandler, path, type.name(), type.sinceVersion());
}
}
private void validateEnumType(final ErrorHandler errorHandler, final Deque path, final EnumType type)
{
if (type.sinceVersion() > version)
{
reportError(errorHandler, path, type.name(), type.sinceVersion());
}
path.addLast(type.name());
for (final EnumType.ValidValue validValue : type.validValues())
{
if (validValue.sinceVersion() > version)
{
reportError(errorHandler, path, validValue.name(), validValue.sinceVersion());
}
}
path.removeLast();
}
private void validateSetType(final ErrorHandler errorHandler, final Deque path, final SetType type)
{
if (type.sinceVersion() > version)
{
reportError(errorHandler, path, type.name(), type.sinceVersion());
}
path.addLast(type.name());
for (final SetType.Choice choice : type.choices())
{
if (choice.sinceVersion() > version)
{
reportError(errorHandler, path, choice.name(), choice.sinceVersion());
}
}
path.removeLast();
}
private void validateCompositeType(
final ErrorHandler errorHandler, final Deque path, final CompositeType type)
{
if (type.sinceVersion() > version)
{
reportError(errorHandler, path, type.name(), type.sinceVersion());
}
path.addLast(type.name());
for (final Type subType : type.getTypeList())
{
validateType(errorHandler, path, subType);
}
path.removeLast();
}
private void validateField(final ErrorHandler errorHandler, final Deque path, final Field field)
{
if (field.sinceVersion() > version)
{
reportError(errorHandler, path, field.name(), field.sinceVersion());
}
final List groupFields = field.groupFields();
if (null != groupFields)
{
path.addLast(field.name());
for (final Field groupField : groupFields)
{
validateField(errorHandler, path, groupField);
}
path.removeLast();
}
}
private void reportError(
final ErrorHandler errorHandler, final Deque path, final String name, final int sinceVersion)
{
final StringBuilder sb = new StringBuilder();
for (final String step : path)
{
sb.append(step).append('.');
}
sb.append(name)
.append(".sinceVersion=").append(sinceVersion)
.append(" > messageSchema.version=").append(version);
errorHandler.error(sb.toString());
}
private static Node findNode(final Node contextNode, final String path)
{
try
{
return (Node)XPathFactory.newInstance().newXPath()
.evaluate(path, contextNode, XPathConstants.NODE);
}
catch (final XPathExpressionException ex)
{
throw new IllegalArgumentException("Unable to locate node with path=" + path, ex);
}
}
}