com.blackbear.flatworm.config.impl.DefaultConfigurationReaderImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.blackbear.flatworm.config.impl.DefaultConfigurationReaderImpl.java

Source

/*
 * Flatworm - A Java Flat File Importer/Exporter Copyright (C) 2004 James M. Turner.
 * Extended by James Lawrence 2005
 * Extended by Josh Brackett in 2011 and 2012
 * Extended by Alan Henson in 2016
 *
 * 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 com.blackbear.flatworm.config.impl;

import com.google.common.base.Joiner;

import com.blackbear.flatworm.CardinalityMode;
import com.blackbear.flatworm.FileFormat;
import com.blackbear.flatworm.Util;
import com.blackbear.flatworm.config.BeanBO;
import com.blackbear.flatworm.config.CardinalityBO;
import com.blackbear.flatworm.config.ConfigurationReader;
import com.blackbear.flatworm.config.ConfigurationValidator;
import com.blackbear.flatworm.config.ConversionOptionBO;
import com.blackbear.flatworm.config.ConverterBO;
import com.blackbear.flatworm.config.LineBO;
import com.blackbear.flatworm.config.LineElement;
import com.blackbear.flatworm.config.RecordBO;
import com.blackbear.flatworm.config.RecordDefinitionBO;
import com.blackbear.flatworm.config.RecordElementBO;
import com.blackbear.flatworm.config.ScriptletBO;
import com.blackbear.flatworm.config.SegmentElementBO;
import com.blackbear.flatworm.errors.FlatwormConfigurationException;
import com.blackbear.flatworm.errors.FlatwormParserException;

import org.apache.commons.lang.StringUtils;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

/**
 * The {@code ConfigurationReader} class is used to initialize Flatworm with an XML configuration file which describes the format and
 * conversion options to be applied to the input file to produce output beans.
 *
 * @author Alan Henson
 */
public class DefaultConfigurationReaderImpl implements ConfigurationReader {
    /**
     * {@code loadConfigurationFile} takes an XML configuration file, and returns a {@code FileFormat} object, which can be used to parse an
     * input file into beans.
     *
     * @param xmlFilePath The path to an XML file which contains a valid Flatworm configuration.
     * @return A {@code FileFormat} object which can parse the specified format.
     * @throws FlatwormConfigurationException If the configuration data contains invalid syntax.
     * @throws IOException                    If issues occur while reading from I/O.
     */
    @Override
    public FileFormat loadConfigurationFile(String xmlFilePath) throws FlatwormConfigurationException, IOException {
        FileFormat fileFormat;
        try (InputStream in = getClass().getClassLoader().getResourceAsStream(xmlFilePath)) {
            if (in != null) {
                fileFormat = loadConfigurationFile(in);
            } else {
                try (InputStream inTakeTwo = new FileInputStream(xmlFilePath)) {
                    fileFormat = loadConfigurationFile(inTakeTwo);
                }
            }
        }
        return fileFormat;
    }

    /**
     * {@code loadConfigurationFile} takes an XML configuration file, and returns a {@code FileFormat} object, which can be used to parse an
     * input file into beans.
     *
     * @param xmlFile An XML file which contains a valid Flatworm configuration.
     * @return A {@code FileFormat} object which can parse the specified format.
     * @throws FlatwormConfigurationException If the configuration data contains invalid syntax.
     * @throws IOException                    If issues occur while reading from I/O.
     */
    @Override
    public FileFormat loadConfigurationFile(File xmlFile) throws FlatwormConfigurationException, IOException {
        return loadConfigurationFile(xmlFile.getAbsolutePath());
    }

