com.adaptris.core.services.jdbc.XmlPayloadTranslatorImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.adaptris.core.services.jdbc.XmlPayloadTranslatorImpl.java

Source

/*
 * Copyright 2015 Adaptris Ltd.
 * 
 * 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.adaptris.core.services.jdbc;

import java.io.StringReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;

import org.apache.commons.lang3.BooleanUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.adaptris.annotation.AdvancedConfig;
import com.adaptris.annotation.InputFieldDefault;
import com.adaptris.core.AdaptrisMessage;
import com.adaptris.core.CoreException;
import com.adaptris.core.util.DocumentBuilderFactoryBuilder;
import com.adaptris.core.util.XmlHelper;
import com.adaptris.jdbc.JdbcResultRow;
import com.adaptris.jdbc.JdbcResultSet;
import com.adaptris.util.XmlUtils;

/**
 * Base class for translating ResultSet contents into an XML Payload.
 * 
 * @author lchan
 * 
 */
public abstract class XmlPayloadTranslatorImpl extends ResultSetTranslatorImp {

    protected static final String ELEMENT_NAME_COLUMN = "column-";
    protected static final String ELEMENT_NAME_ROW = "Row";
    protected static final String ELEMENT_NAME_RESULTS = "Results";

    @AdvancedConfig
    private String xmlColumnPrefix = null;
    @AdvancedConfig
    private String xmlColumnRegexp = null;
    @AdvancedConfig
    private String cdataColumnRegexp = null;
    @InputFieldDefault(value = "false")
    private Boolean stripIllegalXmlChars = null;
    private String outputMessageEncoding = null;
    private transient Pattern cdataColumnRegexpPattern = null;

    private transient Pattern xmlColumnRegexpPattern = null;

    public XmlPayloadTranslatorImpl() {
        super();
    }

    @Override
    public void init() throws CoreException {
        super.init();
        if (xmlColumnRegexp != null) {
            xmlColumnRegexpPattern = Pattern.compile(xmlColumnRegexp);
        }
        if (cdataColumnRegexp != null) {
            cdataColumnRegexpPattern = Pattern.compile(cdataColumnRegexp);
        }
    }

    @Override
    public void close() {
        super.close();
        xmlColumnRegexpPattern = null;
        cdataColumnRegexpPattern = null;
    }

    protected List<Element> createListFromResultSet(DocumentBuilder builder, Document doc, JdbcResultSet rs)
            throws SQLException {
        List<Element> results = new ArrayList<Element>();

        List<String> elementNames = new ArrayList<>();
        boolean firstRecord = true;
        for (JdbcResultRow row : rs.getRows()) {
            Element elementRow = doc.createElement(getColumnNameStyle().format(ELEMENT_NAME_ROW));
            // let's go through and build up the element names we need once.
            if (firstRecord) {
                firstRecord = false;
                elementNames = createElementNames(row);
            }
            for (int i = 0; i < row.getFieldCount(); i++) {
                String columnName = row.getFieldName(i);
                String value = toString(row, i);

                Element node = doc.createElement(elementNames.get(i));
                if (isXmlColumn(columnName)) {
                    try {
                        Document xmlColumn = null;
                        xmlColumn = builder.parse(createInputSource(value));
                        node.appendChild(doc.importNode(xmlColumn.getFirstChild(), true));
                    } catch (Exception e) {
                        if (isDisplayColumnErrors()) {
                            log.warn("Failed to parse column {} as an XML Document, treating as text.", columnName);
                            log.trace("Failed to parse column {} as an XML Document", columnName, e);
                        }
                        node.appendChild(createTextNode(doc, value, isCdataColumn(columnName)));
                    }
                } else {
                    node.appendChild(createTextNode(doc, value, isCdataColumn(columnName)));
                }
                elementRow.appendChild(node);
            }
            results.add(elementRow);
        }
        return results;
    }

    private List<String> createElementNames(JdbcResultRow row) throws SQLException {
        List<String> result = new ArrayList<>();
        for (int i = 0; i < row.getFieldCount(); i++) {
            String columnName = row.getFieldName(i);
            // Numbering starts at 0, so make it SQL-alike and start our count at 1.
            String elementName = getColumnNameStyle()
                    .format(XmlHelper.safeElementName(columnName, ELEMENT_NAME_COLUMN + (i + 1)));
            log.trace("Creating element [{}] from column [{}]", elementName, columnName);
            result.add(elementName);
        }
        return result;
    }

