Spring 集成 - XML 架构验证器 - 动态架构文件选择

Spring Integration - XML Schema validator - Dynamic schema file selection

我正在尝试使用 Spring 集成 <int-xml:validating-filter /> 实施 XML 验证。我关注了 usage of spring integration's schema validator? 中的讨论。问题陈述是相同的,但有一个额外的参数。我不想对 schema-location="xyz.xsd" 中的值进行硬编码,而是想为相应的传入 xml 或 DOMSource 输入动态 select 适当的 xsd 文件。 我还关注 http://forum.spring.io/forum/spring-projects/integration/121115-dynamic-schema-location-for-xml-validating-filter-component,其中 Gary​​ Russell 提到:

There's no direct support for dynamic schemas, but you can provide a custom XmlValidator using the xml-validator attribute (mutually exclusive with schema location) Once you've introspected your document to find the schema you wish to validate against, simply delegate to a validator that has been configured to validate against that schema.

You can use a XmlValidatorFactory to create each validator; see the XmlValidatingMessageSelector for how to create a validator, once you know the schema location

由于评论可以追溯到 2012 年,现在 spring 集成中是否有一种方法可以通过动态 select 适当的模式来验证输入 xml?如果没有,谁能举例说明如何实施?

以下是我的 spring 集成配置:

<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway"
  default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel"
  error-channel="errorProcessingChannel" />

<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
  <!-- How to do  -->
  <int-xml:validating-filter xml-validator="xmlValidator" 
                             schema-type="xml-schema" 
                             throw-exception-on-rejection="true" />
  <int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />       
</int:chain>

<bean id="xmlValidator" class="abc.validator.DomSourceValidator" />

这是我的验证器 class 定义的:

import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {

    @Override
    public SAXParseException[] validate(Source source) throws IOException {
        /* How to implement this method? 
           Using XPath I can identify the root node from 'source' and then load
           the appropriate XSD file. But don't know how to proceed 
           or what should be 'return'(ed) from here. 
           Any example is much appreciated.
         */

        return null;
    }

    @Override
    public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
      // TODO Auto-generated method stub
      return null;
    }

}

使用 Spring 集成实现 XML 验证器的最佳方式是什么?

自该评论以来没有任何变化。

正如我在那里所说,您的验证器需要使用 XmlValidatorFactory 为每个模式创建一个验证器;然后为每条消息调用一个特定的验证器;类似于:

String schema = determineSchema(source);
XmlValidator val = lookupValidatorForSchema(schema);
if (val == null) {
    // create a new one and add it to the map.
}
return val.validate(source);

如果它能帮助其他正在尝试做同样事情的人

根据 Gary 的建议,我通过动态识别输入 XML 然后选择适当的架构文件来应用验证来实现 XmlValidator

下面是我的 spring 集成配置:

<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway" default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel" error-channel="errorProcessingChannel" />
    <int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
    <int-xml:validating-filter xml-validator="xmlValidator" 
                         schema-type="xml-schema" 
                         throw-exception-on-rejection="true" /> <!-- a MessageRejectedException is thrown in case validation fails -->
    <int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />       
</int:chain>

<bean id="xmlValidator" class="abc.validator.DomSourceValidator">
    <constructor-arg>
        <map key-type="java.lang.String" value-type="java.lang.String">
            <entry key="OTA_AirAvailRQ"      value="common/schemas/FS_OTA_AirAvailRQ.xsd" />
            <entry key="OTA_AirBookModifyRQ" value="common/schemas/FS_OTA_AirBookModifyRQ.xsd" />
            <entry key="OTA_AirBookRQ"       value="common/schemas/FS_OTA_AirBookRQ.xsd" />
        </map>
    </constructor-arg>
</bean>

为了演示,我使用 OTA 模式文件构建了一个地图 constructor-arg。地图 key 是来自网关的 XML 文件的根节点,value 是 xsd 文件的位置;并形成 key-value 对映射。

参考下面的实现class如何使用这个映射来识别输入XML并应用验证。

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;
import org.xml.sax.SAXParseException;

public class DomSourceValidator implements XmlValidator {

    private static final Log LOGGER = LogFactory.getLog(DomSourceValidator.class);

    private Map<String, String> schemaMap;
    private static Map<String, XmlValidator> validatorMap = new HashMap<>();

    public DomSourceValidator(Map<String, String> schemaMap) {
        this.schemaMap = schemaMap;
    }

    @PostConstruct
    private void init() throws IOException {
        LOGGER.info("Constructing Validators from schema resource list ...");
        Assert.notEmpty(schemaMap, "No schema resource map found");
        if (validatorMap.isEmpty()) {
            XmlValidator validator = null;
            for (Map.Entry<String, String> entry : schemaMap.entrySet()) {
                validator = createValidatorFromResourceUri(entry.getValue());
                validatorMap.put(entry.getKey(), validator);
            }
        }
    }

    @Override
    public SAXParseException[] validate(Source source) throws IOException {
        Assert.notNull(schemaMap, "No validator(s) defined");
        XmlValidator validator = lookupValidator(source);
        return validator.validate(source);
    }

    @Override
    public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
        // Skip implementation
        return null;
    }

    private XmlValidator lookupValidator(Source source) {
        String reqType = determineRequestType(source);
        LOGGER.info("Loading validator for type: " + reqType);
        XmlValidator xmlValidator = validatorMap.get(reqType);
        Assert.notNull(xmlValidator, "No validator found for type: " + reqType);
        return xmlValidator;
    }

    private String determineRequestType(Source source) {
        if (source instanceof DOMSource) {
            return ((DOMSource) source).getNode().getFirstChild().getNodeName();
        }
        return null;
    }

    private XmlValidator createValidatorFromResourceUri(String schemaResource) throws IOException {
        Assert.notNull(schemaResource);
        return XmlValidatorFactory.createValidator(new ClassPathResource(schemaResource), XmlValidatorFactory.SCHEMA_W3C_XML);
    }
}

一旦 spring bean id="xmlValidator" 被初始化,@PostConstruct 就会开始使用来自资源 URI 的 XmlValidatorFactory 创建验证器实例以具有预初始化验证器。

如果存在验证错误,则会抛出 org.springframework.integration.MessageRejectedException: Message was rejected due to XML Validation errors(参考 <int-xml:validating-filter /> 中的 throw-exception-on-rejection="true")。

上面的实现对我来说工作得很好。人们可以进一步定制它,或者 post 另一个版本来实现同样的效果。

备注

除了使用 <int-xml:validating-filter />,还可以在 <int-chain /> 中使用 <int:service-activator />,因为逻辑上 <int-xml:validating-filter /> 实际上不执行任何过滤逻辑。但它达到了目的。