com.autonomy.aci.client.services.impl.AbstractStAXProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.autonomy.aci.client.services.impl.AbstractStAXProcessor.java

Source

/*
 * Copyright 2006-2018 Micro Focus International plc.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

package com.autonomy.aci.client.services.impl;

import com.autonomy.aci.client.services.AciErrorException;
import com.autonomy.aci.client.services.ProcessorException;
import com.autonomy.aci.client.services.StAXProcessor;
import com.autonomy.aci.client.transport.AciResponseInputStream;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * Abstract <tt>Processor</tt> that should be used by all processors wanting to process the ACI response via the
 * <a href="http://jcp.org/en/jsr/detail?id=173">JSR173</a> StAX API. Some of the properties that can be set on the
 * {@link XMLInputFactory} are exposed, so that the implementation can be modified to suit the required situation.
 * For example, setting {@link #namespaceAware} and {@link #validating} to <tt>false</tt> should theoretically speed
 * up parsing, especially as ACI responses shouldn't require either to be parsed, (both are <tt>false</tt> by default
 * for this very reason). When a subclass is created, this class checks to see if any of the properties have been set
 * as system properties and if they have it sets the corresponding property to this value, these values can then be
 * overridden by using the appropriate setter method.
 */
public abstract class AbstractStAXProcessor<T> implements StAXProcessor<T> {

