org.apache.synapse.mediators.validate.ValidateMediator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.synapse.mediators.validate.ValidateMediator.java

Source

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 org.apache.synapse.mediators.validate;

import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAP12Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.Constants;
import org.apache.synapse.config.Util;
import org.apache.synapse.config.Property;
import org.apache.synapse.mediators.AbstractListMediator;
import org.apache.synapse.mediators.MediatorProperty;
import org.jaxen.JaxenException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;

/**
 * Validate a message or an element against a schema
 * <p/>
 * This internally uses the Xerces2-j parser, which cautions a lot about thread-safety and
 * memory leaks. Hence this initial implementation will create a single parser instance
 * for each unique mediator instance, and re-use it to validate multiple messages - even
 * concurrently - by synchronizing access
 */
public class ValidateMediator extends AbstractListMediator {

    private static final Log log = LogFactory.getLog(ValidateMediator.class);
    private static final Log trace = LogFactory.getLog(Constants.TRACE_LOGGER);

    /**
     * Default schema language (http://www.w3.org/2001/XMLSchema) and validator feature ids.
     */
    private static final String DEFAULT_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";

    /**
     * A list of property keys, referring to the schemas to be used for the validation
     */
    private List schemaKeys = new ArrayList();

    /**
     * An XPath expression to be evaluated against the message to find the element to be validated.
     * If this is not specified, the validation will occur against the first child element of the
     * SOAP body
     */
    private AXIOMXPath source = null;

    /**
     * A Map containing properties for the validate mediator - such as
     * features to be passed to the actual validator (Xerces)
     */
    private Map properties = new HashMap();

    /**
     * This is the actual Validator instance used to validate messages - probably
     * by multiple threads. Always *USE* validatorLock to synchronize access to this
     */
    private Validator validator = null;

    /**
     * Lock used to ensure thread-safe creation and use of the above Validator
     */
    private final Object validatorLock = new Object();

    /**
     * This is the reference to the DefaultHandler instance
     */
    private final MyErrorHandler errorHandler = new MyErrorHandler();

    private static final String DEFAULT_XPATH = "//s11:Envelope/s11:Body/child::*[position()=1] | "
            + "//s12:Envelope/s12:Body/child::*[position()=1]";

    public ValidateMediator() {
        // create the default XPath
        try {
            this.source = new AXIOMXPath(DEFAULT_XPATH);
            this.source.addNamespace("s11", SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
            this.source.addNamespace("s12", SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);
        } catch (JaxenException e) {
            handleException("Error creating source XPath expression", e);
        }
    }

    /**
     * Return the OMNode to be validated. If a source XPath is not specified, this will
     * default to the first child of the SOAP body i.e. - //*:Envelope/*:Body/child::*
     *
     * @param synCtx the message context
     * @return the OMNode against which validation should be performed
     */
    private OMNode getValidateSource(MessageContext synCtx) {

        try {
            Object o = source.evaluate(synCtx.getEnvelope());
            if (o instanceof OMNode) {
                return (OMNode) o;
            } else if (o instanceof List && !((List) o).isEmpty()) {
                return (OMNode) ((List) o).get(0); // Always fetches *only* the first
            } else {
                handleException("The evaluation of the XPath expression " + source + " must result in an OMNode");
            }
        } catch (JaxenException e) {
            handleException("Error evaluating XPath " + source + " on message");
        }
        return null;
    }

    public boolean mediate(MessageContext synCtx) {

        log.debug("ValidateMediator - Validate mediator mediate()");
        ByteArrayInputStream baisFromSource = null;
        boolean shouldTrace = shouldTrace(synCtx.getTracingState());
        if (shouldTrace) {
            trace.trace("Start : Validate mediator");
        }
        try {
            // create a byte array output stream and serialize the source node into it
            ByteArrayOutputStream baosForSource = new ByteArrayOutputStream();
            XMLStreamWriter xsWriterForSource = XMLOutputFactory.newInstance().createXMLStreamWriter(baosForSource);

            // serialize the validation target and get an input stream into it
            OMNode validateSource = getValidateSource(synCtx);
            if (shouldTrace) {
                trace.trace("Validate Source : " + validateSource.toString());
            }
            validateSource.serialize(xsWriterForSource);
            baisFromSource = new ByteArrayInputStream(baosForSource.toByteArray());

        } catch (Exception e) {
            handleException("Error accessing source element for validation : " + source, e);
        }

        try {
            XMLReader reader = XMLReaderFactory.createXMLReader();
            SAXSource saxSrc = new SAXSource(reader, new InputSource(baisFromSource));

            synchronized (validatorLock) {

                // initialize schemas/Validator if required
                initialize(synCtx);

                // perform actual validation
                validator.validate(saxSrc);

                if (errorHandler.isValidationError()) {
                    if (log.isDebugEnabled()) {
                        log.debug("Validation of element returned by XPath : " + source
                                + " failed against the given schemas with Message : "
                                + errorHandler.getSaxParseException().getMessage()
                                + " Executing 'on-fail' sequence");
                        log.debug("Failed message envelope : " + synCtx.getEnvelope());
                    }
                    // super.mediate() invokes the "on-fail" sequence of mediators
                    if (shouldTrace) {
                        trace.trace("Validation failed. Invoking the \"on-fail\" " + "sequence of mediators");
                    }
                    return super.mediate(synCtx);
                }
            }
        } catch (SAXException e) {
            handleException("Error validating " + source + " element" + e.getMessage(), e);
        } catch (IOException e) {
            handleException("Error validating " + source + " element" + e.getMessage(), e);
        }

        log.debug("validation of element returned by the XPath expression : " + source
                + " succeeded against the given schemas and the current message");
        if (shouldTrace) {
            trace.trace("End : Validate mediator");
        }
        return true;
    }

    /**
     * Perform actual initialization of this validate mediator instance - if required
     */
    private void initialize(MessageContext msgCtx) {

        // flag to check if we need to initialize/re-initialize the schema Validator
        boolean reCreate = false;

        // if any of the schemas are not loaded or expired, load or re-load them
        Iterator iter = schemaKeys.iterator();
        while (iter.hasNext()) {
            String propKey = (String) iter.next();
            Property dp = msgCtx.getConfiguration().getPropertyObject(propKey);
            if (dp != null && dp.isDynamic()) {
                if (!dp.isCached() || dp.isExpired()) {
                    reCreate = true; // request re-initialization of Validator
                }
            }
        }

        // do not re-initialize Validator unless required
        if (!reCreate && validator != null) {
            return;
        }

        try {
            // Create SchemaFactory and configure for the default schema language - XMLSchema
            SchemaFactory factory = SchemaFactory.newInstance(DEFAULT_SCHEMA_LANGUAGE);
            factory.setErrorHandler(errorHandler);

            // set any features on/off as requested
            iter = properties.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                String value = (String) entry.getValue();
                factory.setFeature((String) entry.getKey(), value != null && "true".equals(value));
            }

            Schema schema = null;

            StreamSource[] sources = new StreamSource[schemaKeys.size()];
            iter = schemaKeys.iterator();
            int i = 0;
            while (iter.hasNext()) {
                String propName = (String) iter.next();
                sources[i++] = Util.getStreamSource(msgCtx.getConfiguration().getProperty(propName));
            }
            schema = factory.newSchema(sources);

            // Setup validator and input source
            // Features set for the SchemaFactory get propagated to Schema and Validator (JAXP 1.4)
            validator = schema.newValidator();
            validator.setErrorHandler(errorHandler);

        } catch (SAXException e) {
            handleException("Error creating Validator", e);
        }
    }