    /**
     * {@code loadConfigurationFile} takes an {@link InputStream} and returns a {@code FileFormat} object, which can be used to parse an
     * input file into beans.
     *
     * @param in The {@link InputStream} instance to use in parsing the configuration file.
     * @return a constructed {@link FileFormat} if the parsing was successful.
     * @throws FlatwormConfigurationException If the configuration data contains invalid syntax.
     * @throws IOException                    If issues occur while reading from I/O.
     */
    @Override
    public FileFormat loadConfigurationFile(InputStream in) throws FlatwormConfigurationException, IOException {
        DocumentBuilder parser;
        Document document;
        NodeList children;
        FileFormat fileFormat = null;

        try {
            DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
            parser = fact.newDocumentBuilder();
            document = parser.parse((new InputSource(in)));
            children = document.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (("file-format".equals(child.getNodeName())) && (child.getNodeType() == Node.ELEMENT_NODE)) {
                    fileFormat = (FileFormat) traverse(child);
                    break;
                }
            }

            if (fileFormat != null) {
                // Make sure we haven't double dipped the default handling of data.
                if (fileFormat.hasDefaultRecord() && fileFormat.isIgnoreUnmappedRecords()) {
                    throw new FlatwormParserException(
                            "You cannot have default Records (those lacking identifier configuration) and "
                                    + "the ignore-unmapped-records flag set to true - you must have one or the other.");
                }
            }
        } catch (Exception e) {
            throw new FlatwormConfigurationException(e.getMessage(), e);
        }
        return fileFormat;
    }

    /**
     * Given the {@code node}, collect the child nodes and return them in a {@link List} as their reconstituted object - this assumes that
     * the child nodes have configuration data that result in configuration DTOs.
     *
     * @param node The parent node.
     * @return a {@link List} of all child configuration elements in their DTO format.
     * @throws FlatwormConfigurationException should parsing the child nodes fail due configuration syntax errors.
     */
    protected List<Object> getChildNodes(Node node) throws FlatwormConfigurationException {
        List<Object> nodes = new ArrayList<>();
        NodeList children = node.getChildNodes();
        if (children != null) {
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                Object o = traverse(child);
                if (o != null)
                    nodes.add(o);
            }
        }
        return nodes;
    }

    /**
     * Fetch the child node of {@code node} that has the given name.
     *
     * @param node     The parent node.
     * @param nodeName The name of the node to search for.
     * @return The child node by the given {@code nodeName} if it is found, else {@code null} is returned.
     */
    protected Node getChildElementNodeName(Node node, String nodeName) {
        NodeList children = node.getChildNodes();
        Node childNode = null;
        if (children != null) {
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (nodeName.equals(child.getNodeName()) && child.getNodeType() == Node.ELEMENT_NODE) {
                    childNode = child;
                    break;
                }
            }
        }
        return childNode;
    }

    /**
     * Retrieve all children nodes of a given name from the parent {@code node}.
     *
     * @param node     The parent node.
     * @param nodeName The name of the nodes to collect.
     * @return a {@link List} of all nodes found by the given name.
     */
    protected List<Node> getChildElementNodesOfType(Node node, String nodeName) {
        List<Node> nodes = new ArrayList<>();
        NodeList children = node.getChildNodes();
        if (children != null) {
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (nodeName.equals(child.getNodeName()) && child.getNodeType() == Node.ELEMENT_NODE)
                    nodes.add(child);
            }
        }
        return nodes;
    }

    /**
     * Retrieve the text value found within the given {@code node} and return it.
     *
     * @param node The node from which the text value will be retrieved.
     * @return the text value found in the given {@code node} or {@code null} if no text value exists.
     */
    protected String getChildTextNodeValue(Node node) {
        String textValue = null;
        NodeList children = node.getChildNodes();
        if (children != null) {
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (child.getNodeType() == Node.TEXT_NODE) {
                    textValue = child.getNodeValue();
                }
            }
        }
        return textValue;
    }

    /**
     * Retrieve the {@code CDATA} value from a given Node - noting that the {@code node}'s child nodes will be searched and the first {@code
     * CDATA} tag will be returned.
     *
     * @param node The node that will be searched.
     * @return The {@code CDATA} found as a {@link String} or {@code null} if a {@code CDATA} node was not found.
     */
    protected String getChildCDataNodeValue(Node node) {
        String result = null;
        NodeList children = node.getChildNodes();
        if (children != null) {
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (child.getNodeType() == Node.CDATA_SECTION_NODE) {
                    result = CharacterData.class.cast(child).getData();
                    if (result != null) {
                        result = result.trim();
                    }
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Determine if a given {@code node} has the given named attribute and if the value of that attribute is {@code null}.
     *
     * @param node The node to search.
     * @param name The name of the attribute.
     * @return {@code true} if the attribute exists in the node and isn't {@code null} and {@code false} if not.
     */
    protected boolean hasAttributeValueNamed(Node node, String name) {
        return (node != null && node.getAttributes().getNamedItem(name) != null);
    }

    /**
     * Get the value of a given attribute for the given node.
     *
     * @param node The node.
     * @param name The attribute name.
     * @return The value of the attribute if present and {@code null} if not.
     */
    protected String getAttributeValueNamed(Node node, String name) {
        return hasAttributeValueNamed(node, name) ? node.getAttributes().getNamedItem(name).getNodeValue() : null;
    }

    /**
     * Retrieve an attribute by name as a {@link Node}.
     *
     * @param node The node that may contain the attribute.
     * @param name The name of the attribute.
     * @return The attribute as a {@link Node} instance if found and {@code null} if not.
     */
    protected Node getAttributeNamed(Node node, String name) {
        NamedNodeMap map = node.getAttributes();
        return map.getNamedItem(name);
    }

    /**
     * Traverse the given {@link Node} and attemt to extract flatworm configuration DTOs from the content.
     *
     * @param node The node to traverse.
     * @return a constructed flatform DTO object if found - if not, {@code null} is returned.
     * @throws FlatwormConfigurationException should the parsing hit unsupported or incorrect syntax.
     */
    private Object traverse(Node node) throws FlatwormConfigurationException {
        int type = node.getNodeType();
        if (type == Node.ELEMENT_NODE) {
            String nodeName = node.getNodeName();

            // File Format.
            if (nodeName.equals("file-format")) {
                FileFormat fileFormat = readFileFormat(node);
                List<String> errors = ConfigurationValidator.validateFileFormat(fileFormat);
                if (!errors.isEmpty()) {
                    throw new FlatwormConfigurationException(
                            String.format("Validation of the configuration content failed. See below:%n%s",
                                    Joiner.on(String.format("%n")).join(errors)));
                }
                return fileFormat;
            }

            // ConverterBO.
            if (nodeName.equals("converter")) {
                return readConverter(node);
            }

            // RecordBO.
            if (nodeName.equals("record")) {
                return readRecord(node);
            }

            // RecordBO Definition.
            if (nodeName.equals("record-definition")) {
                return readRecordDefinition(node);
            }

            // BeanBO.
            if (nodeName.equals("bean")) {
                return readBean(node);
            }

            // LineBO.
            if (nodeName.equals("line")) {
                return readLine(node);
            }

            // Segments.
            if (nodeName.equals("segment-element")) {
                return readSegmentElement(node);
            }

            // Records.
            if (nodeName.equals("record-element")) {
                return readRecordElement(node);
            }
        }
        return null;
    }

    /**
     * Read {@link FileFormat} data from the given {@code node}. Note that all child nodes will be parsed and the DTO data that makes up a
     * {@link FileFormat} instance will also be parsed.
     *
     * @param node The node to parse.
     * @return a {@link FileFormat} instance constructed from the {@code node}'s data.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected FileFormat readFileFormat(Node node) throws FlatwormConfigurationException {
        FileFormat fileFormat = new FileFormat();
        String encoding = Charset.defaultCharset().name();
        if (hasAttributeValueNamed(node, "encoding")) {
            encoding = getAttributeValueNamed(node, "encoding");
        }
        fileFormat.setEncoding(encoding);

        fileFormat.setIgnoreUnmappedRecords(
                Boolean.parseBoolean(getAttributeValueNamed(node, "ignore-unmapped-records")));

        List<Object> childNodes = getChildNodes(node);
        childNodes.forEach(childNode -> {
            if (childNode.getClass().isAssignableFrom(ConverterBO.class)) {
                fileFormat.addConverter(ConverterBO.class.cast(childNode));
            } else if (childNode.getClass().isAssignableFrom(RecordBO.class)) {
                fileFormat.addRecord(RecordBO.class.cast(childNode));
            }
        });
        return fileFormat;
    }

    /**
     * Read a {@link ConverterBO} instance from the given {@code node}.
     *
     * @param node The node to parse.
     * @return a constructed {@link ConverterBO} instance.
     */
    protected ConverterBO readConverter(Node node) {
        return ConverterBO.builder().converterClass(getAttributeValueNamed(node, "class"))
                .method(getAttributeValueNamed(node, "method"))
                .returnType(getAttributeValueNamed(node, "return-type")).name(getAttributeValueNamed(node, "name"))
                .build();
    }

    /**
     * Parse a {@link RecordBO} instance out of the information found in the given node.
     *
     * @param node The node.
     * @return a constructed {@link RecordBO} instance.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected RecordBO readRecord(Node node) throws FlatwormConfigurationException {
        RecordBO record = new RecordBO();
        record.setName(getAttributeValueNamed(node, "name"));
        Node identChild = getChildElementNodeName(node, "record-ident");
        if (identChild != null) {
            Node lengthChild = getChildElementNodeName(identChild, "length-ident");
            Node fieldChild = getChildElementNodeName(identChild, "field-ident");
            Node scriptChild = getChildElementNodeName(identChild, "script-ident");
            if (lengthChild != null) {
                record.setRecordIdentity(readLengthIdentity(lengthChild));
            } else if (fieldChild != null) {
                record.setRecordIdentity(readFieldIdentity(fieldChild));
            } else if (scriptChild != null) {
                record.setRecordIdentity(readScriptIdentity(scriptChild));
            }
        }

        // Before/After Scriptlets.
        record.setBeforeScriptlet(readScriptlet(node, "before-scriptlet"));
        record.setAfterScriptlet(readScriptlet(node, "after-scriptlet"));

        // Record Definition
        Node recordChild = getChildElementNodeName(node, "record-definition");
        RecordDefinitionBO recordDefinition = readRecordDefinition(recordChild);
        recordDefinition.setParentRecord(record);
        record.setRecordDefinition(recordDefinition);

        return record;
    }

    /**
     * Read the {@code length-ident} information from the given {@code node} and populate it in the {@link RecordBO} instance.
     *
     * @param node The node containing the data.
     * @return a built {@link LengthIdentityImpl} instance.
     */
    protected LengthIdentityImpl readLengthIdentity(Node node) {
        LengthIdentityImpl lengthIdentity = new LengthIdentityImpl();
        lengthIdentity.setMinLength(Util.tryParseInt(getAttributeValueNamed(node, "min-length")));
        lengthIdentity.setMaxLength(Util.tryParseInt(getAttributeValueNamed(node, "max-length")));
        return lengthIdentity;
    }

    /**
     * Read the {@code field-ident} information from the given {@code node} and populate it in the {@link RecordBO} instance.
     *
     * @param node The node containing the data.
     * @return a built {@link FieldIdentityImpl} instance.
     */
    protected FieldIdentityImpl readFieldIdentity(Node node) {

        FieldIdentityImpl fieldIdentity = new FieldIdentityImpl(
                Util.tryParseBoolean(getAttributeValueNamed(node, "ignore-case"), false));

        fieldIdentity.setStartPosition(Util.tryParseInt(getAttributeValueNamed(node, "field-start")));
        fieldIdentity.setFieldLength(Util.tryParseInt(getAttributeValueNamed(node, "field-length")));

        List<Node> matchNodes = getChildElementNodesOfType(node, "match-string");
        matchNodes.forEach(matchNode -> fieldIdentity.addMatchingString(getChildTextNodeValue(matchNode)));

        return fieldIdentity;
    }

    /**
     * Read the {@code script-identity} tag values from the given node and populate the {@link RecordBO} instance with the values.
     *
     * @param node The node containing the data.
     * @return a built {@link ScriptIdentityImpl} instance.
     * @throws FlatwormConfigurationException should the {@code script-ident} node be invalid.
     */
    protected ScriptIdentityImpl readScriptIdentity(Node node) throws FlatwormConfigurationException {
        ScriptIdentityImpl scriptIdentity = null;
        ScriptletBO scriptlet = readScriptlet(node);
        if (scriptlet != null) {
            scriptIdentity = new ScriptIdentityImpl(scriptlet);
        }
        return scriptIdentity;
    }

    /**
     * Read the {@link ScriptletBO} data values from the given child node (identified by the {@code elementName} parameter), 
     * and return it a constructed instance.
     *
     * @param node The parent node containing the child node {@link ScriptletBO} data.
     * @param elementName The name of the child element within the {@code node} that has the scriptlet data.             
     * @return a built {@link ScriptletBO} instance.
     * @throws FlatwormConfigurationException should the node not contain valid information.
     */
    protected ScriptletBO readScriptlet(Node node, String elementName) throws FlatwormConfigurationException {
        ScriptletBO scriptlet = null;
        Node scriptletNode = getChildElementNodeName(node, elementName);
        if (scriptletNode != null) {
            scriptlet = readScriptlet(scriptletNode);
        }
        return scriptlet;
    }

    /**
     * Read the {@link ScriptletBO} data values from the given node and return it a constructed instance.
     *
     * @param node The node containing the data.
     * @return a built {@link ScriptletBO} instance.
     * @throws FlatwormConfigurationException should the node not contain valid information.
     */
    protected ScriptletBO readScriptlet(Node node) throws FlatwormConfigurationException {
        String scriptEngineName = getAttributeValueNamed(node, "script-engine");
        String scriptMethodName = getAttributeValueNamed(node, "method-name");
        String scriptFile = getAttributeValueNamed(node, "script-file");

        // Simple JavaScript doesn't require CDATA - see if it exists in the text field alone.
        String script = getChildTextNodeValue(node);

        // If not, look for CDATA.
        if (StringUtils.isBlank(script)) {
            script = getChildCDataNodeValue(node);
        }

        ScriptletBO scriptlet = new ScriptletBO(scriptEngineName, scriptMethodName);
        if (!StringUtils.isBlank(script)) {
            scriptlet.setScript(script);
        } else if (!StringUtils.isBlank(scriptFile)) {
            scriptlet.setScriptFile(scriptFile);
        }
        return scriptlet;
    }

    /**
     * Parse a {@link RecordDefinitionBO} instance out of the information found in the given node.
     *
     * @param node The node.
     * @return a constructed {@link RecordDefinitionBO} instance.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected RecordDefinitionBO readRecordDefinition(Node node) throws FlatwormConfigurationException {
        RecordDefinitionBO rd = new RecordDefinitionBO();

        List<Object> childNodes = getChildNodes(node);
        childNodes.forEach(childNode -> {
            if (childNode.getClass().isAssignableFrom(BeanBO.class)) {
                rd.addBean(BeanBO.class.cast(childNode));
            } else if (childNode.getClass().isAssignableFrom(LineBO.class)) {
                rd.addLine(LineBO.class.cast(childNode));
            }
        });
        return rd;
    }

    /**
     * Parse a {@link BeanBO} instance out of the information found in the given node.
     *
     * @param node The node.
     * @return a constructed {@link BeanBO} instance.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected BeanBO readBean(Node node) throws FlatwormConfigurationException {
        BeanBO bean = new BeanBO();
        bean.setBeanName(getAttributeValueNamed(node, "name"));
        bean.setBeanClass(getAttributeValueNamed(node, "class"));
        try {
            bean.setBeanObjectClass(Class.forName(bean.getBeanClass()));
        } catch (ClassNotFoundException e) {
            throw new FlatwormConfigurationException("Unable to load class " + bean.getBeanClass(), e);
        }
        return bean;
    }

    /**
     * Parse a {@link LineBO} instance out of the information found in the given node.
     *
     * @param node The node.
     * @return a constructed {@link LineBO} instance.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected LineBO readLine(Node node) throws FlatwormConfigurationException {
        LineBO line = new LineBO();

        // JBL - Determine if this line is delimited
        // Determine value of quote character, default = "
        // These fields are optional
        line.setDelimiter(getAttributeValueNamed(node, "delimit"));
        line.setQuoteChar(getAttributeValueNamed(node, "quote"));

        line.setRecordStartLine(Util.tryParseBoolean(getAttributeValueNamed(node, "record-start-line")));
        line.setRecordEndLine(Util.tryParseBoolean(getAttributeValueNamed(node, "record-end-line")));

        // Before/After Scriptlets.
        line.setBeforeScriptlet(readScriptlet(node, "before-scriptlet"));
        line.setAfterScriptlet(readScriptlet(node, "after-scriptlet"));

        // Note the child nodes will be a collection of record-elements and segment-elements.
        List<Object> childNodes = getChildNodes(node);
        childNodes.stream().filter(childNode -> childNode instanceof LineElement).map(LineElement.class::cast)
                .forEach(line::addLineElement);

        return line;
    }

    /**
     * Parse a {@link SegmentElementBO} instance out of the information found in the given node.
     *
     * @param node The node.
     * @return a constructed {@link SegmentElementBO} instance.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected SegmentElementBO readSegmentElement(Node node) throws FlatwormConfigurationException {
        SegmentElementBO segment = new SegmentElementBO();

        segment.setCardinality(readCardinality(node));

        Node fieldChild = getChildElementNodeName(node, "field-ident");
        if (fieldChild != null) {
            segment.setFieldIdentity(readFieldIdentity(fieldChild));
        }

        List<Object> childNodes = getChildNodes(node);
        childNodes.stream().filter(childNode -> childNode instanceof LineElement).map(LineElement.class::cast)
                .forEach(segment::addLineElement);

        return segment;
    }

    /**
     * Parse a {@link CardinalityBO} instance out of the information found in the given node and return it.
     * @param node    The node.
     * @return The built up {@link CardinalityBO} instance.                
     */
    protected CardinalityBO readCardinality(Node node) {
        CardinalityBO cardinality = new CardinalityBO();

        String segmentMode = getAttributeValueNamed(node, "cardinality-mode");
        cardinality.setMinCount(Util.tryParseInt(getAttributeValueNamed(node, "minCount")));
        cardinality.setMaxCount(Util.tryParseInt(getAttributeValueNamed(node, "maxCount")));

        cardinality.setBeanRef(getAttributeValueNamed(node, "beanref"));
        cardinality.setParentBeanRef(getAttributeValueNamed(node, "parent-beanref"));
        cardinality.setAddMethod(getAttributeValueNamed(node, "add-method"));
        cardinality.setPropertyName(getAttributeValueNamed(node, "property-name"));

        if (!StringUtils.isBlank(segmentMode)) {
            if (segmentMode.toUpperCase().startsWith(CardinalityMode.LOOSE.name())) {
                cardinality.setCardinalityMode(CardinalityMode.LOOSE);
            } else if (segmentMode.toUpperCase().startsWith(CardinalityMode.RESTRICTED.name())) {
                cardinality.setCardinalityMode(CardinalityMode.RESTRICTED);
            } else if (segmentMode.toUpperCase().startsWith(CardinalityMode.STRICT.name())) {
                cardinality.setCardinalityMode(CardinalityMode.STRICT);
            } else if (segmentMode.toUpperCase().startsWith(CardinalityMode.SINGLE.name())) {
                cardinality.setCardinalityMode(CardinalityMode.SINGLE);
            }
        } else {
            cardinality.setCardinalityMode(CardinalityMode.LOOSE);
        }
        return cardinality;
    }

    /**
     * Parse a {@link RecordElementBO} instance out of the information found in the given node.
     *
     * @param node The node.
     * @return a constructed {@link RecordElementBO} instance.
     * @throws FlatwormConfigurationException should the configuration data contain invalid syntax or values.
     */
    protected RecordElementBO readRecordElement(Node node) throws FlatwormConfigurationException {
        RecordElementBO recordElement = new RecordElementBO();

        Node start = getAttributeNamed(node, "start");
        Node end = getAttributeNamed(node, "end");

        Node length = getAttributeNamed(node, "length");
        Node beanref = getAttributeNamed(node, "beanref");
        Node converterName = getAttributeNamed(node, "converter-name");
        Node ignoreField = getAttributeNamed(node, "ignore-field");
        Node trimValue = getAttributeNamed(node, "trim-value");

        if (start != null) {
            recordElement.setFieldStart(Util.tryParseInt(start.getNodeValue()));
        }
        if (end != null) {
            recordElement.setFieldEnd(Util.tryParseInt(end.getNodeValue()));
        }
        if (length != null) {
            recordElement.setFieldLength(Util.tryParseInt(length.getNodeValue()));
        }
        if (beanref != null) {
            int lastIndex;
            recordElement.setCardinality(readCardinality(node));
            recordElement.getCardinality().setCardinalityMode(CardinalityMode.SINGLE);
            if (!StringUtils.isBlank(recordElement.getCardinality().getBeanRef())
                    && (lastIndex = recordElement.getCardinality().getBeanRef().lastIndexOf('.')) > -1) {
                String beanRef = recordElement.getCardinality().getBeanRef();
                recordElement.getCardinality().setBeanRef(beanRef.substring(0, lastIndex));
                recordElement.getCardinality().setPropertyName(beanRef.substring(lastIndex + 1));
            }
        }
        if (converterName != null) {
            recordElement.setConverterName(converterName.getNodeValue());
        }
        if (ignoreField != null) {
            recordElement.setIgnoreField(Util.tryParseBoolean(ignoreField.getNodeValue()));
        }
        if (trimValue != null) {
            recordElement.setTrimValue(Util.tryParseBoolean(trimValue.getNodeValue()));
        }

        readConversionOptions(node, recordElement);
        return recordElement;
    }

    /**
     * Parse a {@link ConversionOptionBO}s out of the information found in the given node.
     *
     * @param node          The node.
     * @param recordElement The {@link RecordElementBO} instance to update with the {@link ConversionOptionBO} instances found.
     */
    protected void readConversionOptions(Node node, RecordElementBO recordElement) {
        List<Node> childNodes = getChildElementNodesOfType(node, "conversion-option");
        for (Node o : childNodes) {
            String name = getAttributeValueNamed(o, "name");
            String value = getAttributeValueNamed(o, "value");
            ConversionOptionBO co = new ConversionOptionBO(name, value);
            recordElement.addConversionOption(name, co);
        }
    }

    // TODO need to refactor the validation logic to a single validation class.

}