Java tutorial
/* * 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; } }