![JAR search and dependency download from the Maven repository](/logo.png)
org.smooks.edi.edisax.model.EDIConfigDigester Maven / Gradle / Ivy
/*-
* ========================LICENSE_START=================================
* smooks-edi-sax
* %%
* Copyright (C) 2020 Smooks
* %%
* Licensed under the terms of the Apache License Version 2.0, or
* the GNU Lesser General Public License version 3.0 or later.
*
* SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-or-later
*
* ======================================================================
*
* 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.
*
* ======================================================================
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* =========================LICENSE_END==================================
*/
package org.smooks.edi.edisax.model;
import org.smooks.assertion.AssertArgument;
import org.smooks.edi.edisax.EDIConfigurationException;
import org.smooks.edi.edisax.EDITypeEnum;
import org.smooks.edi.edisax.model.internal.*;
import org.smooks.resource.URIResourceLocator;
import org.smooks.xml.XmlUtil;
import org.smooks.xml.XsdDOMValidator;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Digests an edi-message-mapping and populates a {@link Edimap}.
*
* @author bardl
*/
public class EDIConfigDigester {
private URI modelURI;
private URIResourceLocator importLocator;
public static final String XSD_V10 = "http://www.milyn.org/schema/edi-message-mapping-1.0.xsd";
public static final String XSD_V11 = "http://www.milyn.org/schema/edi-message-mapping-1.1.xsd";
public static final String XSD_V12 = "http://www.milyn.org/schema/edi-message-mapping-1.2.xsd";
public static final String XSD_V13 = "http://www.milyn.org/schema/edi-message-mapping-1.3.xsd";
public static final String XSD_V14 = "http://www.milyn.org/schema/edi-message-mapping-1.4.xsd";
public static final String XSD_V15 = "http://www.milyn.org/schema/edi-message-mapping-1.5.xsd";
public static final String XSD_V16 = "http://www.milyn.org/schema/edi-message-mapping-1.6.xsd";
public static final String XSD_V17 = "https://www.smooks.org/schema/edi-message-mapping-1.7.xsd";
private static final String NAMESPACE_SUFFIX = ":";
/**
* Public default Constructor.
*/
public EDIConfigDigester() {
importLocator = new URIResourceLocator();
}
/**
* Public constructor.
* @param modelURI The model resource URI.
* @param importBaseURI The base URI for loading imports.
*/
public EDIConfigDigester(URI modelURI, URI importBaseURI) {
AssertArgument.isNotNull(importBaseURI, "importBaseURI");
this.modelURI = modelURI;
importLocator = new URIResourceLocator();
importLocator.setBaseURI(importBaseURI);
}
/**
* Digest the XML edi-message-mapping configuration stream.
* @param stream the edi-message-mapping stream.
* @return the {@link Edimap}.
* @throws IOException Error parsing the XML stream.
* @throws SAXException Error parsing the XML stream.
* @throws EDIConfigurationException Multiple or no namespaces in edi-message-mapping.
*/
public static Edimap digestConfig(InputStream stream) throws IOException, SAXException, EDIConfigurationException {
return new EDIConfigDigester().digestEDIConfig(stream);
}
/**
* Digest the XML edi-message-mapping configuration stream.
* @param stream the edi-message-mapping stream.
* @return the {@link Edimap}.
* @throws IOException Error parsing the XML stream.
* @throws SAXException Error parsing the XML stream.
* @throws EDIConfigurationException Multiple or no namespaces in edi-message-mapping.
*/
public Edimap digestEDIConfig(InputStream stream) throws IOException, SAXException, EDIConfigurationException {
Document configDoc;
try {
configDoc = XmlUtil.parseStream(stream);
} catch (ParserConfigurationException ee) {
throw new SAXException("Unable to parse Smooks configuration.", ee);
}
return digestEDIConfig(configDoc);
}
/**
* Digest the XML edi-message-mapping configuration stream.
* @param stream the edi-message-mapping stream.
* @return the {@link Edimap}.
* @throws IOException Error parsing the XML stream.
* @throws SAXException Error parsing the XML stream.
* @throws EDIConfigurationException Multiple or no namespaces in edi-message-mapping.
*/
public Edimap digestEDIConfig(Reader stream) throws IOException, SAXException, EDIConfigurationException {
Document configDoc;
try {
configDoc = XmlUtil.parseStream(stream);
} catch (ParserConfigurationException ee) {
throw new SAXException("Unable to parse Smooks configuration.", ee);
}
return digestEDIConfig(configDoc);
}
private Edimap digestEDIConfig(Document configDoc) throws SAXException, EDIConfigurationException, IOException {
XsdDOMValidator validator = new XsdDOMValidator(configDoc);
if (validator.getNamespaces().size() == 0) {
throw new EDIConfigurationException("The edi-message-mapping configuration must contain a namespace.");
}
if (validator.getNamespaces().size() > 1) {
throw new EDIConfigurationException("Unsupported use of multiple configuration namespaces from inside the edi-message-mapping configuration.");
}
String ediNS = validator.getNamespaces().get(0).toString();
validator.validate();
Edimap edimap = new Edimap(modelURI);
if(assertValidXSD(ediNS)) {
digestXSDValidatedConfig(configDoc, edimap, ediNS);
} else {
throw new SAXException("Cannot parse edi-message-mapping configuration. Unsupported default Namespace '" + ediNS + "'.");
}
return edimap;
}
/**
* Assert that schema used for validation are valid, i.e. the schema is
* either edi-message-mapping-1.0 or edi-message-mapping-1.1.
* @param ediNS the schema used for validation.
* @return true if ediNS is valid, false otherwise.
*/
private static boolean assertValidXSD(String ediNS) {
return XSD_V10.equals(ediNS) || XSD_V11.equals(ediNS) || XSD_V12.equals(ediNS) || XSD_V13.equals(ediNS) || XSD_V14.equals(ediNS) || XSD_V15.equals(ediNS) || XSD_V16.equals(ediNS) || XSD_V17.equals(ediNS);
}
/**
* Digest child elements of edimap.
* @param configDoc the Edimap element.
* @param edimap the {@link Edimap} to populate.
* @param schemaName the schema uri.
* @throws EDIConfigurationException is thrown when unable to retrieve namespace in configuration.
*/
private void digestXSDValidatedConfig(Document configDoc, Edimap edimap, String schemaName) throws EDIConfigurationException {
// Check the namespace attribute
Element documentElement = configDoc.getDocumentElement();
//Retrieve the namespace for the schema.
String namespacePrefix = retrieveNamespace(documentElement, schemaName);
NodeList nodes = documentElement.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeName().equalsIgnoreCase(namespacePrefix + "import")) {
digestImport(node, edimap);
} else if (node.getNodeName().equalsIgnoreCase(namespacePrefix + "description")) {
digestDescription(node, edimap);
} else if (node.getNodeName().equalsIgnoreCase(namespacePrefix + "delimiters")) {
digestDelimiters(node, edimap);
} else if (node.getNodeName().equalsIgnoreCase(namespacePrefix + "segments")) {
digestSegments(node, edimap, namespacePrefix);
}
}
}
/**
* Digest attributes of Delimiter element and populate Delimiter.
* @param node the Delimiter element.
* @param edimap the {@link Edimap} to populate.
*/
private static void digestDelimiters(Node node, Edimap edimap) {
Delimiters delimiters = new Delimiters();
edimap.setDelimiters(delimiters);
delimiters.setSegment(getAttributeValue(node, "segment"));
delimiters.setField(getAttributeValue(node, "field"));
delimiters.setFieldRepeat(getAttributeValue(node, "fieldRepeat"));
delimiters.setComponent(getAttributeValue(node, "component"));
delimiters.setSubComponent(getAttributeValue(node, "sub-component"));
delimiters.setEscape(getAttributeValue(node, "escape"));
delimiters.setDecimalSeparator(getAttributeValue(node, "decimalSeparator"));
}
/**
* Digest attributes of Description element and populate Description.
* @param node the Description element.
* @param edimap the {@link Edimap} to populate.
*/
private static void digestDescription(Node node, Edimap edimap) {
Description description = new Description();
edimap.setDescription(description);
description.setName(getAttributeValue(node, "name"));
description.setVersion(getAttributeValue(node, "version"));
String namespace = getAttributeValue(node, "namespace");
if (namespace != null) {
description.setNamespace(namespace);
} else {
description.setNamespace(XMLConstants.NULL_NS_URI);
}
}
/**
* Digest attributes of Import element and populate Import.
* @param node the Import element.
* @param edimap the {@link Edimap} to populate.
*/
private void digestImport(Node node, Edimap edimap) {
Import edimapImport = new Import();
edimap.getImports().add(edimapImport);
URI resourceURI = importLocator.resolveURI(getAttributeValue(node, "resource"));
edimapImport.setResourceURI(resourceURI);
edimapImport.setNamespace(getAttributeValue(node, "namespace"));
edimapImport.setTruncatableFields(getNodeValueAsBoolean(node, "truncatableFields"));
edimapImport.setTruncatableComponents(getNodeValueAsBoolean(node, "truncatableComponents"));
edimapImport.setTruncatableSegments(getNodeValueAsBoolean(node, "truncatableSegments"));
}
/**
* Digest attributes and child elements of Segments element. Populates Segments.
* @param node the Segments element.
* @param edimap the {@link Edimap} to populate.
* @param namespacePrefix the prefix used to name elements in xml.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void digestSegments(Node node, Edimap edimap, String namespacePrefix) throws EDIConfigurationException {
SegmentGroup segments = new SegmentGroup();
setValuesForMappingNode(node, segments, namespacePrefix, null);
segments.setNamespace(edimap.getDescription().getNamespace());
edimap.setSegments(segments);
edimap.setIgnoreUnmappedSegments(getNodeValueAsBoolean(node, "ignoreUnmappedSegments"));
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node currentNode = nodes.item(i);
digestSegmentGroup(currentNode, edimap.getSegments().getSegments(), namespacePrefix, segments);
}
}
/**
* Digests attributes and child elements of Segment element.
* @param node the Segment element.
* @param segmentGroup the {@link SegmentGroup} to populate.
* @param namespacePrefix the prefix used to name elements in xml.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void digestSegment(Node node, SegmentGroup segmentGroup, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
if(segmentGroup instanceof Segment) {
Segment segment = (Segment) segmentGroup;
setValuesForSegment(segment, node, namespacePrefix, parent);
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node currentNode = nodes.item(i);
if (currentNode.getNodeName().equalsIgnoreCase(namespacePrefix + "field")) {
Field field = new Field();
segment.getFields().add(field);
digestField(currentNode, field, namespacePrefix, segment);
} else {
digestSegmentGroup(currentNode, segment.getSegments(), namespacePrefix, segment);
}
}
} else {
segmentGroup.setMaxOccurs(getNodeValueAsInteger(node, "maxOccurs"));
segmentGroup.setMinOccurs(getNodeValueAsInteger(node, "minOccurs"));
setValuesForMappingNode(node, segmentGroup, namespacePrefix, parent);
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node currentNode = nodes.item(i);
digestSegmentGroup(currentNode, segmentGroup.getSegments(), namespacePrefix, segmentGroup);
}
}
}
private boolean digestSegmentGroup(Node currentNode, List segmentGroupList, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
if (currentNode.getNodeName().equalsIgnoreCase(namespacePrefix + "segmentGroup")) {
SegmentGroup segment = new SegmentGroup();
segment.setDocumentation(getNodeValue(currentNode, namespacePrefix + "documentation"));
segmentGroupList.add(segment);
digestSegment(currentNode, segment, namespacePrefix, parent);
return true;
} else if (currentNode.getNodeName().equalsIgnoreCase(namespacePrefix + "segment")) {
Segment segment = new Segment();
segmentGroupList.add(segment);
digestSegment(currentNode, segment, namespacePrefix, parent);
return true;
}
return false;
}
/**
* Digests attributes and child elements of Field element.
* @param node the Field element.
* @param field the {@link Field} to populate
* @param namespacePrefix the prefix used to name elements in xml.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void digestField(Node node, Field field, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
setValuesForField(field, node, namespacePrefix, parent);
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node currentNode = nodes.item(i);
if (currentNode.getNodeName().equalsIgnoreCase(namespacePrefix + "component")) {
Component component = new Component();
field.getComponents().add(component);
digestComponent(currentNode, component, namespacePrefix, field);
}
}
}
/**
* Digests attributes and child elements of Component element.
* @param node the Component element.
* @param component the {@link Component} to populate.
* @param namespacePrefix the prefix used to name elements in xml.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void digestComponent(Node node, Component component, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
setValuesForComponent(component, node, namespacePrefix, parent);
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node currentNode = nodes.item(i);
if (currentNode.getNodeName().equalsIgnoreCase(namespacePrefix + "sub-component")) {
SubComponent subComponent = new SubComponent();
component.getSubComponents().add(subComponent);
setValuesForSubComponent(currentNode, subComponent, namespacePrefix, component);
}
}
}
/**
* Set values in {@link Segment}.
* @param segment the {@link Segment} to populate.
* @param node the Segment element.
*/
private void setValuesForSegment(Segment segment, Node node, String namespacePrefix, MappingNode parent) {
segment.setMaxOccurs(getNodeValueAsInteger(node, "maxOccurs"));
segment.setMinOccurs(getNodeValueAsInteger(node, "minOccurs"));
segment.setSegcode(getAttributeValue(node, "segcode"));
segment.setTruncatable(getNodeValueAsBoolean(node, "truncatable"));
segment.setIgnoreUnmappedFields(getNodeValueAsBoolean(node, "ignoreUnmappedFields"));
segment.setDescription(getAttributeValue(node, "description"));
setValuesForMappingNode(node, segment, namespacePrefix, parent);
if(segment.getNodeTypeRef() == null) {
// Backward compatibility support. Schema pre v 1.4 supported a segref
// on , which we thn generalised as nodeTypeRef for all node types.
segment.setNodeTypeRef(getAttributeValue(node, "segref"));
}
}
/**
* Set values in {@link Field}.
* @param field the {@link Field} to populate.
* @param node the Field element.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void setValuesForField(Field field, Node node, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
field.setRequired(getNodeValueAsBoolean(node, "required"));
field.setTruncatable(getNodeValueAsBoolean(node, "truncatable"));
setValuesForValueNode(node, field, namespacePrefix, parent);
}
/**
* Set values in {@link Component}.
* @param component the {@link Component} to populate.
* @param node the Component element.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void setValuesForComponent(Component component, Node node, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
component.setRequired(getNodeValueAsBoolean(node, "required"));
component.setTruncatable(getNodeValueAsBoolean(node, "truncatable"));
setValuesForValueNode(node, component, namespacePrefix, parent);
}
/**
* Set values in {@link SubComponent}.
* @param node the {@link SubComponent} to populate.
* @param subComponent the SubComponent element.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void setValuesForSubComponent(Node node, SubComponent subComponent, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
subComponent.setRequired(getNodeValueAsBoolean(node, "required"));
setValuesForValueNode(node, subComponent, namespacePrefix, parent);
}
/**
* Set values in {@link MappingNode}.
* @param node the {@link MappingNode} to populate.
* @param mappingNode the MappingNode element.
*/
private void setValuesForMappingNode(Node node, MappingNode mappingNode, String namespacePrefix, MappingNode parent) {
mappingNode.setName(getAttributeValue(node, "name"));
mappingNode.setXmltag(getAttributeValue(node, "xmltag"));
mappingNode.setNodeTypeRef(getAttributeValue(node, "nodeTypeRef"));
mappingNode.setDocumentation(getNodeValue(node, namespacePrefix + "documentation"));
mappingNode.setParent(parent);
if (parent != null) {
mappingNode.setNamespace(parent.getNamespace());
}
}
/**
* Set values in {@link ValueNode}.
* @param node the {@link ValueNode} to populate.
* @param valueNode the ValueNode element.
* @throws EDIConfigurationException is thrown when values are badly formatted.
*/
private void setValuesForValueNode(Node node, ValueNode valueNode, String namespacePrefix, MappingNode parent) throws EDIConfigurationException {
setValuesForMappingNode(node, valueNode, namespacePrefix, parent);
String type = getAttributeValue(node, "dataType");
if(type != null) {
valueNode.setDataType(type);
} else {
valueNode.setDataType(getAttributeValue(node, "type"));
}
valueNode.setMinLength(getNodeValueAsInteger(node, "minLength"));
valueNode.setMaxLength(getNodeValueAsInteger(node, "maxLength"));
String dataTypeParams = getAttributeValue(node, "dataTypeParameters");
if(dataTypeParams != null) {
digestParameters(valueNode, dataTypeParams);
} else {
digestParameters(valueNode, getAttributeValue(node, "typeParameters"));
}
}
/**
* Digests parameters from parameters attribute and insertsthe parameters into
* the valueNode. If first parameter is not a key-value-pair the parameter is
* considered to be a custom class name.
* @param valueNode the valueNode to populate.
* @param value the parameters as a string value.
* @throws EDIConfigurationException is thrown when parameters are used incorrectly.
*/
private static void digestParameters(ValueNode valueNode, String value) throws EDIConfigurationException {
if (value != null && !value.equals("")) {
List> result = new ArrayList>();
String[] parameters = value.split(";");
String[] entry;
String customClass = null;
for (int i = 0; i < parameters.length; i++) {
String parameter = parameters[i];
entry = parameter.split("=");
if (entry.length == 1) {
if (i == 0) {
customClass = entry[0];
result.add(new ParamEntry(CustomTypeConverter.CLASS_PROPERTY_NAME, entry[0]));
} else {
throw new EDIConfigurationException("Invalid use of paramaters in ValueNode. A parameter-entry should consist of a key-value-pair separated with the '='-character. Example: [parameters=\"key1=value1;key2=value2\"]");
}
} else if (entry.length == 2) {
result.add(new ParamEntry(entry[0], entry[1]));
} else {
throw new EDIConfigurationException("Invalid use of paramaters in ValueNode. A parameter-entry should consist of a key-value-pair separated with the '='-character. Example: [parameters=\"key1=value1;key2=value2\"]");
}
}
valueNode.setDataTypeParameters(result);
if ( valueNode.getDataType().equals(EDITypeEnum.CUSTOM_NAME) && customClass == null) {
throw new EDIConfigurationException("When using the Custom type in ValueNode the custom class type must exist as the first element in parameters");
} else if ( customClass != null && !valueNode.getDataType().equals(EDITypeEnum.CUSTOM_NAME)) {
throw new EDIConfigurationException("When first parameter in list of parameters is not a key-value-pair the type of the ValueNode should be Custom.");
}
}
}
/**
* Gets attribute value from node if it exists. Otherwise returns null.
* @param node the node.
* @param name the name of the attribute.
* @return Boolean value if attribute exists in node.
*/
private static Boolean getNodeValueAsBoolean(Node node, String name) {
String value = getAttributeValue(node, name);
if (value == null) {
return null;
}
return Boolean.parseBoolean(value);
}
/**
* Gets attribute value from node if it exists. Otherwise returns null.
* @param node the node.
* @param name the name of the attribute.
* @return Integer value if attribute exists in node.
*/
private static Integer getNodeValueAsInteger(Node node, String name) {
String value = getAttributeValue(node, name);
if (value == null) {
return null;
}
return Integer.parseInt(value);
}
/**
* Gets attribute value from node if it exists. Otherwise returns null.
* @param node the node.
* @param name the name of the attribute.
* @return String value if attribute exists in node.
*/
private static String getAttributeValue(Node node, String name) {
return node.getAttributes().getNamedItem(name) != null ? node.getAttributes().getNamedItem(name).getNodeValue() : null;
}
/**
* Gets attribute value from node if it exists. Otherwise returns null.
* @param node the node.
* @param name the name of the attribute.
* @return String value if attribute exists in node.
*/
private static String getNodeValue(Node node, String name) {
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node n = nodes.item(i);
if (n.getNodeName().equals(name)) {
return n.getTextContent();
// } else if (node.hasChildNodes()) {
// String value = getNodeValue(n, name);
// if (value != null) {
// return value;
// }
}
}
return null; //().getNamedItem(name) != null ? node.getAttributes().getNamedItem(name).getNodeValue() : null;
}
/**
* Retrives the namespaceprefix from edimap element in edi-message-mapping.
* @param documentElement the edimap element.
* @param schemaName the schema uri.
* @return the namespaceprefix used in the edi-message-mapping.
* @throws EDIConfigurationException is thrown when no namespace exists in configuration.
*/
private static String retrieveNamespace(Element documentElement, String schemaName) throws EDIConfigurationException {
NamedNodeMap attributes = documentElement.getAttributes();
Node node;
for (int i = 0; i < attributes.getLength(); i++) {
node = attributes.item(i);
if (node.getNodeValue().equals(schemaName)) {
String prefix = node.getNodeName();
if (prefix.startsWith("xmlns:")) {
return node.getNodeName().replace("xmlns:", "") + NAMESPACE_SUFFIX;
} else if (prefix.startsWith("xmlns")) {
return node.getNodeName().replace("xmlns", "");
} else {
throw new EDIConfigurationException("No namespace exists in edi-message-mapping. A namespace must be declared in order to digest configuration file.");
}
}
}
return "";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy