From fc515930010ded8b1d6fb106a0a2e13ee94c2ae3 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 14:12:54 -0400 Subject: [PATCH] INT-1519, INT-957, INT-1515, Added support for propagating XML Validation exceptions with MessageRejectedException, added support for 'xml-validator' attribute, change XmlValidatingMessageSelector to be bootstrapped with either Resource/Schema or XmlValidator --- .../integration/filter/MessageFilter.java | 18 +++++-- ...gregatedXmlMessageValidationException.java | 29 +++++++++++ .../XmlPayloadValidatingFilterParser.java | 49 ++++++++++++++----- ...java => XmlValidatingMessageSelector.java} | 35 ++++++++----- .../xml/config/spring-integration-xml-2.0.xsd | 30 ++++++++++-- ...oadValidatingFilterParserTests-context.xml | 22 ++++++++- ...XmlPayloadValidatingFilterParserTests.java | 41 ++++++++++++++-- 7 files changed, 186 insertions(+), 38 deletions(-) create mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java rename spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/{SchemaValidatingMessageSelector.java => XmlValidatingMessageSelector.java} (62%) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java index 0e4ed71c07..d81fc8137f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java @@ -100,14 +100,24 @@ public class MessageFilter extends AbstractReplyProducingMessageHandler { @Override protected Object handleRequestMessage(Message message) { - if (this.selector.accept(message)) { - return message; - } + Throwable filterException = null; + try { + if (this.selector.accept(message)) { + return message; + } + } catch (Exception e) { + filterException = e; + } if (this.discardChannel != null) { this.getMessagingTemplate().send(this.discardChannel, message); } if (this.throwExceptionOnRejection) { - throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message"); + if (filterException != null){ + throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message", filterException); + } + else { + throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message"); + } } return null; } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java new file mode 100644 index 0000000000..011369040c --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java @@ -0,0 +1,29 @@ +/** + * + */ +package org.springframework.integration.xml; + +import java.util.Iterator; +import java.util.List; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +@SuppressWarnings("serial") +public class AggregatedXmlMessageValidationException extends RuntimeException { + + private final List exceptions; + + public AggregatedXmlMessageValidationException(List exceptions){ + this.exceptions = exceptions; + } + /** + * Will return iterator of exceptions aggregated by this Class. + * + * @return + */ + public Iterator exceptionIterator(){ + return exceptions.iterator(); + } +} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java index f688a4c1b8..7ffa7deb52 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java @@ -16,10 +16,12 @@ package org.springframework.integration.xml.config; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** @@ -28,19 +30,15 @@ import org.w3c.dom.Element; */ public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointParser { private static String SELECTOR = - "org.springframework.integration.xml.selector.SchemaValidatingMessageSelector"; + "org.springframework.integration.xml.selector.XmlValidatingMessageSelector"; private static String FILTER = "org.springframework.integration.config.FilterFactoryBean"; + + /** Constant that defines a W3C XML Schema. */ + public static final String SCHEMA_W3C_XML = "http://www.w3.org/2001/XMLSchema"; - @Override - protected boolean shouldGenerateId() { - return true; - } - - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } + /** Constant that defines a RELAX NG Schema. */ + public static final String SCHEMA_RELAX_NG = "http://relaxng.org/ns/structure/1.0"; @Override protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { @@ -49,11 +47,36 @@ public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointPa IntegrationNamespaceUtils.setReferenceIfAttributeDefined(filterBuilder, element, "discard-channel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(filterBuilder, element, "throw-exception-on-rejection"); - BeanDefinitionBuilder selectorBuilder = BeanDefinitionBuilder.genericBeanDefinition(SELECTOR); - selectorBuilder.addConstructorArgValue(element.getAttribute("schema-location")); - selectorBuilder.addPropertyValue("schemaType", element.getAttribute("schema-type")); + BeanDefinitionBuilder selectorBuilder = BeanDefinitionBuilder.genericBeanDefinition(SELECTOR); + String validator = element.getAttribute("xml-validator"); + String schemaLocation = element.getAttribute("schema-location"); + boolean validatorDefined = StringUtils.hasText(validator); + boolean schemaLocationDefined = StringUtils.hasText(schemaLocation); + selectorBuilder.addPropertyValue("throwExceptionOnRejection", element.getAttribute("throw-exception-on-rejection")); + + if (!(validatorDefined ^ schemaLocationDefined)) { + throw new BeanDefinitionStoreException("Exactly one of 'xml-validator' or 'schema-location' is allowed on the 'validating-filter' element"); + } + if (schemaLocationDefined){ + selectorBuilder.addConstructorArgValue(schemaLocation); + String schemaType = "xml-schema".equals(element.getAttribute("schema-type")) ? SCHEMA_W3C_XML : SCHEMA_RELAX_NG;; + selectorBuilder.addConstructorArgValue(schemaType); + } + else { + selectorBuilder.addConstructorArgReference(validator); + } filterBuilder.addPropertyValue("targetObject", selectorBuilder.getBeanDefinition()); return filterBuilder; } + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java similarity index 62% rename from spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java rename to spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java index e4c82c0857..0d0b3e9900 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java @@ -20,9 +20,12 @@ import org.springframework.core.io.Resource; import org.springframework.integration.Message; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.xml.AggregatedXmlMessageValidationException; import org.springframework.integration.xml.DefaultXmlPayloadConverter; import org.springframework.integration.xml.XmlPayloadConverter; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.xml.validation.XmlValidator; import org.springframework.xml.validation.XmlValidatorFactory; import org.xml.sax.SAXParseException; @@ -32,19 +35,28 @@ import org.xml.sax.SAXParseException; * @since 2.0 * */ -public class SchemaValidatingMessageSelector implements MessageSelector{ +public class XmlValidatingMessageSelector implements MessageSelector { private final XmlValidator xmlValidator; - private volatile String schemaType = XmlValidatorFactory.SCHEMA_W3C_XML; + private volatile boolean throwExceptionOnRejection; private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter(); - public SchemaValidatingMessageSelector(Resource schema) throws Exception{ + public XmlValidatingMessageSelector(XmlValidator xmlValidator) throws Exception{ + Assert.notNull(xmlValidator, "XmlValidator can not be 'null'"); + this.xmlValidator = xmlValidator; + } + + public XmlValidatingMessageSelector(Resource schema, String schemaType) throws Exception{ Assert.notNull(schema, "You must provide XML schema location to perform validation"); this.xmlValidator = XmlValidatorFactory.createValidator(schema, schemaType); } + public void setThrowExceptionOnRejection(boolean throwExceptionOnRejection) { + this.throwExceptionOnRejection = throwExceptionOnRejection; + } + /** * Converter used to convert payloads prior to validation * @@ -54,19 +66,18 @@ public class SchemaValidatingMessageSelector implements MessageSelector{ this.converter = converter; } - public void setSchemaType(String schemaType) { - this.schemaType = schemaType; - } - + @SuppressWarnings("unchecked") public boolean accept(Message message) { - // TODO Need to figure out how the exceptions could be propagated since the return from this method is true/false - // and 'throw-exception-on-rejection'is actually set on the filter + SAXParseException[] validationExceptions = null; try { - SAXParseException[] validationExceptions = xmlValidator.validate(converter.convertToSource(message.getPayload())); - return validationExceptions.length == 0 ? true : false; + validationExceptions = xmlValidator.validate(converter.convertToSource(message.getPayload())); } catch (Exception e) { - e.printStackTrace(); throw new MessageHandlingException(message, e); } + boolean validationSuccess = ObjectUtils.isEmpty(validationExceptions); + if (!validationSuccess && throwExceptionOnRejection){ + throw new AggregatedXmlMessageValidationException(CollectionUtils.arrayToList(validationExceptions)); + } + return validationSuccess; } } diff --git a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd index dde312d875..b8b0836c3b 100644 --- a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd +++ b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd @@ -513,7 +513,7 @@ - Defines a validating filter. + Defines an XML validating filter. @@ -529,9 +529,32 @@ + + + + Allows you to plug-in custom 'org.springframework.xml.validation.XmlValidator' strategy + + + + + + + + - - + + + + Allows you to point to a Message Channel where you want discarded messages to be sent. + + + + + + + + + @@ -540,6 +563,7 @@ + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml index 30e11e3ae2..aaa7df5eb3 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml @@ -8,11 +8,29 @@ http://www.springframework.org/schema/integration/xml http://www.springframework.org/schema/integration/xml/spring-integration-xml-2.0.xsd"> - + + + + + + + + + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java index a0f7a58930..42779cecfa 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageRejectedException; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.xml.util.XmlTestUtil; @@ -42,21 +43,53 @@ public class XmlPayloadValidatingFilterParserTests { Document doc = XmlTestUtil.getDocumentForString("hello"); GenericMessage docMessage = new GenericMessage(doc); PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); - MessageChannel inputChannel = ac.getBean("inputChannel", MessageChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelA", MessageChannel.class); inputChannel.send(docMessage); assertNotNull(validChannel.receive(100)); - } @Test - public void testInvalidMessage() throws Exception { + public void testInvalidMessageWithDiscardChannel() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); Document doc = XmlTestUtil.getDocumentForString(""); GenericMessage docMessage = new GenericMessage(doc); PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); PollableChannel invalidChannel = ac.getBean("invalidOutputChannel", PollableChannel.class); - MessageChannel inputChannel = ac.getBean("inputChannel", MessageChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelA", MessageChannel.class); inputChannel.send(docMessage); assertNotNull(invalidChannel.receive(100)); assertNull(validChannel.receive(100)); } + @Test(expected=MessageRejectedException.class) + public void testInvalidMessageWithThrowException() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString(""); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + PollableChannel invalidChannel = ac.getBean("invalidOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelB", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(invalidChannel.receive(100)); + assertNull(validChannel.receive(100)); + } + @Test + public void testValidMessageWithValidator() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString("hello"); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelC", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(validChannel.receive(100)); + } + @Test + public void testInvalidMessageWithValidatorAndDiscardChannel() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString(""); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + PollableChannel invalidChannel = ac.getBean("invalidOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelC", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(invalidChannel.receive(100)); + } }