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:
Oleg Zhurakousky
2010-10-14 14:12:54 -04:00
parent e738d34535
commit fc51593001
7 changed files with 186 additions and 38 deletions

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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/>

View File

@@ -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));
}
}