com.jkoolcloud.tnt4j.streams.configure.sax.ConfigParserHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.jkoolcloud.tnt4j.streams.configure.sax.ConfigParserHandler.java

Source

/*
 * Copyright 2014-2017 JKOOL, 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 com.jkoolcloud.tnt4j.streams.configure.sax;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.*;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import com.jkoolcloud.tnt4j.streams.configure.OutputProperties;
import com.jkoolcloud.tnt4j.streams.configure.StreamsConfigData;
import com.jkoolcloud.tnt4j.streams.configure.jaxb.ResourceReferenceType;
import com.jkoolcloud.tnt4j.streams.fields.ActivityField;
import com.jkoolcloud.tnt4j.streams.fields.ActivityFieldDataType;
import com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocator;
import com.jkoolcloud.tnt4j.streams.fields.ActivityFieldMappingType;
import com.jkoolcloud.tnt4j.streams.filters.AbstractExpressionFilter;
import com.jkoolcloud.tnt4j.streams.filters.DefaultValueFilter;
import com.jkoolcloud.tnt4j.streams.filters.StreamFiltersGroup;
import com.jkoolcloud.tnt4j.streams.inputs.TNTInputStream;
import com.jkoolcloud.tnt4j.streams.outputs.TNTStreamOutput;
import com.jkoolcloud.tnt4j.streams.parsers.ActivityParser;
import com.jkoolcloud.tnt4j.streams.parsers.GenericActivityParser;
import com.jkoolcloud.tnt4j.streams.transform.AbstractScriptTransformation;
import com.jkoolcloud.tnt4j.streams.transform.ValueTransformation;
import com.jkoolcloud.tnt4j.streams.utils.StreamsCache;
import com.jkoolcloud.tnt4j.streams.utils.StreamsResources;
import com.jkoolcloud.tnt4j.streams.utils.Utils;

/**
 * Implements the SAX DefaultHandler for parsing TNT4J-Streams configuration.
 *
 * @version $Revision: 1 $
 *
 * @see com.jkoolcloud.tnt4j.streams.configure.StreamsConfigLoader
 * @see com.jkoolcloud.tnt4j.streams.configure.sax.StreamsConfigSAXParser
 */
public class ConfigParserHandler extends DefaultHandler {

    /**
     * Constant for default location delimiter in configuration definition.
     */
    public static final String LOC_DELIM = "|"; // NON-NLS

    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String CONFIG_ROOT_ELMT_OLD = "tw-direct-feed"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String CONFIG_ROOT_ELMT = "tnt-data-source"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String PARSER_ELMT = "parser"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    protected static final String STREAM_ELMT = "stream"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String PROPERTY_ELMT = "property"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String FIELD_ELMT = "field"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String FIELD_MAP_ELMT = "field-map"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String FIELD_MAP_REF_ELMT = "field-map-ref"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String FIELD_LOC_ELMT = "field-locator"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String FIELD_TRANSFORM_ELMT = "field-transform"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String PARSER_REF_ELMT = "parser-ref"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String TNT4J_PROPERTIES_ELMT = "tnt4j-properties"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String FILTER_ELMT = "filter"; // NON-NLS
    // /**
    // * Constant for name of TNT4J-Streams XML configuration tag {@value}.
    // */
    // private static final String RULE_ELMT = "rule"; // NON-NLS
    // /**
    // * Constant for name of TNT4J-Streams XML configuration tag {@value}.
    // */
    // private static final String STEP_ELMT = "step"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String VALUE_ELMT = "value"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String EXPRESSION_ELMT = "expression"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String REF_ELMT = "reference"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String JAVA_OBJ_ELMT = "java-object"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String PARAM_ELMT = "param"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String EMBEDDED_ACTIVITY_ELMT = "embedded-activity"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String CACHE_ELMT = "cache"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String CACHE_ENTRY_ELMT = "entry"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String CACHE_KEY_ELMT = "key"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag {@value}.
     */
    private static final String RESOURCE_REF_ELMT = "resource-ref"; // NON-NLS

    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    protected static final String NAME_ATTR = "name"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String CLASS_ATTR = "class"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String VALUE_ATTR = "value"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String LOC_TYPE_ATTR = "locator-type"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String LOCATOR_ATTR = "locator"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String VALUE_TYPE_ATTR = "value-type"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String SPLIT_ATTR = "split"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String SEPARATOR_ATTR = "separator"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String DATA_TYPE_ATTR = "datatype"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String RADIX_ATTR = "radix"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    protected static final String UNITS_ATTR = "units"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String FORMAT_ATTR = "format"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String LOCALE_ATTR = "locale"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String TIMEZONE_ATTR = "timezone"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String SOURCE_ATTR = "source";
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String TARGET_ATTR = "target"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String TYPE_ATTR = "type"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String TAGS_ATTR = "tags"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String REQUIRED_ATTR = "required"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String TRANSPARENT_ATTR = "transparent"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String ID_ATTR = "id"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String BEAN_REF_ATTR = "beanRef"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String LANG_ATTR = "lang"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String HANDLE_ATTR = "handle"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String EVALUATION_ATTR = "evaluation"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String AGGREGATION_ATTR = "aggregation"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String FORMATTING_PATTERN_ATTR = "formattingPattern"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String URI_ATTR = "uri"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration tag attribute {@value}.
     */
    private static final String RESOURCE_ATTR = "resource"; // NON-NLS
    /**
     * Constant for name of TNT4J-Streams XML configuration entity {@value}.
     */
    private static final String CDATA = "<![CDATA[]]>"; // NON-NLS

    /**
     * Currently configured TNT input stream.
     */
    protected TNTInputStream<?, ?> currStream = null;
    private Map<String, String> currProperties = null;
    private ActivityParser currParser = null;
    private ActivityField currField = null;
    @SuppressWarnings("rawtypes")
    private StreamFiltersGroup currFilter = null;

    private boolean currFieldHasLocValAttr = false;
    // private boolean currFieldHasLocElmt = false;
    // private boolean currFieldHasMapElmt = false;

    private StreamsConfigData streamsConfigData = null;
    private Map<String, Object> javaObjectsMap = null;

    private Map<String, Map<String, ?>> resourcesMap;

    /**
     * Configuration parsing locator.
     */
    protected Locator currParseLocation = null;

    private boolean processingTNT4JProperties = false;
    private JavaObjectData javaObjectData = null;
    private Property currProperty = null;
    private FieldLocatorData currLocatorData = null;
    private FieldTransformData currTransform = null;
    private FilterValueData currFilterValue = null;
    private FilterExpressionData currFilterExpression = null;
    private boolean processingCache = false;
    private CacheEntryData currCacheEntry = null;

    /**
     * Buffer to put current configuration element (token) data value
     */
    protected StringBuilder elementData;

    /**
     * Constructs a new ConfigurationParserHandler.
     */
    public ConfigParserHandler() {
    }

    /**
     * Returns streams and parsers data loaded from configuration.
     *
     * @return un-marshaled streams and parsers data
     */
    public StreamsConfigData getStreamsConfigData() {
        return streamsConfigData;
    }

    @Override
    public void setDocumentLocator(Locator locator) {
        currParseLocation = locator;
    }