    private static final long serialVersionUID = -6678111384531149923L;

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractStAXProcessor.class);

    /**
     * Holds the factory for creating <tt>XMLStreamReader</tt>'s...
     */
    private transient XMLInputFactory xmlInputFactory;

    /**
     * Turns on/off implementation specific DTD validation.
     */
    private boolean namespaceAware;

    /**
     * Turns on/off namespace processing for XML 1.0 support.
     */
    private boolean validating;

    /**
     * Requires the processor to coalesce adjacent character data.
     */
    private boolean coalescing;

    /**
     * Replace internal entity references with their replacement text and report them as characters.
     */
    private boolean replacingEntityReferences;

    /**
     * Resolve external parsed entities.
     */
    private boolean supportingExternalEntities;

    /**
     * Use this property to request processors that do not support DTDs.
     */
    private boolean supportDtd;

    /**
     * Holds the processor to use when an error response is detected.
     */
    private StAXProcessor<AciErrorException> errorProcessor;

    /**
     * This constructor gets a new {@link XMLInputFactory} instance that is reused every time
     * {@link #process(com.autonomy.aci.client.transport.AciResponseInputStream)} is called, this
     * should be faster than creating a new instance every time this method is called.
     * <p>
     * The properties are set to the following defaults if they are not specified as system properties:
     * <table summary="">
     * <tr><th>Property</th><th>Default</th></tr>
     * <tr><td>XMLInputFactory.IS_NAMESPACE_AWARE</td><td><tt>false</tt></td></tr>
     * <tr><td>XMLInputFactory.IS_VALIDATING<tt>false</tt></td></tr>
     * <tr><td>XMLInputFactory.IS_COALESCING<tt>false</tt></td></tr>
     * <tr><td>XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES<tt>true</tt></td></tr>
     * <tr><td>XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES<tt>false</tt></td></tr>
     * <tr><td>XMLInputFactory.SUPPORT_DTD<tt>true</tt></td></tr>
     * </table>
     */
    protected AbstractStAXProcessor() {
        // See if the various XMLInputFactory properties are set as system properties...
        namespaceAware = BooleanUtils.toBoolean(
                StringUtils.defaultString(System.getProperty(XMLInputFactory.IS_NAMESPACE_AWARE), "false"));
        validating = BooleanUtils
                .toBoolean(StringUtils.defaultString(System.getProperty(XMLInputFactory.IS_VALIDATING), "false"));
        coalescing = BooleanUtils
                .toBoolean(StringUtils.defaultString(System.getProperty(XMLInputFactory.IS_COALESCING), "false"));
        replacingEntityReferences = BooleanUtils.toBoolean(StringUtils
                .defaultString(System.getProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES), "true"));
        supportingExternalEntities = BooleanUtils.toBoolean(StringUtils
                .defaultString(System.getProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES), "false"));
        supportDtd = BooleanUtils
                .toBoolean(StringUtils.defaultString(System.getProperty(XMLInputFactory.SUPPORT_DTD), "true"));

        // Create the XMLStreamReader factory...
        xmlInputFactory = XMLInputFactory.newInstance();
    }

    private void readObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
        // Read in all the serialized properties...
        inputStream.defaultReadObject();

        // Recreate the XMLStreamReader factory...
        xmlInputFactory = XMLInputFactory.newInstance();
    }

    /**
     * This method firstly checks that the content type of the response is text based and can be parsed. If so, it
     * converts the <tt>AciResponseInputStream</tt> into a StAX <tt>XMLStreamReader</tt> and calls the the {@link
     * #process(javax.xml.stream.XMLStreamReader)} method that should be implemented in a subclass to do all the work.
     * @param aciResponseInputStream The ACI response to process
     * @return An object of type <tt>T</tt>
     * @throws AciErrorException  If the ACI response was an error response
     * @throws ProcessorException If an error occurred during the processing of the IDOL response
     */
    public T process(final AciResponseInputStream aciResponseInputStream) {
        LOGGER.trace("process() called...");

        if (!"text/xml".equalsIgnoreCase(aciResponseInputStream.getContentType())) {
            throw new ProcessorException(
                    "This processor is unable to process non-text ACI responses. The content type for this response is "
                            + aciResponseInputStream.getContentType());
        }

        // Define this here so we can make sure it's closed when the processor is finished...
        XMLStreamReader xmlStreamReader = null;

        try {
            // Update the factory with the various properties as they might have changed since the last run...
            xmlInputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, namespaceAware);
            xmlInputFactory.setProperty(XMLInputFactory.IS_VALIDATING, validating);
            xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, coalescing);
            xmlInputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, replacingEntityReferences);
            xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
                    supportingExternalEntities);
            xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, supportDtd);

            // Convert the input stream..
            xmlStreamReader = xmlInputFactory.createXMLStreamReader(aciResponseInputStream);
            return process(xmlStreamReader);
        } catch (final XMLStreamException xmlse) {
            throw new ProcessorException("Unable to convert the InputStream to a XMLStreamReader", xmlse);
        } finally {
            if (xmlStreamReader != null) {
                try {
                    // This does NOT close the underlying AciResponseInputStream
                    xmlStreamReader.close();
                } catch (final XMLStreamException xmlse) {
                    LOGGER.error("Unable to close the XMLStreamReader.", xmlse);
                }
            }
        }
    }

    /**
     * Process the ACI response input into an object of type <tt>T</tt>.
     * @param xmlStreamReader The ACI response to process
     * @return An object of type <tt>T</tt>
     * @throws AciErrorException  If the ACI response was an error response
     * @throws ProcessorException If an error occurred during the processing of the IDOL response
     */
    public abstract T process(final XMLStreamReader xmlStreamReader);

    /**
     * Reads from the XML stream and tries to determine if the ACI response contains an error or not. The stream is left
     * at the end of the body content of the <tt>/autnresponse/response</tt> element, if one could be found.
     * @param xmlStreamReader The response to process
     * @return <tt>true</tt> if the response contains an error, <tt>false</tt> otherwise
     * @throws javax.xml.stream.XMLStreamException If there was a problem reading the IDOL Server response.
     */
    protected boolean isErrorResponse(final XMLStreamReader xmlStreamReader) throws XMLStreamException {
        LOGGER.trace("isErrorResponse() called...");

        // Get the /autnresponse/response element...
        while (xmlStreamReader.hasNext()) {
            // Get the event type...
            final int eventType = xmlStreamReader.next();

            // Check to see if it's a start event...
            if ((XMLEvent.START_ELEMENT == eventType)
                    && ("response".equalsIgnoreCase(xmlStreamReader.getLocalName()))) {
                return "ERROR".equalsIgnoreCase(xmlStreamReader.getElementText());
            }
        }

        // Couldn't find a /autnresponse/response element...
        throw new XMLStreamException("Unable to find /autnresponse/response element.");
    }

    /**
     * Process the remainder of the IDOL response with the configured error processor.
     * @param xmlStreamReader The IDOL response fragment containing the error information
     * @throws AciErrorException        Once the response has been parsed for all the error information it contains
     * @throws ProcessorException       If something went wrong while trying to process the error response
     * @throws IllegalArgumentException if no error processor has been set.
     */
    protected void processErrorResponse(final XMLStreamReader xmlStreamReader) {
        LOGGER.trace("processErrorResponse() ");

        // Sanity check...
        Validate.notNull(errorProcessor,
                "Unable to process the error response, as no errorProcessor has been configured.");

        // Process the error response and propagate the resulting exception...
        errorProcessor.process(xmlStreamReader);
    }

    /**
     * Move the cursor forward through the XML stream to the next start element.
     * @param xmlStreamReader The XML stream to use
     * @throws XMLStreamException If there was an error using the stream
     */
    protected void forwardToNextStartElement(final XMLStreamReader xmlStreamReader) throws XMLStreamException {
        while (xmlStreamReader.hasNext()) {
            final int eventType = xmlStreamReader.next();
            if (XMLEvent.START_ELEMENT == eventType) {
                return;
            }
        }
        throw new XMLStreamException("No more START_ELEMENT events found");
    }

    /**
     * Move the cursor forward through the XML stream to the next start or end element, which ever comes first.
     * @param xmlStreamReader The XML stream to use
     * @return The type of event forwarded to, i.e. <tt>XMLEvent.START_ELEMENT</tt> or <tt>XMLEvent.END_ELEMENT</tt>.
     * @throws XMLStreamException If there was an error using the stream
     */
    protected int forwardToNextStartOrEndElement(final XMLStreamReader xmlStreamReader) throws XMLStreamException {
        while (xmlStreamReader.hasNext()) {
            final int eventType = xmlStreamReader.next();
            if ((XMLEvent.START_ELEMENT == eventType) || (XMLEvent.END_ELEMENT == eventType)) {
                return eventType;
            }
        }
        throw new XMLStreamException("No more START_ELEMENT or END_ELEMENT events found");
    }

    /**
     * Forwards through the stream looking for the an element with <tt>elementName</tt>
     * @param elementName     The name of the element to find.
     * @param xmlStreamReader The stream to forward through
     * @throws XMLStreamException If there was an error using the stream, or if no element with <tt>elementName</tt>
     *                            could be found
     */
    protected void forwardToNamedStartElement(final String elementName, final XMLStreamReader xmlStreamReader)
            throws XMLStreamException {
        while (xmlStreamReader.hasNext()) {
            final int eventType = xmlStreamReader.next();
            if ((eventType == XMLEvent.START_ELEMENT) && (elementName.equals(xmlStreamReader.getLocalName()))) {
                return;
            }
        }
        throw new XMLStreamException("Unable to find a start element for, " + elementName);
    }

    public StAXProcessor<AciErrorException> getErrorProcessor() {
        return errorProcessor;
    }

    public void setErrorProcessor(final StAXProcessor<AciErrorException> errorProcessor) {
        this.errorProcessor = errorProcessor;
    }

    public boolean isNamespaceAware() {
        return namespaceAware;
    }

    public void setNamespaceAware(final boolean namespaceAware) {
        this.namespaceAware = namespaceAware;
    }

    public boolean isValidating() {
        return validating;
    }

    public void setValidating(final boolean validating) {
        this.validating = validating;
    }

    public boolean isCoalescing() {
        return coalescing;
    }

    public void setCoalescing(final boolean coalescing) {
        this.coalescing = coalescing;
    }

    public boolean isReplacingEntityReferences() {
        return replacingEntityReferences;
    }

    public void setReplacingEntityReferences(final boolean replacingEntityReferences) {
        this.replacingEntityReferences = replacingEntityReferences;
    }

    public boolean isSupportingExternalEntities() {
        return supportingExternalEntities;
    }

    public void setSupportingExternalEntities(final boolean supportingExternalEntities) {
        this.supportingExternalEntities = supportingExternalEntities;
    }

    public boolean isSupportDtd() {
        return supportDtd;
    }

    public void setSupportDtd(final boolean supportDtd) {
        this.supportDtd = supportDtd;
    }

}