    /**
     * This class handles validation errors to be used for error reporting
     */
    private class MyErrorHandler extends DefaultHandler {

        private boolean validationError = false;
        private SAXParseException saxParseException = null;

        public void error(SAXParseException exception) throws SAXException {
            validationError = true;
            saxParseException = exception;
        }

        public void fatalError(SAXParseException exception) throws SAXException {
            validationError = true;
            saxParseException = exception;
        }

        public void warning(SAXParseException exception) throws SAXException {
        }

        public boolean isValidationError() {
            return validationError;
        }

        public SAXParseException getSaxParseException() {
            return saxParseException;
        }
    }

    private void handleException(String msg) {
        log.error(msg);
        throw new SynapseException(msg);
    }

    private void handleException(String msg, Exception e) {
        log.error(msg, e);
        throw new SynapseException(msg, e);
    }

    // setters and getters

    /**
     * Get a mediator property. The common use case is a feature for the
     * underlying Xerces validator
     *
     * @param key property key / feature name
     * @return property string value (usually true|false)
     */
    public Object getProperty(String key) {
        return properties.get(key);
    }

    /**
     * Set a property for this mediator
     *
     * @param key   the property key / feature name
     * @param value property string value (usually true|false)
     * @see #getProperty(String)
     */
    public void setProperty(String key, Object value) {
        properties.put(key, value);
    }

    /**
     * Add a list of 'MediatorProperty'ies to this mediator
     *
     * @param list a List of MediatorProperty objects
     */
    public void addAllProperties(List list) {
        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            Object o = iter.next();
            if (o instanceof MediatorProperty) {
                MediatorProperty prop = (MediatorProperty) o;
                setProperty(prop.getName(), prop.getValue());
            } else {
                handleException("Attempt to set invalid property type. " + "Expected MediatorProperty type got "
                        + o.getClass().getName());
            }
        }
    }

    /**
     * Set a list of local property names which refer to a list of schemas to be
     * used for validation
     *
     * @param schemaKeys list of local property names
     */
    public void setSchemaKeys(List schemaKeys) {
        this.schemaKeys = schemaKeys;
    }

    /**
     * Set the given XPath as the source XPath
     * @param source an XPath to be set as the source
     */
    public void setSource(AXIOMXPath source) {
        this.source = source;
    }

    /**
     * Get the source XPath which yeilds the source element for validation
     * @return the XPath which yeilds the source element for validation
     */
    public AXIOMXPath getSource() {
        return source;
    }

    /**
     * The keys for the schema resources used for validation
     * @return schema registry keys
     */
    public List getSchemaKeys() {
        return schemaKeys;
    }

    /**
     * Properties for the actual Xerces validator
     * @return properties to be passed to the Xerces validator
     */
    public Map getProperties() {
        return properties;
    }
}