    @Override
    public void startDocument() throws SAXException {
        currStream = null;
        currProperties = null;
        currParser = null;
        currField = null;
        currFilter = null;
        currLocatorData = null;
        currFieldHasLocValAttr = false;
        // currFieldHasLocElmt = false;
        // currFieldHasMapElmt = false;
        processingTNT4JProperties = false;
        streamsConfigData = new StreamsConfigData();
        javaObjectsMap = new HashMap<>();
        processingCache = false;
    }

    @Override
    public void endDocument() throws SAXException {
        javaObjectsMap.clear();
        if (resourcesMap != null) {
            resourcesMap.clear();
        }
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes)
            throws SAXException {
        if (CONFIG_ROOT_ELMT.equals(qName) || CONFIG_ROOT_ELMT_OLD.equals(qName)) {
            if (streamsConfigData.isStreamsAvailable()) {
                throw new SAXParseException(
                        StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                                "ConfigParserHandler.multiple.elements", qName),
                        currParseLocation);
            }
        } else if (PROPERTY_ELMT.equals(qName)) {
            processProperty(attributes);
        } else if (FIELD_ELMT.equals(qName)) {
            processField(attributes);
        } else if (FIELD_LOC_ELMT.equals(qName)) {
            processFieldLocator(attributes);
        } else if (FIELD_MAP_ELMT.equals(qName)) {
            processFieldMap(attributes);
        } else if (PARSER_REF_ELMT.equals(qName)) {
            processParserRef(attributes);
        } else if (PARSER_ELMT.equals(qName)) {
            processParser(attributes);
        } else if (STREAM_ELMT.equals(qName)) {
            processStream(attributes);
        } else if (FILTER_ELMT.equals(qName)) {
            processFilter(attributes);
        } else if (VALUE_ELMT.equals(qName)) {
            processValue(attributes);
        } else if (EXPRESSION_ELMT.equals(qName)) {
            processFilterExpression(attributes);
        } else if (TNT4J_PROPERTIES_ELMT.equals(qName)) {
            processTNT4JProperties(attributes);
        } else if (REF_ELMT.equals(qName)) {
            processReference(attributes);
        } else if (JAVA_OBJ_ELMT.equals(qName)) {
            processJavaObject(attributes);
        } else if (PARAM_ELMT.equals(qName)) {
            processParam(attributes);
        } else if (FIELD_TRANSFORM_ELMT.equals(qName)) {
            processFieldTransform(attributes);
        } else if (EMBEDDED_ACTIVITY_ELMT.equals(qName)) {
            processEmbeddedActivity(attributes);
        } else if (CACHE_ELMT.equals(qName)) {
            processCache(attributes);
        } else if (CACHE_ENTRY_ELMT.equals(qName)) {
            processCacheEntry(attributes);
        } else if (CACHE_KEY_ELMT.equals(qName)) {
            processKey(attributes);
        } else if (FIELD_MAP_REF_ELMT.equals(qName)) {
            processFieldMapReference(attributes);
        } else if (RESOURCE_REF_ELMT.equals(qName)) {
            processResourceReference(attributes);
        }
    }

    private void processCache(Attributes attributes) {
        processingCache = true;
    }

    /**
     * Processes a {@code <key>} element under <entry> element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processCacheEntry(Attributes attrs) throws SAXException {
        if (currCacheEntry != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", CACHE_ENTRY_ELMT), currParseLocation);
        }

        if (processingCache == false) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", CACHE_ENTRY_ELMT, CACHE_ELMT),
                    currParseLocation);
        }

        if (currCacheEntry == null) {
            currCacheEntry = new CacheEntryData();
        }

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (ID_ATTR.equals(attName)) {
                currCacheEntry.id = attValue;
            }
        }

        notEmpty(currCacheEntry.id, CACHE_ENTRY_ELMT, ID_ATTR);
    }

    /**
     * Processes a {@code <key>} element under <entry> element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processKey(Attributes attrs) throws SAXException {
        if (currCacheEntry == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", CACHE_KEY_ELMT, CACHE_ENTRY_ELMT),
                    currParseLocation);
        }

        elementData = new StringBuilder();
    }

    /**
     * Processes a {@code <parser>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processParser(Attributes attrs) throws SAXException {
        if (currParser != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", PARSER_ELMT), currParseLocation);
        }
        String name = null;
        String className = null;
        String tags = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                name = attValue;
            } else if (CLASS_ATTR.equals(attName)) {
                className = attValue;
            } else if (TAGS_ATTR.equals(attName)) {
                tags = attValue;
            }
        }

        notEmpty(name, PARSER_ELMT, NAME_ATTR);
        notEmpty(className, PARSER_ELMT, CLASS_ATTR);
        if (streamsConfigData.getParser(name) != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.duplicate.parser.definition", name), currParseLocation);
        }
        try {
            Object newParser = Utils.createInstance(className);
            if (!(newParser instanceof ActivityParser)) {
                throw new SAXNotSupportedException(StreamsResources.getStringFormatted(
                        StreamsResources.RESOURCE_BUNDLE_NAME, "ConfigParserHandler.not.implement.interface",
                        PARSER_ELMT, CLASS_ATTR, className, ActivityParser.class.getName(), getLocationInfo()));
            }
            currParser = (ActivityParser) newParser;
        } catch (Exception exc) {
            throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.failed.to.load", PARSER_ELMT, CLASS_ATTR, className, getLocationInfo()),
                    exc);
        }
        if (currParser != null) {
            currParser.setName(name);
            currParser.setTags(tags);
            streamsConfigData.addParser(currParser);
        }
    }

    /**
     * Processes a {@code <field>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processField(Attributes attrs) throws SAXException {
        if (currField != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", FIELD_ELMT), currParseLocation);
        }
        if (currParser == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", FIELD_ELMT, PARSER_ELMT),
                    currParseLocation);
        }
        currFieldHasLocValAttr = false;
        // currFieldHasLocElmt = false;
        // currFieldHasMapElmt = false;
        String field = null;
        ActivityFieldDataType dataType = null;
        String locatorType = null;
        String locator = null;
        String valueType = null;
        String separator = null;
        String pattern = null;
        String units = null;
        String format = null;
        String locale = null;
        String timeZone = null;
        String value = null;
        int radix = 10;
        String reqVal = "";
        boolean transparent = false;
        boolean split = false;
        String id = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                field = attValue;
            } else if (DATA_TYPE_ATTR.equals(attName)) {
                dataType = ActivityFieldDataType.valueOf(attValue);
            } else if (LOC_TYPE_ATTR.equals(attName)) {
                locatorType = attValue;
            } else if (LOCATOR_ATTR.equals(attName)) {
                locator = attValue;
            } else if (VALUE_TYPE_ATTR.equals(attName)) {
                valueType = attValue;
            } else if (SEPARATOR_ATTR.equals(attName)) {
                separator = attValue;
            } else if (FORMATTING_PATTERN_ATTR.equals(attName))
                pattern = attValue;
            else if (RADIX_ATTR.equals(attName)) {
                radix = Integer.parseInt(attValue);
            } else if (UNITS_ATTR.equals(attName)) {
                units = attValue;
            } else if (FORMAT_ATTR.equals(attName)) {
                format = attValue;
            } else if (LOCALE_ATTR.equals(attName)) {
                locale = attValue;
            } else if (TIMEZONE_ATTR.equals(attName)) {
                timeZone = attValue;
            } else if (VALUE_ATTR.equals(attName)) {
                value = attValue;
            } else if (REQUIRED_ATTR.equals(attName)) {
                reqVal = attValue;
            } else if (TRANSPARENT_ATTR.equals(attName)) {
                transparent = Boolean.parseBoolean(attValue);
            } else if (SPLIT_ATTR.equals(attName)) {
                split = Boolean.parseBoolean(attValue);
            } else if (ID_ATTR.equals(attName)) {
                id = attValue;
            }
        }
        // make sure required fields are present
        notEmpty(field, FIELD_ELMT, NAME_ATTR);

        if (separator != null && pattern != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.cannot.contain", FIELD_ELMT, SEPARATOR_ATTR, FORMATTING_PATTERN_ATTR),
                    currParseLocation);
        }

        ActivityFieldLocator afl;
        ActivityField af = new ActivityField(field);

        if (value != null) {
            currFieldHasLocValAttr = true;
            afl = new ActivityFieldLocator(value);
            afl.setRadix(radix);
            afl.setRequired(reqVal);
            if (dataType != null) {
                afl.setDataType(dataType);
            }
            if (StringUtils.isNotEmpty(units)) {
                afl.setUnits(units);
            }
            if (StringUtils.isNotEmpty(format)) {
                afl.setFormat(format, locale);
            }
            if (StringUtils.isNotEmpty(timeZone)) {
                afl.setTimeZone(timeZone);
            }
            if (StringUtils.isNotEmpty(id)) {
                afl.setId(id);
            }
            af.addLocator(afl);
        } else if (StringUtils.isNotEmpty(locator)) {
            currFieldHasLocValAttr = true;
            String[] locators = currParser.canHaveDelimitedLocators() ? locator.split(Pattern.quote(LOC_DELIM))
                    : new String[] { locator };
            for (String loc : locators) {
                if (StringUtils.isNotEmpty(loc)) {
                    afl = new ActivityFieldLocator(locatorType, loc);
                    afl.setRadix(radix);
                    afl.setRequired(reqVal);
                    if (dataType != null) {
                        afl.setDataType(dataType);
                    }
                    if (StringUtils.isNotEmpty(units)) {
                        afl.setUnits(units);
                    }
                    if (StringUtils.isNotEmpty(format)) {
                        afl.setFormat(format, locale);
                    }
                    if (StringUtils.isNotEmpty(timeZone)) {
                        afl.setTimeZone(timeZone);
                    }
                    if (StringUtils.isNotEmpty(id)) {
                        afl.setId(id);
                    }
                    af.addLocator(afl);
                }
            }
        } else if (StringUtils.isEmpty(locator)) {
            af.setGroupLocator(radix, reqVal, dataType, units, format, locale, timeZone);
        }

        if (separator != null) {
            af.setSeparator(separator);
        }
        if (StringUtils.isNotEmpty(pattern)) {
            af.setFormattingPattern(pattern);
        }
        if (StringUtils.isNotEmpty(valueType)) {
            af.setValueType(valueType);
        }
        af.setRequired(reqVal);
        af.setTransparent(transparent);
        af.setSplitCollection(split);
        currField = af;
    }

    /**
     * Checks if attribute resolved {@link String} value is not empty.
     *
     * @param attrValue
     *            attribute resolved value
     * @param elemName
     *            element name
     * @param attrName
     *            attribute name
     * @throws SAXParseException
     *             if attribute resolved {@link String} value is {@code null} or {@code ""}
     */
    protected void notEmpty(String attrValue, String elemName, String attrName) throws SAXParseException {
        if (StringUtils.isEmpty(attrValue)) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.missing.attribute", elemName, attrName), currParseLocation);
        }
    }

    /**
     * Checks if element resolved {@link String} value is not empty.
     *
     * @param attrValue
     *            attribute resolved value
     * @param elemName
     *            element name
     * @throws SAXParseException
     *             if attribute resolved {@link String} value is {@code null} or {@code ""}
     */
    protected void notEmpty(String attrValue, String elemName) throws SAXParseException {
        if (StringUtils.isEmpty(attrValue)) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.missing.element", elemName), currParseLocation);
        }
    }

    /**
     * Checks if attribute resolved value is not {@code null}.
     *
     * @param attrValue
     *            attribute resolved value
     * @param elemName
     *            element name
     * @param attrName
     *            attribute name
     *
     * @throws SAXParseException
     *             if attribute resolved value is {@code null}
     */
    protected void notNull(Object attrValue, String elemName, String attrName) throws SAXParseException {
        if (attrValue == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.missing.attribute", elemName, attrName), currParseLocation);
        }
    }

    /**
     * Processes a {@code <embedded-activity>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processEmbeddedActivity(Attributes attrs) throws SAXException {
        if (currField != null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration5", EMBEDDED_ACTIVITY_ELMT, FIELD_ELMT),
                    currParseLocation);
        }
        if (currParser == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", EMBEDDED_ACTIVITY_ELMT, PARSER_ELMT),
                    currParseLocation);
        }
        String field = null;
        String locatorType = null;
        String locator = null;
        String reqVal = "";
        boolean transparent = true;
        boolean split = true;
        String id = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                field = attValue;
            } else if (LOC_TYPE_ATTR.equals(attName)) {
                locatorType = attValue;
            } else if (LOCATOR_ATTR.equals(attName)) {
                locator = attValue;
            } else if (REQUIRED_ATTR.equals(attName)) {
                reqVal = attValue;
            } else if (ID_ATTR.equals(attName)) {
                id = attValue;
            }
        }
        // make sure required fields are present
        notEmpty(field, EMBEDDED_ACTIVITY_ELMT, NAME_ATTR);
        notEmpty(locator, EMBEDDED_ACTIVITY_ELMT, LOCATOR_ATTR);
        notEmpty(locatorType, EMBEDDED_ACTIVITY_ELMT, LOC_TYPE_ATTR);

        ActivityFieldLocator afl;
        ActivityField af = new ActivityField(field);

        String[] locators = currParser.canHaveDelimitedLocators() ? locator.split(Pattern.quote(LOC_DELIM))
                : new String[] { locator };
        for (String loc : locators) {
            if (StringUtils.isNotEmpty(loc)) {
                afl = new ActivityFieldLocator(locatorType, loc);
                afl.setRequired(reqVal);
                if (StringUtils.isNotEmpty(id)) {
                    afl.setId(id);
                }
                af.addLocator(afl);
            }
        }

        af.setRequired(reqVal);
        af.setTransparent(transparent);
        af.setSplitCollection(split);
        currField = af;
    }

    /**
     * Processes a {@code <field-locator>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processFieldLocator(Attributes attrs) throws SAXException {
        if (currLocatorData != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", FIELD_LOC_ELMT), currParseLocation);
        }
        if (currField == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration3", FIELD_LOC_ELMT, FIELD_ELMT,
                    EMBEDDED_ACTIVITY_ELMT), currParseLocation);
        }
        if (!currField.hasDynamicAttrs() && currFieldHasLocValAttr) {
            throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.element.has.both", FIELD_ELMT, LOCATOR_ATTR, VALUE_ATTR, FIELD_LOC_ELMT,
                    getLocationInfo()));
        }
        // if (currFieldHasMapElmt) {
        // throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
        // "ConfigParserHandler.element.has.both2", FIELD_ELMT, FIELD_LOC_ELMT, FIELD_MAP_ELMT,
        // getLocationInfo()));
        // }

        currLocatorData = new FieldLocatorData();

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (DATA_TYPE_ATTR.equals(attName)) {
                currLocatorData.dataType = ActivityFieldDataType.valueOf(attValue);
            } else if (LOC_TYPE_ATTR.equals(attName)) {
                currLocatorData.locatorType = attValue;
            } else if (LOCATOR_ATTR.equals(attName)) {
                currLocatorData.locator = attValue;
            } else if (RADIX_ATTR.equals(attName)) {
                currLocatorData.radix = Integer.parseInt(attValue);
            } else if (UNITS_ATTR.equals(attName)) {
                currLocatorData.units = attValue;
            } else if (FORMAT_ATTR.equals(attName)) {
                currLocatorData.format = attValue;
            } else if (LOCALE_ATTR.equals(attName)) {
                currLocatorData.locale = attValue;
            } else if (TIMEZONE_ATTR.equals(attName)) {
                currLocatorData.timeZone = attValue;
            } else if (VALUE_ATTR.equals(attName)) {
                currLocatorData.value = attValue;
            } else if (REQUIRED_ATTR.equals(attName)) {
                currLocatorData.reqVal = attValue;
            } else if (ID_ATTR.equals(attName)) {
                currLocatorData.id = attValue;
            }
        }

        if (currLocatorData.value != null && currLocatorData.value.isEmpty()) {
            currLocatorData.value = null;
        }

        // make sure any fields that are required based on other fields are
        // specified
        if (ActivityFieldDataType.DateTime == currLocatorData.dataType) {
            if (currLocatorData.format == null) {
                throw new SAXParseException(StreamsResources.getStringFormatted(
                        StreamsResources.RESOURCE_BUNDLE_NAME, "ConfigParserHandler.missing.attribute2",
                        FIELD_LOC_ELMT, FORMAT_ATTR, currLocatorData.dataType), currParseLocation);
            }
            // if (locale == null)
            // {
            //
            // }
        } else if (ActivityFieldDataType.Timestamp == currLocatorData.dataType) {
            if (currLocatorData.units == null) {
                throw new SAXParseException(StreamsResources.getStringFormatted(
                        StreamsResources.RESOURCE_BUNDLE_NAME, "ConfigParserHandler.missing.attribute2",
                        FIELD_LOC_ELMT, UNITS_ATTR, currLocatorData.dataType), currParseLocation);
            }
        }
        // currFieldHasLocElmt = true;

        elementData = new StringBuilder();
    }

    /**
     * Processes a {@code <field-map>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processFieldMap(Attributes attrs) throws SAXException {
        if (currField == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration3", FIELD_MAP_ELMT, FIELD_ELMT, FIELD_LOC_ELMT),
                    currParseLocation);
        }
        // if (currFieldHasLocElmt && currLocatorData == null) {
        // throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
        // "ConfigParserHandler.element.has.both2", FIELD_ELMT, FIELD_LOC_ELMT, FIELD_MAP_ELMT,
        // getLocationInfo()));
        // }
        if (CollectionUtils.isEmpty(currField.getLocators()) && currLocatorData == null) {
            throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.element.no.binding", FIELD_MAP_ELMT, FIELD_LOC_ELMT, getLocationInfo()));
        }
        String source = null;
        String target = null;
        String type = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (SOURCE_ATTR.equals(attName)) {
                source = attValue;
            } else if (TARGET_ATTR.equals(attName)) {
                target = attValue;
            } else if (TYPE_ATTR.equals(attName)) {
                type = attValue;
            }
        }
        notNull(source, FIELD_MAP_ELMT, SOURCE_ATTR);
        notNull(target, FIELD_MAP_ELMT, TARGET_ATTR);

        if (currLocatorData != null) {
            currLocatorData.valueMapItems.add(new FieldLocatorData.ValueMapData(source, target,
                    StringUtils.isEmpty(type) ? null : ActivityFieldMappingType.valueOf(type)));
        } else {
            // currFieldHasMapElmt = true;
            List<ActivityFieldLocator> locators = currField.getLocators();
            if (locators != null) {
                for (ActivityFieldLocator loc : locators) {
                    loc.addValueMap(source, target,
                            StringUtils.isEmpty(type) ? null : ActivityFieldMappingType.valueOf(type));
                }
            }
        }
    }

    /**
     * Processes a {@code <field-map-ref>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    @SuppressWarnings("unchecked")
    private void processFieldMapReference(Attributes attrs) throws SAXException {
        if (currField == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration3", FIELD_MAP_REF_ELMT, FIELD_ELMT, FIELD_LOC_ELMT),
                    currParseLocation);
        }

        if (CollectionUtils.isEmpty(currField.getLocators()) && currLocatorData == null) {
            throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.element.no.binding", FIELD_MAP_REF_ELMT, FIELD_LOC_ELMT,
                    getLocationInfo()));
        }

        String reference = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (RESOURCE_ATTR.equals(attName)) {
                reference = attValue;
            }
        }

        notEmpty(reference, FIELD_MAP_REF_ELMT, RESOURCE_ATTR);

        Object val = Utils.getMapValueByPath(reference, resourcesMap);

        if (val instanceof Map) {
            AttributesImpl mappingAttrs = new AttributesImpl();
            for (Map.Entry<String, ?> entry : ((Map<String, ?>) val).entrySet()) {
                mappingAttrs.clear();
                mappingAttrs.addAttribute(null, null, SOURCE_ATTR, null, entry.getKey());
                mappingAttrs.addAttribute(null, null, TARGET_ATTR, null, String.valueOf(entry.getValue()));

                processFieldMap(mappingAttrs);
            }
        }
    }

    /**
     * Processes a {@code <resource-ref>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processResourceReference(Attributes attrs) throws SAXException {
        String id = null;
        String type = null;
        String uri = null;
        String delim = null;

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (ID_ATTR.equals(attName)) {
                id = attValue;
            } else if (TYPE_ATTR.equals(attName)) {
                type = attValue;
            } else if (URI_ATTR.equals(attName)) {
                uri = attValue;
            } else if (SEPARATOR_ATTR.equals(attName)) {
                delim = attValue;
            }
        }

        notEmpty(id, RESOURCE_REF_ELMT, ID_ATTR);
        notEmpty(type, RESOURCE_REF_ELMT, TYPE_ATTR);
        notEmpty(uri, RESOURCE_REF_ELMT, URI_ATTR);

        if (type.equals(ResourceReferenceType.VALUES_MAP.value())) {
            try {
                if (resourcesMap == null) {
                    resourcesMap = new HashMap<>(5);
                }

                InputStream is;

                try {
                    URL url = new URL(uri);
                    is = url.openStream();
                } catch (MalformedURLException exc) {
                    is = new FileInputStream(uri);
                }

                if (uri.toLowerCase().endsWith(".json")) { // NON-NLS
                    resourcesMap.put(id, Utils.fromJsonToMap(is, false));
                } else if (uri.toLowerCase().endsWith(".csv")) { // NON-NLS
                    resourcesMap.put(id, fromPropsToMap(is, StringUtils.isEmpty(delim) ? "," : delim)); // NON-NLS
                } else if (uri.toLowerCase().endsWith(".properties")) { // NON-NLS
                    resourcesMap.put(id, fromPropsToMap(is, StringUtils.isEmpty(delim) ? "=" : delim)); // NON-NLS
                } else {
                    throw new SAXParseException(
                            StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                                    "ConfigParserHandler.invalidResource", id, uri),
                            currParseLocation);
                }
                Utils.close(is);
            } catch (Exception e) {
                throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                        "ConfigParserHandler.jsonParseException", id, uri), e);
            }
        } else {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.invalidRefType", id, type), currParseLocation);
        }
    }

    private static Map<String, ?> fromPropsToMap(InputStream is, String delim) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        Map<String, String> propsMap = new HashMap<>(10);

        String line;
        while ((line = br.readLine()) != null) {
            String[] tokens = line.split(delim); // StringUtils.split (line, delim);

            String key = tokens.length > 0 ? tokens[0] : "";
            String value = tokens.length > 1 ? tokens[1] : "";

            propsMap.put(tokens[0].trim(), tokens[1].trim());
        }

        return propsMap;
    }

    /**
     * Processes a {@code <field-transform>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processFieldTransform(Attributes attrs) throws SAXException {
        if (currTransform != null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration", FIELD_TRANSFORM_ELMT),
                    currParseLocation);
        }

        if (currField == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration4", FIELD_TRANSFORM_ELMT, FIELD_ELMT,
                    EMBEDDED_ACTIVITY_ELMT, FIELD_LOC_ELMT), currParseLocation);
        }

        if (currTransform == null) {
            currTransform = new FieldTransformData();
        }

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                currTransform.name = attValue;
            } else if (BEAN_REF_ATTR.equals(attName)) {
                currTransform.beanRef = attValue;
            } else if (LANG_ATTR.equals(attName)) {
                currTransform.scriptLang = attValue;
            }
        }

        if (StringUtils.isNoneEmpty(currTransform.beanRef, currTransform.scriptLang)) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.cannot.contain", FIELD_TRANSFORM_ELMT, BEAN_REF_ATTR, LANG_ATTR),
                    currParseLocation);
        }

        handleFieldLocatorCDATA();

        elementData = new StringBuilder();
    }

    /**
     * Processes a {@code <stream>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processStream(Attributes attrs) throws SAXException {
        if (currStream != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", STREAM_ELMT), currParseLocation);
        }
        String name = null;
        String className = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                name = attValue;
            } else if (CLASS_ATTR.equals(attName)) {
                className = attValue;
            }
        }
        notEmpty(name, STREAM_ELMT, NAME_ATTR);
        notEmpty(className, STREAM_ELMT, CLASS_ATTR);
        if (streamsConfigData.getStream(name) != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.duplicate", STREAM_ELMT, name), currParseLocation);
        }
        try {
            Object newStream = Utils.createInstance(className);
            if (!(newStream instanceof TNTInputStream)) {
                throw new SAXNotSupportedException(StreamsResources.getStringFormatted(
                        StreamsResources.RESOURCE_BUNDLE_NAME, "ConfigParserHandler.not.extend.class", STREAM_ELMT,
                        CLASS_ATTR, className, TNTInputStream.class.getName(), getLocationInfo()));
            }
            currStream = (TNTInputStream<?, ?>) newStream;
        } catch (Exception exc) {
            throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.failed.to.load", STREAM_ELMT, CLASS_ATTR, className, getLocationInfo()),
                    exc);
        }

        currStream.setName(name);
        streamsConfigData.addStream(currStream);

        currStream.ensureOutputSet();
    }

    /**
     * Processes a {@code <property>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processProperty(Attributes attrs) throws SAXException {
        if (currStream == null && currParser == null && javaObjectData == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration4", PROPERTY_ELMT, STREAM_ELMT, PARSER_ELMT,
                    JAVA_OBJ_ELMT), currParseLocation);
        }

        if (currProperty == null) {
            currProperty = new Property();
        } else {
            currProperty.reset();
        }
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                currProperty.name = attValue;
            } else if (VALUE_ATTR.equals(attName)) {
                currProperty.value = attValue;
            }
        }

        notEmpty(currProperty.name, PROPERTY_ELMT, NAME_ATTR);

        elementData = new StringBuilder();
    }

    /**
     * Processes a {@code <parser-ref>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs element
     */
    private void processParserRef(Attributes attrs) throws SAXException {
        if (currField == null && currStream == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration4", PARSER_REF_ELMT, FIELD_ELMT,
                    EMBEDDED_ACTIVITY_ELMT, STREAM_ELMT), currParseLocation);
        }
        String parserName = null;
        String aggregationType = null;

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                parserName = attValue;
            } else if (AGGREGATION_ATTR.equals(attName)) {
                aggregationType = attValue;
            }
        }

        notEmpty(parserName, PARSER_REF_ELMT, NAME_ATTR);

        ActivityParser parser = streamsConfigData.getParser(parserName);
        if (parser == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.undefined.reference", PARSER_REF_ELMT, parserName),
                    currParseLocation);
        }

        if (currField != null) {
            currField.addStackedParser(parser, aggregationType);
        } else {
            try {
                currStream.addReference(parser);
            } catch (IllegalStateException exc) {
                throw new SAXParseException(StreamsResources.getStringFormatted(
                        StreamsResources.RESOURCE_BUNDLE_NAME, "ConfigParserHandler.could.not.add.stream.parser",
                        currStream.getName(), parserName), currParseLocation, exc);
            }
        }
    }

    /**
     * Processes a {@code <reference>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processReference(Attributes attrs) throws SAXException {
        if (currStream == null && currParser == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration3", REF_ELMT, STREAM_ELMT, PARSER_ELMT),
                    currParseLocation);
        }

        if (currStream != null) {
            processStreamReference(attrs);
        }

        if (currParser != null) {
            processParserReference(attrs);
        }
    }

    /**
     * Process parser {@code <reference>} element, e.g., pre-parser.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXParseException
     *             if error occurs parsing element
     */
    private void processParserReference(Attributes attrs) throws SAXParseException {
        String refObjName = getRefObjectNameFromAttr(attrs);
        notEmpty(refObjName, REF_ELMT, NAME_ATTR);
        Object refObject = findReference(refObjName);
        try {
            currParser.addReference(refObject);
        } catch (IllegalStateException exc) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.could.not.add.stream.reference", currStream.getName(), refObjName),
                    currParseLocation, exc);
        }

    }

    /**
     * Process stream {@code <reference>} element, e.g., parser, output, etc.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXParseException
     *             if error occurs parsing element
     */
    private void processStreamReference(Attributes attrs) throws SAXParseException {
        String refObjName = getRefObjectNameFromAttr(attrs);
        notEmpty(refObjName, REF_ELMT, NAME_ATTR);
        Object refObject = findReference(refObjName);
        if (refObject == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.undefined.reference", REF_ELMT, refObjName), currParseLocation);
        }

        try {
            currStream.addReference(refObject);
        } catch (IllegalStateException exc) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.could.not.add.stream.reference", currStream.getName(), refObjName),
                    currParseLocation, exc);
        }
    }

    private String getRefObjectNameFromAttr(Attributes attrs) {
        String refObjName = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                refObjName = attValue;
            }
        }
        return refObjName;
    }

    private Object findReference(String refName) {
        Object refObject = streamsConfigData == null ? null : streamsConfigData.getParser(refName);
        if (refObject == null) {
            refObject = streamsConfigData == null ? null : streamsConfigData.getStream(refName);
        }
        if (refObject == null) {
            refObject = javaObjectsMap == null ? null : javaObjectsMap.get(refName);
        }

        return refObject;
    }

    /**
     * Processes a {@code <java-object>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processJavaObject(Attributes attrs) throws SAXException {
        if (javaObjectData == null) {
            javaObjectData = new JavaObjectData();
        }

        String name = null;
        String className = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                name = attValue;
            } else if (CLASS_ATTR.equals(attName)) {
                className = attValue;
            }
        }

        notEmpty(name, JAVA_OBJ_ELMT, NAME_ATTR);
        notEmpty(className, JAVA_OBJ_ELMT, CLASS_ATTR);

        javaObjectData.name = name;
        javaObjectData.className = className;
    }

    /**
     * Processes a {@code <param>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processParam(Attributes attrs) throws SAXException {
        if (javaObjectData == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", PARAM_ELMT, JAVA_OBJ_ELMT),
                    currParseLocation);
        }

        String name = null;
        String value = null;
        String type = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                name = attValue;
            } else if (VALUE_ATTR.equals(attName)) {
                value = attValue;
            } else if (TYPE_ATTR.equals(attName)) {
                type = attValue;
            }
        }

        notEmpty(name, PARAM_ELMT, NAME_ATTR);
        notEmpty(type, PARAM_ELMT, TYPE_ATTR);

        try {
            Object obj;
            if (StringUtils.isEmpty(value)) {
                obj = Utils.createInstance(type);
            } else {
                obj = javaObjectsMap.get(value);
                if (obj == null) {
                    obj = Utils.createInstance(type, new Object[] { value }, String.class);
                }
            }
            javaObjectData.addArg(obj);
            javaObjectData.addType(type);
        } catch (Exception exc) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.could.not.init.obj.param", javaObjectData.name, name),
                    currParseLocation, exc);
        }
    }

    /**
     * Processes a {@code <filter>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processFilter(Attributes attrs) throws SAXException {
        if (currFilter != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", FILTER_ELMT), currParseLocation);
        }

        if (currLocatorData == null && currField == null && currParser == null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration4", FILTER_ELMT, FIELD_LOC_ELMT, FIELD_ELMT,
                    EMBEDDED_ACTIVITY_ELMT, PARSER_ELMT), currParseLocation);
        }

        String name = null;
        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (NAME_ATTR.equals(attName)) {
                name = attValue;
            }
        }

        // make sure required fields are present
        notEmpty(name, FILTER_ELMT, NAME_ATTR);

        if (currLocatorData != null || currField != null) {
            currFilter = new StreamFiltersGroup<>(name);
        } else if (currParser != null) {
            currFilter = new StreamFiltersGroup<>(name);
        }

        handleFieldLocatorCDATA();
    }

    /**
     * Processes a {@code <value>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processValue(Attributes attrs) throws SAXException {
        if (processingCache) {
            processCacheValue(attrs);
        } else {
            processFilterValue(attrs);
        }
    }

    /**
     * Processes a {@code <value>} element under <filter> element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processFilterValue(Attributes attrs) throws SAXException {
        if (currFilterValue != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", VALUE_ELMT), currParseLocation);
        }

        if (currFilter == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", VALUE_ELMT, FILTER_ELMT),
                    currParseLocation);
        }

        if (currFilterValue == null) {
            currFilterValue = new FilterValueData();
        }

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (HANDLE_ATTR.equals(attName)) {
                currFilterValue.handle = attValue;
            } else if (EVALUATION_ATTR.equals(attName)) {
                currFilterValue.evaluation = attValue;
            } else if (FORMAT_ATTR.equals(attName)) {
                currFilterValue.format = attValue;
            }
        }

        elementData = new StringBuilder();
    }

    /**
     * Processes a {@code <value>} element under <cache> element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processCacheValue(Attributes attrs) throws SAXException {
        if (currCacheEntry == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", VALUE_ELMT, CACHE_ENTRY_ELMT),
                    currParseLocation);
        }

        elementData = new StringBuilder();
    }

    /**
     * Processes a {@code <expression>} element.
     *
     * @param attrs
     *            List of element attributes
     *
     * @throws SAXException
     *             if error occurs parsing element
     */
    private void processFilterExpression(Attributes attrs) throws SAXException {
        if (currFilterExpression != null) {
            throw new SAXParseException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.malformed.configuration", EXPRESSION_ELMT), currParseLocation);
        }

        if (currFilter == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", EXPRESSION_ELMT, FILTER_ELMT),
                    currParseLocation);
        }

        if (currFilterExpression == null) {
            currFilterExpression = new FilterExpressionData();
        }

        for (int i = 0; i < attrs.getLength(); i++) {
            String attName = attrs.getQName(i);
            String attValue = attrs.getValue(i);
            if (HANDLE_ATTR.equals(attName)) {
                currFilterExpression.handle = attValue;
            } else if (LANG_ATTR.equals(attName)) {
                currFilterExpression.lang = attValue;
            }
        }

        elementData = new StringBuilder();
    }

    private void processTNT4JProperties(Attributes attrs) throws SAXException {
        if (currStream == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.malformed.configuration2", TNT4J_PROPERTIES_ELMT, STREAM_ELMT),
                    currParseLocation);
        }

        processingTNT4JProperties = true;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {

        String cdata = new String(ch, start, length);

        if (this.elementData != null) {
            this.elementData.append(cdata);
        }
    }

    /**
     * Returns string buffer contained string for current configuration element (token).
     *
     * @return configuration element (token) data string value, or {@code null} if no element data
     */
    protected String getElementData() {
        return elementData == null ? null : elementData.toString().trim();
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        try {
            if (STREAM_ELMT.equals(qName)) {
                if (currProperties != null) {
                    currStream.setProperties(currProperties.entrySet());
                    currProperties.clear();
                }
                currStream = null;
            } else if (PARSER_ELMT.equals(qName)) {
                if (currProperties != null) {
                    currParser.setProperties(currProperties.entrySet());
                    currProperties.clear();
                }
                currParser = null;
            } else if (FIELD_ELMT.equals(qName) || EMBEDDED_ACTIVITY_ELMT.equals(qName)) {
                validateActivityField(currField, qName);
                currParser.addField(currField);
                currField = null;
                currFieldHasLocValAttr = false;
                // currFieldHasLocElmt = false;
                // currFieldHasMapElmt = false;
            } else if (FIELD_LOC_ELMT.equals(qName)) {
                if (currLocatorData != null) {
                    handleFieldLocator(currLocatorData);

                    currLocatorData = null;
                    elementData = null;
                }
            } else if (TNT4J_PROPERTIES_ELMT.equals(qName)) {
                processingTNT4JProperties = false;
            } else if (JAVA_OBJ_ELMT.equals(qName)) {
                if (javaObjectData != null) {
                    handleJavaObject(javaObjectData);

                    javaObjectData.reset();
                }
            } else if (PROPERTY_ELMT.equals(qName)) {
                if (currProperty != null) {
                    handleProperty(currProperty);

                    currProperty.reset();
                    elementData = null;
                }
            } else if (FIELD_TRANSFORM_ELMT.equals(qName)) {
                if (currTransform != null) {
                    handleFieldTransform(currTransform);

                    currTransform = null;
                    elementData = null;
                }
            } else if (FILTER_ELMT.equals(qName)) {
                if (currFilter != null) {
                    handleFilter(currFilter);

                    currFilter = null;
                }
            } else if (VALUE_ELMT.equals(qName)) {
                if (currFilterValue != null) {
                    handleFilterValue(currFilterValue);

                    currFilterValue = null;
                    elementData = null;
                } else if (currCacheEntry != null) {
                    handleValue(currCacheEntry);

                    elementData = null;
                }
            } else if (EXPRESSION_ELMT.equals(qName)) {
                if (currFilterExpression != null) {
                    handleFilterExpression(currFilterExpression);

                    currFilterExpression = null;
                    elementData = null;
                }
            } else if (CACHE_ELMT.equals(qName)) {
                processingCache = false;
            } else if (CACHE_ENTRY_ELMT.equals(qName)) {
                if (currCacheEntry != null) {
                    handleCacheEntry(currCacheEntry);

                    currCacheEntry = null;
                }
            } else if (CACHE_KEY_ELMT.equals(qName)) {
                if (currCacheEntry != null) {
                    handleKey(currCacheEntry);

                    elementData = null;
                }
            }
        } catch (SAXException exc) {
            throw exc;
        } catch (Exception e) {
            throw new SAXException(e.getLocalizedMessage() + getLocationInfo(), e);
        }
    }

    private void handleJavaObject(JavaObjectData javaObjectData) throws Exception {
        if (javaObjectData == null) {
            return;
        }

        Object obj = Utils.createInstance(javaObjectData.className, javaObjectData.getArgs(),
                javaObjectData.getTypes());

        javaObjectsMap.put(javaObjectData.name, obj);

        if (MapUtils.isNotEmpty(currProperties)) {
            if (obj instanceof TNTStreamOutput) {
                ((TNTStreamOutput<?>) obj).setProperties(currProperties.entrySet());
            }

            currProperties.clear();
        }
    }

    private void handleProperty(Property currProperty) throws SAXException {
        String eDataVal = getElementData();
        if (eDataVal != null) {
            if (currProperty.value != null && eDataVal.length() > 0) {
                throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                        "ConfigParserHandler.element.has.both3", PROPERTY_ELMT, VALUE_ATTR, getLocationInfo()));
            } else if (currProperty.value == null) {
                currProperty.value = eDataVal;
            }
        }

        notNull(currProperty.value, PROPERTY_ELMT, VALUE_ATTR);

        if (processingTNT4JProperties) {
            Map.Entry<String, String> p = new AbstractMap.SimpleEntry<>(currProperty.name, currProperty.value);
            currStream.output().setProperty(OutputProperties.PROP_TNT4J_PROPERTY, p);
        } else {
            if (currProperties == null) {
                currProperties = new HashMap<>();
            }
            currProperties.put(currProperty.name, currProperty.value);
        }
    }

    private void handleFieldLocator(FieldLocatorData currLocatorData) throws SAXException {
        handleFieldLocatorCDATA();

        if (currLocatorData.locator != null && currLocatorData.locator.isEmpty()) {
            currLocatorData.locator = null;
        }

        // make sure common required fields are present
        if (currLocatorData.locator == null && currLocatorData.value == null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.must.contain", FIELD_LOC_ELMT, LOCATOR_ATTR, VALUE_ATTR),
                    currParseLocation);
        }
        if (currLocatorData.locator != null && currLocatorData.value != null) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.cannot.contain", FIELD_LOC_ELMT, LOCATOR_ATTR, VALUE_ATTR),
                    currParseLocation);
        }

        ActivityFieldLocator afl = currLocatorData.value != null ? new ActivityFieldLocator(currLocatorData.value)
                : new ActivityFieldLocator(currLocatorData.locatorType, currLocatorData.locator);
        afl.setRadix(currLocatorData.radix);
        afl.setRequired(currLocatorData.reqVal);
        afl.setId(currLocatorData.id);
        if (currLocatorData.format != null) {
            afl.setFormat(currLocatorData.format, currLocatorData.locale);
        }
        if (currLocatorData.dataType != null) {
            afl.setDataType(currLocatorData.dataType);
        }
        if (currLocatorData.units != null) {
            afl.setUnits(currLocatorData.units);
        }
        if (currLocatorData.timeZone != null) {
            afl.setTimeZone(currLocatorData.timeZone);
        }

        if (CollectionUtils.isNotEmpty(currLocatorData.valueMapItems)) {
            for (FieldLocatorData.ValueMapData vmd : currLocatorData.valueMapItems) {
                afl.addValueMap(vmd.source, vmd.target, vmd.mapTyp);
            }
        }

        if (CollectionUtils.isNotEmpty(currLocatorData.valueTransforms)) {
            for (ValueTransformation<Object, Object> vt : currLocatorData.valueTransforms) {
                afl.addTransformation(vt);
            }
        }

        if (currLocatorData.filter != null) {
            afl.setFilter(currLocatorData.filter);
        }

        currField.addLocator(afl);
    }

    private void handleFieldLocatorCDATA() throws SAXException {
        if (currLocatorData == null) {
            return;
        }

        String eDataVal = getElementData();

        if (StringUtils.isNotEmpty(eDataVal)) {
            if (StringUtils.isNotEmpty(currLocatorData.locator) && eDataVal.length() > 0) {
                throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                        "ConfigParserHandler.element.has.both3", FIELD_LOC_ELMT, LOCATOR_ATTR, getLocationInfo()));
            }

            currLocatorData.locator = eDataVal;
        }
    }

    @SuppressWarnings("unchecked")
    private void handleFieldTransform(FieldTransformData currTransformData) throws SAXException {
        String eDataVal = getElementData();

        if (eDataVal != null) {
            if (StringUtils.isNotEmpty(currTransformData.beanRef) && eDataVal.length() > 0) {
                throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                        "ConfigParserHandler.element.has.both3", FIELD_TRANSFORM_ELMT, BEAN_REF_ATTR,
                        getLocationInfo()));
            } else if (currTransformData.scriptCode == null) {
                currTransformData.scriptCode = eDataVal;
            }
        }

        if (StringUtils.isEmpty(currTransformData.beanRef) && StringUtils.isEmpty(currTransformData.scriptCode)) {
            throw new SAXParseException(
                    StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                            "ConfigParserHandler.must.contain", FIELD_TRANSFORM_ELMT, BEAN_REF_ATTR, CDATA),
                    currParseLocation);
        }

        ValueTransformation<Object, Object> transform;

        if (StringUtils.isNotEmpty(currTransformData.beanRef)) {
            Object tObj = javaObjectsMap.get(currTransformData.beanRef);

            if (tObj == null) {
                throw new SAXParseException(StreamsResources.getStringFormatted(
                        StreamsResources.RESOURCE_BUNDLE_NAME, "ConfigParserHandler.undefined.reference",
                        FIELD_TRANSFORM_ELMT, currTransformData.beanRef), currParseLocation);
            }

            if (tObj instanceof ValueTransformation) {
                transform = (ValueTransformation<Object, Object>) tObj;
            } else {
                throw new SAXNotSupportedException(
                        StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                                "ConfigParserHandler.not.extend.class", FIELD_TRANSFORM_ELMT, BEAN_REF_ATTR,
                                tObj.getClass().getName(), ValueTransformation.class.getName(), getLocationInfo()));
            }
        } else {
            transform = AbstractScriptTransformation.createScriptTransformation(currTransformData.name,
                    currTransformData.scriptLang, currTransformData.scriptCode);
        }

        if (currLocatorData != null) {
            currLocatorData.valueTransforms.add(transform);
        } else {
            currField.addTransformation(transform);
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void handleFilter(StreamFiltersGroup currFilter) throws SAXException {
        if (currLocatorData != null) {
            currLocatorData.filter = currFilter;
        } else if (currField != null) {
            currField.setFilter(currFilter);
        } else if (currParser != null) {
            ((GenericActivityParser<?>) currParser).setActivityFilter(currFilter);
        }
    }

    @SuppressWarnings("unchecked")
    private void handleFilterValue(FilterValueData fvData) throws SAXException {
        String eDataVal = getElementData();

        if (eDataVal != null) {
            fvData.value = eDataVal;
        }

        currFilter.addFilter(new DefaultValueFilter(fvData.handle, fvData.evaluation, fvData.format, fvData.value));
    }

    @SuppressWarnings("unchecked")
    private void handleFilterExpression(FilterExpressionData feData) throws SAXException {
        String eDataVal = getElementData();

        if (eDataVal != null) {
            feData.expression = eDataVal;
        }

        if (currLocatorData != null || currField != null) {
            currFilter.addFilter(
                    AbstractExpressionFilter.createExpressionFilter(feData.handle, feData.lang, feData.expression));
        } else if (currParser != null) {
            currFilter.addFilter(AbstractExpressionFilter.createActivityExpressionFilter(feData.handle, feData.lang,
                    feData.expression));
        }
    }

    private void handleCacheEntry(CacheEntryData currCacheEntry) throws SAXException {
        StreamsCache.addEntry(currCacheEntry.id, currCacheEntry.key, currCacheEntry.value);
    }

    private void handleKey(CacheEntryData currCacheEntry) throws SAXException {
        String eDataVal = getElementData();

        if (eDataVal != null) {
            currCacheEntry.key = eDataVal;
        }

        notEmpty(currCacheEntry.key, CACHE_KEY_ELMT);
    }

    private void handleValue(CacheEntryData currCacheEntry) throws SAXException {
        String eDataVal = getElementData();

        if (eDataVal != null) {
            currCacheEntry.value = eDataVal;
        }

        notEmpty(currCacheEntry.value, VALUE_ELMT);
    }

    /**
     * Gets a string representing the current line in the file being parsed. Used for error messages.
     *
     * @return string representing current line number being parsed
     */
    protected String getLocationInfo() {
        String locInfo = "";
        if (currParseLocation != null) {
            locInfo = StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.at.line", currParseLocation.getLineNumber());
        }
        return locInfo;
    }

    private void validateActivityField(ActivityField aField, String qName) throws SAXException {
        if (CollectionUtils.isEmpty(aField.getLocators())) {
            throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                    "ConfigParserHandler.element.must.have", qName, LOCATOR_ATTR, VALUE_ATTR, FIELD_LOC_ELMT,
                    getLocationInfo()));
        }

        List<String> dynamicLocators = new ArrayList<>();
        Utils.resolveCfgVariables(dynamicLocators, aField.getFieldTypeName(), aField.getValueType());

        for (String dLoc : dynamicLocators) {
            if (!aField.hasDynamicLocator(dLoc)) {
                throw new SAXException(StreamsResources.getStringFormatted(StreamsResources.RESOURCE_BUNDLE_NAME,
                        "ConfigParserHandler.element.ref.missing", qName, FIELD_LOC_ELMT, dLoc, getLocationInfo()));
            }
        }
    }

    private static class Property {
        private String name;
        private String value;

        private Property() {
            reset();
        }

        private void reset() {
            name = null;
            value = null;
        }
    }

    private static class FieldLocatorData {
        ActivityFieldDataType dataType;
        String locatorType;
        String locator;
        String units;
        String format;
        String locale;
        String timeZone;
        String value;
        int radix;
        String reqVal;
        String id;

        List<ValueMapData> valueMapItems;
        List<ValueTransformation<Object, Object>> valueTransforms;
        StreamFiltersGroup<Object> filter;

        private FieldLocatorData() {
            reset();
        }

        private void reset() {
            dataType = null;
            locatorType = null;
            locator = null;
            units = null;
            format = null;
            locale = null;
            timeZone = null;
            value = null;
            radix = 10;
            reqVal = ""; /* string to allow override */
            id = null;

            if (valueMapItems == null) {
                valueMapItems = new ArrayList<>();
            } else {
                valueMapItems.clear();
            }

            if (valueTransforms == null) {
                valueTransforms = new ArrayList<>();
            } else {
                valueTransforms.clear();
            }

            filter = null;
        }

        private static class ValueMapData {
            String source;
            String target;
            ActivityFieldMappingType mapTyp;

            private ValueMapData(String source, String target, ActivityFieldMappingType mapTyp) {
                this.source = source;
                this.target = target;
                this.mapTyp = mapTyp;
            }
        }
    }

    private static class JavaObjectData {
        private String name;
        private String className;
        private List<Object> args;
        private List<Class<?>> types;

        private void addArg(Object arg) {
            if (args == null) {
                args = new ArrayList<>();
            }

            args.add(arg);
        }

        private void addType(String typeClass) throws ClassNotFoundException {
            addType(Class.forName(typeClass));
        }

        private void addType(Class<?> typeClass) {
            if (types == null) {
                types = new ArrayList<>();
            }

            types.add(typeClass);
        }

        private Object[] getArgs() {
            return args == null ? new Object[0] : args.toArray();
        }

        private Class<?>[] getTypes() {
            Class<?>[] typesArray = new Class[types == null ? 0 : types.size()];
            if (types != null) {
                typesArray = types.toArray(typesArray);
            }

            return typesArray;
        }

        private void reset() {
            name = "";
            className = "";
            if (args != null) {
                args.clear();
            }
            if (types != null) {
                types.clear();
            }
        }
    }

    private static class FieldTransformData {
        String name;
        String beanRef;
        String scriptLang;
        String scriptCode;

        private void reset() {
            name = "";
            beanRef = "";
            scriptLang = "";
            scriptCode = "";
        }
    }

    private static class FilterValueData {
        String handle;
        String evaluation;
        String format;
        String value;

        private void reset() {
            handle = "";
            evaluation = "";
            format = "";
            value = "";
        }
    }

    private static class FilterExpressionData {
        String handle;
        String lang;
        String expression;

        private void reset() {
            handle = "";
            lang = "";
            expression = "";
        }
    }

    private static class CacheEntryData {
        String id;
        String key;
        String value;

        private void reset() {
            id = "";
            key = "";
            value = "";
        }
    }
}