在 JAX-WS RI 中延迟验证部分传入的 Web 服务请求

validate parts of the incoming web service request lazily in JAX-WS RI

我必须在 Java 8 网络服务 (JAX-WS RI) 中处理大请求。一个请求包含一个 "header" 和许多 "records" 像这样(不是说第 2、3、4、5 个学生记录根据 schema 是无效的):

<helloStudentsServiceRequest>
    <workshop>
        <name>wsname</name>
        <tutor>tutorname</tutor>
    </workshop>
    <studentList>
        <!--1 or more repetitions: -->
        <student>
            <Name>st1</Name>
            <Birth>1999-11-11</Birth>
        </student>
        <student>
            <Name>st2</Name>
        </student>
        <student>
            <Name>st2</Name>
            <Birth>199O-11-11</Birth>
        </student>
        <student>
            <Name>st3</Name>
            <Birth>stoneage</Birth>
        </student>
        <student>
            <Birth>stoneage</Birth>
        </student>
        <student>
            <Name>st6</Name>
            <Birth>2001-11-12</Birth>
        </student>
    </studentList>
</helloStudentsServiceRequest>

我使用模式 (XSD) 来描述我的界面。例如:

<xsd:complexType name="HelloStudentsServiceRequestType">
    <xsd:sequence>
        <xsd:element name="workshop" type="WorkshopType" />
        <xsd:element name="studentList">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="student" type="StudentType" maxOccurs="unbounded"/>
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="WorkshopType">
    <xsd:sequence>
        <xsd:element name="name" type="xsd:string" />
        <xsd:element name="tutor" type="xsd:string" />
    </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="StudentType">
</xsd:annotation>
    <xsd:sequence>
        <xsd:element name="Name" type="xsd:string" />
        <xsd:element name="Birth" type="xsd:date" />
    </xsd:sequence>
</xsd:complexType>

我想使用此架构来验证请求。

但是,如果请求中的一些 "record" 根据架构无效,我将无法拒绝该请求。在这种情况下,我必须处理有效记录和 return 一些关于无效记录的有意义的信息(最好是 SAX 错误)。

我希望请求的架构不包含完成此 "lazy validation" 所需的技术细节。例如 我不想 使用 "big string" 来保存 "record" 然后根据模式验证该字符串。这会破坏界面并且难以与客户沟通。所以这对我来说 不行:

...
<xsd:element name="studentList">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element name="student" type="xsd:string" maxOccurs="unbounded"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>
...

您知道在 JAX-WS RI (JAXB2) 中解决这个问题的优雅方法吗?

可以自定义 JAXB 绑定以将 XML 的一部分映射到 DOM。详情:

https://jaxb.java.net/guide/Avoid_strong_databinding.html#Mapping_to_DOM

所以我将 "WorkshopType" 和 "StudentType" 映射到 DOM。现在可以单独验证这些 DOMs 并拒绝或部分处理传入消息。如果 DOM 验证不成功,也可以捕获 SAX 异常并将其发回客户端。

working example zipped

我使用了这个绑定定制:

<jxb:bindings version="1.0" 
xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<jxb:bindings
    schemaLocation="WorkshopType.xsd"
    node="//xsd:complexType[@name='WorkshopType']">
    <jxb:dom />
</jxb:bindings> 
<jxb:bindings
    schemaLocation="StudentType.xsd"
    node="//xsd:complexType[@name='StudentType']">
    <jxb:dom />
</jxb:bindings> 

这些是模式:

对于请求:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" xmlns="helloStudentsNS"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
targetNamespace="helloStudentsNS">

<xsd:include schemaLocation="StudentType.xsd" />
<xsd:include schemaLocation="WorkshopType.xsd" />

<xsd:element name="helloStudentsServiceRequest" type="HelloStudentsServiceRequestType" />
<xsd:element name="helloStudentsServiceResponse" type="HelloStudentsServiceResponseType" />

<xsd:complexType name="HelloStudentsServiceRequestType">
    <xsd:sequence>
        <xsd:element name="workshop" type="WorkshopType" />
        <xsd:element name="studentList">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="student" type="StudentType" maxOccurs="unbounded"/>
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    </xsd:sequence>
</xsd:complexType>

<xsd:complexType name="HelloStudentsServiceResponseType">
    <xsd:sequence>
        <xsd:element name="GreetingHeader" type="xsd:string" />
        <xsd:element name="StudentGreeting" type="xsd:string" maxOccurs="unbounded"/>
    </xsd:sequence>
</xsd:complexType>

对于研讨会和学生类型:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" 
xmlns="helloStudentsNS"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
jaxb:version="2.0"
targetNamespace="helloStudentsNS">

<xsd:element name="workshop" type="WorkshopType"/>

<xsd:complexType name="WorkshopType">
    <!-- <xsd:annotation><xsd:appinfo><jaxb:dom/></xsd:appinfo></xsd:annotation> -->
    <xsd:sequence>
        <xsd:element name="name" type="xsd:string" />
        <xsd:element name="tutor" type="xsd:string" />
    </xsd:sequence>
</xsd:complexType>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" 
xmlns="helloStudentsNS" 
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jaxb:version="2.0" targetNamespace="helloStudentsNS">

<!-- 
-->
<xsd:element name="student" type="StudentType"/> 

<xsd:complexType name="StudentType">
    <!-- <xsd:annotation><xsd:appinfo><jaxb:dom/></xsd:appinfo></xsd:annotation> -->
    <xsd:sequence>
        <xsd:element name="Name" type="xsd:string" />
        <xsd:element name="Birth" type="xsd:date" />
    </xsd:sequence>
</xsd:complexType>

这是 java 代码:

public HelloStudentsServiceResponseType greetStudents(HelloStudentsServiceRequestType req) {

    WorkshopType ws;
    // unmarshall workshop data
    Element element = req.getWorkshop(); // workshop is retrieved as DOM 
    try {
        Unmarshaller u = jc.createUnmarshaller(); // jaxb ri unmarshaller not thread safe: https://jaxb.java.net/guide/Performance_and_thread_safety.html
        u.setSchema(workshopSchema); // enable validation by workshopType.xsd
        ws = u.unmarshal(element, WorkshopType.class).getValue(); // unmarshal
    } catch (JAXBException e) {
        // this is fatal
        throw new RuntimeException("fatal: invalid workshop data: ", e);
    }

    // unmarshall students
    List<String> studentGreetings = new ArrayList<>();
    for(Element studentElement : req.getStudentList().getStudent()) { // retrieve students as DOMs
        try {
            StudentType st;
            Unmarshaller u = jc.createUnmarshaller(); // jaxb ri unmarshaller not thread safe: https://jaxb.java.net/guide/Performance_and_thread_safety.html
            u.setSchema(studentSchema); // enable validation by studentType.xsd
            st = u.unmarshal(studentElement, StudentType.class).getValue();
            studentGreetings.add("Dear " + st.getName() + ", " + st.getBirth() + ", welcome in the workshop!");
        } catch (JAXBException e) {
            Throwable cause = e.getLinkedException();
            if(cause != null) {
                studentGreetings.add("Dear student, your data is invalid: " + cause.getMessage());
            }
            else {
                studentGreetings.add("Dear student, there's an unknown problem with your data");
            }
        }
    }

    // construct response
    HelloStudentsServiceResponseType response = new HelloStudentsServiceResponseType();
    response.setGreetingHeader("I'd like to greet everybody in the " + ws.getName() + " workshop. " +
            "Your tutor is " + ws.getTutor());
    response.getStudentGreeting().addAll(studentGreetings);

    return response;
}