Author: davsclaus Date: Wed Oct 17 17:13:46 2012 New Revision: 1399351 URL: http://svn.apache.org/viewvc?rev=1399351&view=rev Log: CAMEL-5608: Ensure validator component closes input stream after usage. Align code with the xlst/xpath is doing. Added option to fail if no body or not.
Modified: camel/branches/camel-2.9.x/ (props changed) camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/component/validator/ValidatorComponent.java camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java camel/branches/camel-2.9.x/camel-core/src/test/java/org/apache/camel/component/validator/FileValidatorRouteTest.java Propchange: camel/branches/camel-2.9.x/ ------------------------------------------------------------------------------ Merged /camel/trunk:r1399333 Merged /camel/branches/camel-2.10.x:r1399335 Propchange: camel/branches/camel-2.9.x/ ------------------------------------------------------------------------------ Binary property 'svnmerge-integrated' - no diff available. Modified: camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/component/validator/ValidatorComponent.java URL: http://svn.apache.org/viewvc/camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/component/validator/ValidatorComponent.java?rev=1399351&r1=1399350&r2=1399351&view=diff ============================================================================== --- camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/component/validator/ValidatorComponent.java (original) +++ camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/component/validator/ValidatorComponent.java Wed Oct 17 17:13:46 2012 @@ -27,6 +27,7 @@ import org.apache.camel.Endpoint; import org.apache.camel.impl.DefaultComponent; import org.apache.camel.impl.ProcessorEndpoint; import org.apache.camel.processor.validation.ValidatingProcessor; +import org.apache.camel.util.IOHelper; import org.apache.camel.util.ResourceHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,6 +53,8 @@ public class ValidatorComponent extends // force loading of schema at create time otherwise concurrent // processing could cause thread safe issues for the javax.xml.validation.SchemaFactory validator.loadSchema(); + // and make sure to close the input stream after the schema has been loaded + IOHelper.close(is); return new ProcessorEndpoint(uri, this, validator); } Modified: camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java URL: http://svn.apache.org/viewvc/camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java?rev=1399351&r1=1399350&r2=1399351&view=diff ============================================================================== --- camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java (original) +++ camel/branches/camel-2.9.x/camel-core/src/main/java/org/apache/camel/processor/validation/ValidatingProcessor.java Wed Oct 17 17:13:46 2012 @@ -18,27 +18,39 @@ package org.apache.camel.processor.valid import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; import java.net.URL; import java.util.Collections; - import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stax.StAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; +import org.w3c.dom.Node; import org.w3c.dom.ls.LSResourceResolver; - import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.apache.camel.Exchange; +import org.apache.camel.ExpectedBodyTypeException; import org.apache.camel.Processor; +import org.apache.camel.RuntimeTransformException; +import org.apache.camel.TypeConverter; +import org.apache.camel.converter.jaxp.XmlConverter; +import org.apache.camel.util.IOHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A processor which validates the XML version of the inbound message body @@ -47,7 +59,8 @@ import org.apache.camel.Processor; * @version */ public class ValidatingProcessor implements Processor { - // for lazy creation of the Schema + private static final Logger LOG = LoggerFactory.getLogger(ValidatingProcessor.class); + private XmlConverter converter = new XmlConverter(); private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; private Schema schema; private Source schemaSource; @@ -58,6 +71,7 @@ public class ValidatingProcessor impleme private boolean useDom; private boolean useSharedSchema = true; private LSResourceResolver resourceResolver; + private boolean failOnNullBody = true; public void process(Exchange exchange) throws Exception { Schema schema; @@ -69,39 +83,59 @@ public class ValidatingProcessor impleme Validator validator = schema.newValidator(); - Source source; - Result result; + // the underlying input stream, which we need to close to avoid locking files or other resources + Source source = null; + InputStream is = null; try { - if (useDom) { - source = exchange.getIn().getBody(DOMSource.class); - result = new DOMResult(); + Result result = null; + // only convert to input stream if really needed + if (isInputStreamNeeded(exchange)) { + is = exchange.getIn().getBody(InputStream.class); + if (is != null) { + source = getSource(exchange, is); + } } else { - source = exchange.getIn().getBody(SAXSource.class); - result = new SAXResult(); + Object body = exchange.getIn().getBody(); + if (body != null) { + source = getSource(exchange, body); + } } - } catch (Exception e) { - throw new NoXmlBodyValidationException(exchange, e); - } - if (source == null) { - throw new NoXmlBodyValidationException(exchange); - } - // create a new errorHandler and set it on the validator - // must be a local instance to avoid problems with concurrency (to be - // thread safe) - ValidatorErrorHandler handler = errorHandler.getClass().newInstance(); - validator.setErrorHandler(handler); + if (source == null && isFailOnNullBody()) { + throw new NoXmlBodyValidationException(exchange); + } - try { - validator.validate(source, result); - } catch (SAXParseException e) { - // can be thrown for non well formed XML - throw new SchemaValidationException(exchange, schema, Collections.singletonList(e), - Collections.<SAXParseException> emptyList(), - Collections.<SAXParseException> emptyList()); - } + if (source instanceof DOMSource) { + result = new DOMResult(); + } else if (source instanceof StreamSource) { + result = new StreamResult(new StringWriter()); + } else if (source instanceof SAXSource) { + result = new SAXResult(); + } else if (source instanceof StAXSource) { + result = null; + } - handler.handleErrors(exchange, schema, result); + if (source != null) { + // create a new errorHandler and set it on the validator + // must be a local instance to avoid problems with concurrency (to be + // thread safe) + ValidatorErrorHandler handler = errorHandler.getClass().newInstance(); + validator.setErrorHandler(handler); + + try { + LOG.trace("Validating {}", source); + validator.validate(source, result); + handler.handleErrors(exchange, schema, result); + } catch (SAXParseException e) { + // can be thrown for non well formed XML + throw new SchemaValidationException(exchange, schema, Collections.singletonList(e), + Collections.<SAXParseException> emptyList(), + Collections.<SAXParseException> emptyList()); + } + } + } finally { + IOHelper.close(is); + } } public void loadSchema() throws Exception { @@ -177,17 +211,17 @@ public class ValidatingProcessor impleme this.errorHandler = errorHandler; } + @Deprecated public boolean isUseDom() { return useDom; } /** - * Sets whether DOMSource and DOMResult should be used, or SaxSource and - * SaxResult. - * - * @param useDom - * true to use DOM otherwise Sax is used + * Sets whether DOMSource and DOMResult should be used. + * + * @param useDom true to use DOM otherwise */ + @Deprecated public void setUseDom(boolean useDom) { this.useDom = useDom; } @@ -208,6 +242,14 @@ public class ValidatingProcessor impleme this.resourceResolver = resourceResolver; } + public boolean isFailOnNullBody() { + return failOnNullBody; + } + + public void setFailOnNullBody(boolean failOnNullBody) { + this.failOnNullBody = failOnNullBody; + } + // Implementation methods // ----------------------------------------------------------------------- @@ -236,4 +278,96 @@ public class ValidatingProcessor impleme } return factory.newSchema(getSchemaSource()); } + + /** + * Checks whether we need an {@link InputStream} to access the message body. + * <p/> + * Depending on the content in the message body, we may not need to convert + * to {@link InputStream}. + * + * @param exchange the current exchange + * @return <tt>true</tt> to convert to {@link InputStream} beforehand converting to {@link Source} afterwards. + */ + protected boolean isInputStreamNeeded(Exchange exchange) { + Object body = exchange.getIn().getBody(); + if (body == null) { + return false; + } + + if (body instanceof InputStream) { + return true; + } else if (body instanceof Source) { + return false; + } else if (body instanceof String) { + return false; + } else if (body instanceof byte[]) { + return false; + } else if (body instanceof Node) { + return false; + } else if (exchange.getContext().getTypeConverterRegistry().lookup(Source.class, body.getClass()) != null) { + //there is a direct and hopefully optimized converter to Source + return false; + } + // yes an input stream is needed + return true; + } + + /** + * Converts the inbound body to a {@link Source}, if the body is <b>not</b> already a {@link Source}. + * <p/> + * This implementation will prefer to source in the following order: + * <ul> + * <li>DOM - DOM if explicit configured to use DOM</li> + * <li>SAX - SAX as 2nd choice</li> + * <li>Stream - Stream as 3rd choice</li> + * <li>DOM - DOM as 4th choice</li> + * </ul> + */ + protected Source getSource(Exchange exchange, Object body) { + if (isUseDom()) { + // force DOM + return exchange.getContext().getTypeConverter().tryConvertTo(DOMSource.class, exchange, body); + } + + // body may already be a source + if (body instanceof Source) { + return (Source) body; + } + Source source = null; + if (body instanceof InputStream) { + return new StreamSource((InputStream)body); + } + if (body != null) { + TypeConverter tc = exchange.getContext().getTypeConverterRegistry().lookup(Source.class, body.getClass()); + if (tc != null) { + source = tc.convertTo(Source.class, body); + } + } + + if (source == null) { + // then try SAX + source = exchange.getContext().getTypeConverter().tryConvertTo(SAXSource.class, exchange, body); + } + if (source == null) { + // then try stream + source = exchange.getContext().getTypeConverter().tryConvertTo(StreamSource.class, exchange, body); + } + if (source == null) { + // and fallback to DOM + source = exchange.getContext().getTypeConverter().tryConvertTo(DOMSource.class, exchange, body); + } + if (source == null) { + if (isFailOnNullBody()) { + throw new ExpectedBodyTypeException(exchange, Source.class); + } else { + try { + source = converter.toDOMSource(converter.createDocument()); + } catch (ParserConfigurationException e) { + throw new RuntimeTransformException(e); + } + } + } + return source; + } + } \ No newline at end of file Modified: camel/branches/camel-2.9.x/camel-core/src/test/java/org/apache/camel/component/validator/FileValidatorRouteTest.java URL: http://svn.apache.org/viewvc/camel/branches/camel-2.9.x/camel-core/src/test/java/org/apache/camel/component/validator/FileValidatorRouteTest.java?rev=1399351&r1=1399350&r2=1399351&view=diff ============================================================================== --- camel/branches/camel-2.9.x/camel-core/src/test/java/org/apache/camel/component/validator/FileValidatorRouteTest.java (original) +++ camel/branches/camel-2.9.x/camel-core/src/test/java/org/apache/camel/component/validator/FileValidatorRouteTest.java Wed Oct 17 17:13:46 2012 @@ -16,11 +16,14 @@ */ package org.apache.camel.component.validator; +import java.io.File; + import org.apache.camel.ContextTestSupport; import org.apache.camel.Exchange; import org.apache.camel.ValidationException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.util.FileUtil; /** * @@ -40,6 +43,10 @@ public class FileValidatorRouteTest exte Exchange.FILE_NAME, "valid.xml"); MockEndpoint.assertIsSatisfied(validEndpoint, invalidEndpoint, finallyEndpoint); + + // should be able to delete the file + oneExchangeDone.matchesMockWaitTime(); + assertTrue("Should be able to delete the file", FileUtil.deleteFile(new File("target/validator/valid.xml"))); } public void testInvalidMessage() throws Exception { @@ -51,12 +58,15 @@ public class FileValidatorRouteTest exte Exchange.FILE_NAME, "invalid.xml"); MockEndpoint.assertIsSatisfied(validEndpoint, invalidEndpoint, finallyEndpoint); + + // should be able to delete the file + oneExchangeDone.matchesMockWaitTime(); + assertTrue("Should be able to delete the file", FileUtil.deleteFile(new File("target/validator/invalid.xml"))); } @Override protected void setUp() throws Exception { deleteDirectory("target/validator"); - super.setUp(); validEndpoint = resolveMandatoryEndpoint("mock:valid", MockEndpoint.class); invalidEndpoint = resolveMandatoryEndpoint("mock:invalid", MockEndpoint.class); @@ -68,7 +78,7 @@ public class FileValidatorRouteTest exte return new RouteBuilder() { @Override public void configure() throws Exception { - from("file:target/validator") + from("file:target/validator?noop=true") .doTry() .to("validator:org/apache/camel/component/validator/schema.xsd") .to("mock:valid")