    protected static XmlUtils createXmlUtils(AdaptrisMessage msg) {
        XmlUtils xu = null;
        try {
            NamespaceContext ctx = (NamespaceContext) msg.getObjectHeaders()
                    .get(JdbcDataQueryService.KEY_NAMESPACE_CTX);
            DocumentBuilderFactoryBuilder builder = (DocumentBuilderFactoryBuilder) msg.getObjectHeaders()
                    .get(JdbcDataQueryService.KEY_DOCBUILDER_FAC);
            xu = XmlHelper.createXmlUtils(msg, ctx, builder);
        } catch (CoreException e) {
            xu = new XmlUtils();
        }
        return xu;
    }

    protected boolean isXmlColumn(String name) {
        boolean result = false;
        if (getXmlColumnPrefix() != null) {
            if (name.startsWith(xmlColumnPrefix)) {
                result = true;
            }
        }
        if (!result && xmlColumnRegexpPattern != null) {
            result = xmlColumnRegexpPattern.matcher(name).matches();
        }
        return result;
    }

    protected boolean isCdataColumn(String name) {
        boolean result = false;
        if (cdataColumnRegexpPattern != null) {
            result = cdataColumnRegexpPattern.matcher(name).matches();
        }
        return result;
    }

    protected Node createTextNode(Document doc, String value, boolean isCDATA) {
        String munged = stripIllegalXmlChars() ? XmlHelper.stripIllegalXmlCharacters(value) : value;
        if (isCDATA) {
            return doc.createCDATASection(munged);
        }
        return doc.createTextNode(munged);
    }

    protected static InputSource createInputSource(String value) {
        return new InputSource(new StringReader(value));
    }

    /**
     * @return the prefix used to identify columns that should be returned as XML as opposed to text
     */
    public String getXmlColumnPrefix() {
        return xmlColumnPrefix;
    }

    /**
     * Sets the prefix that is used to identify columns that should be treated as XML rather than text.
     * 
     * @param s the prefix; note that this will be a case sensitive match, so if the behaviour of the database / JDBC driver is to
     *          make all columns / labels uppercase, then make sure you use the right case.
     */
    public void setXmlColumnPrefix(String s) {
        xmlColumnPrefix = s;
    }

    public String getXmlColumnRegexp() {
        return xmlColumnRegexp;
    }

    /**
     * Set a regular expression that will be used to check if a column should be treated as XML rather than text.
     * 
     * @param s the regular expression to match that indicates the column is XML, {@link #setXmlColumnPrefix(String)} is always
     *          checked first.
     * @see #setXmlColumnPrefix(String)
     */
    public void setXmlColumnRegexp(String s) {
        xmlColumnRegexp = s;
    }

    public String getOutputMessageEncoding() {
        return outputMessageEncoding;
    }

    /**
     * Set the encoding for the resulting XML document.
     * <p>
     * If not specified the following rules will be applied:
     * </p>
     * <ol>
     * <li>If the {@link com.adaptris.core.AdaptrisMessage#getCharEncoding()} is non-null then that will be used.</li>
     * <li>UTF-8</li>
     * </ol>
     * <p>
     * As a result; the character encoding on the message is always set using {@link com.adaptris.core.AdaptrisMessage#setCharEncoding(String)}.
     * </p>
     * 
     * @param encoding the character
     */
    public void setOutputMessageEncoding(String encoding) {
        outputMessageEncoding = encoding;
    }

    public String getCdataColumnRegexp() {
        return cdataColumnRegexp;
    }

    /**
     * Set the regular expression that will be used to check if a column should be wrapped in a CDATA element.
     * 
     * @param s the regular expression to match that indicates the column should be wrapped in a CDATA element.
     */
    public void setCdataColumnRegexp(String s) {
        this.cdataColumnRegexp = s;
    }

    public Boolean getStripIllegalXmlChars() {
        return stripIllegalXmlChars;
    }

    /**
     * Specify whether or not to strip illegal XML characters from all the data before converting to XML.
     * <p>
     * The following regular expression is used to strip out all invalid XML 1.0 characters :
     * <code>"[^\u0009\r\n\u0020-\uD7FF\uE000-\uFFFD\ud800\udc00-\udbff\udfff]"</code>. Note that not stripping Illegal XML characters
     * can still mean that you get output that looks like XML, but you will not be able to subsequently process it as a DOM object.
     * </p>
     * 
     * @param s true to enable stripping, default is null (false)
     */
    public void setStripIllegalXmlChars(Boolean s) {
        this.stripIllegalXmlChars = s;
    }

    protected boolean stripIllegalXmlChars() {
        return BooleanUtils.toBooleanDefaultIfNull(getStripIllegalXmlChars(), false);
    }

    protected class DocumentWrapper {
        Document document;
        long resultSetCount;

        DocumentWrapper(Document d, long count) {
            resultSetCount = count;
            document = d;
        }
    }
}