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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Throwable> exceptions;
|
||||
|
||||
public AggregatedXmlMessageValidationException(List<Throwable> exceptions){
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
/**
|
||||
* Will return iterator of exceptions aggregated by this Class.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Iterator<Throwable> exceptionIterator(){
|
||||
return exceptions.iterator();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -513,7 +513,7 @@
|
||||
<xsd:complexType>
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines a validating filter.
|
||||
Defines an XML validating filter.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence>
|
||||
@@ -529,9 +529,32 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="xml-validator" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to plug-in custom 'org.springframework.xml.validation.XmlValidator' strategy
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.xml.validation.XmlValidator" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="output-channel" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="discard-channel" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="schema-location" use="required" />
|
||||
<xsd:attribute name="discard-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to point to a Message Channel where you want discarded messages to be sent.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="schema-location"/>
|
||||
<xsd:attribute name="schema-type" default="xml-schema">
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string">
|
||||
@@ -540,6 +563,7 @@
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="throw-exception-on-rejection" type="xsd:boolean" default="false"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -8,11 +8,29 @@
|
||||
http://www.springframework.org/schema/integration/xml http://www.springframework.org/schema/integration/xml/spring-integration-xml-2.0.xsd">
|
||||
|
||||
|
||||
<int-xml:validating-filter id="filter"
|
||||
input-channel="inputChannel"
|
||||
<int-xml:validating-filter id="filterA"
|
||||
input-channel="inputChannelA"
|
||||
output-channel="validOutputChannel"
|
||||
discard-channel="invalidOutputChannel"
|
||||
schema-location="org/springframework/integration/xml/config/validationTestsSchema.xsd"/>
|
||||
|
||||
<int-xml:validating-filter id="filterB"
|
||||
input-channel="inputChannelB"
|
||||
output-channel="validOutputChannel"
|
||||
discard-channel="invalidOutputChannel"
|
||||
throw-exception-on-rejection="true"
|
||||
schema-location="org/springframework/integration/xml/config/validationTestsSchema.xsd"/>
|
||||
|
||||
<int-xml:validating-filter id="filterC"
|
||||
input-channel="inputChannelC"
|
||||
output-channel="validOutputChannel"
|
||||
discard-channel="invalidOutputChannel"
|
||||
xml-validator="xmlValidator"/>
|
||||
|
||||
<bean id="xmlValidator" class="org.springframework.xml.validation.XmlValidatorFactory" factory-method="createValidator">
|
||||
<constructor-arg value="classpath:org/springframework/integration/xml/config/validationTestsSchema.xsd"/>
|
||||
<constructor-arg value="http://www.w3.org/2001/XMLSchema"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="validOutputChannel">
|
||||
<int:queue/>
|
||||
|
||||
@@ -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("<greeting>hello</greeting>");
|
||||
GenericMessage<Document> docMessage = new GenericMessage<Document>(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("<greeting><other/></greeting>");
|
||||
GenericMessage<Document> docMessage = new GenericMessage<Document>(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("<greeting><other/></greeting>");
|
||||
GenericMessage<Document> docMessage = new GenericMessage<Document>(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("<greeting>hello</greeting>");
|
||||
GenericMessage<Document> docMessage = new GenericMessage<Document>(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("<greeting><other/></greeting>");
|
||||
GenericMessage<Document> docMessage = new GenericMessage<Document>(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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user