简体   繁体   中英

Spring: Validate REST controller against XSD schema

At the moment I have RestController with the following code

package be.smartask.api;

import be.smartask.api.model.NumberValue;
import be.smartask.api.model.TextValue;
import be.smartask.api.model.Translations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;


/**
 * @author Glenn Van Schil
 *         Created on 21/01/2016
 */
@CrossOrigin
@RestController
@RequestMapping(path = "/values")
public class Controller {

    @RequestMapping(method = RequestMethod.POST, headers = "Value-Type=text")
    ResponseEntity<?> createAttribute(@RequestBody TextValue value) {
        System.out.println(value.getCode());
        for (Translations.Translation translation : value.getTranslations().getTranslation()) {
            System.out.println(translation.getLang());
            System.out.println(translation.getValue());
        }
        return new ResponseEntity<>(HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.POST, headers = "Value-Type=number")
    ResponseEntity<?> createAttribute(@RequestBody NumberValue value) {
        System.out.println(value.getMinValue());
        System.out.println(value.getMaxValue());
        return new ResponseEntity<>(HttpStatus.CREATED);
    }
}

This works great, I can post a NumberValue like this:

<numberValue id="id" minValue="0" maxValue="10"/>

But when I post a TextValue to NumberValue method

<textvalue code="LUXE">
    <translations>
        <translation lang="en">luxury car</translation>
    </translations>
</textvalue>

It still works. It just leaves the minValue and maxValue to 0,0.

My question is: How can i enforce a 400: Bad Request (or something like that) when the body is not exactly the same as defined in my xsd file? By defined I mean the xml tag's name is not the same or a required xs:attribute is missing, ...

My xsd file:

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">

            <!-- ValueVO -->
    <xs:complexType name="geopoint">
        <xs:attribute type="xs:double" name="lat"/>
        <xs:attribute type="xs:double" name="lon"/>
    </xs:complexType>

    <xs:complexType name="translations">
        <xs:sequence>
            <xs:element name="translation" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                    <xs:simpleContent>
                        <xs:extension base="xs:string">
                            <xs:attribute type="xs:string" name="lang" use="required"/>
                        </xs:extension>
                    </xs:simpleContent>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="value">
        <xs:attribute type="xs:string" name="id" use="required"/>
    </xs:complexType>

    <xs:complexType name="textValue">
        <xs:complexContent>
            <xs:extension base="value">
                <xs:sequence>
                    <xs:element type="translations" name="translations"/>
                </xs:sequence>
                <xs:attribute type="xs:string" name="code" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="numberValue">
        <xs:complexContent>
            <xs:extension base="value">
                <xs:attribute type="xs:double" name="minValue" use="required"/>
                <xs:attribute type="xs:double" name="maxValue" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="dateValue">
        <xs:complexContent>
            <xs:extension base="value">
                <xs:attribute type="xs:date" name="minValue" use="required"/>
                <xs:attribute type="xs:date" name="maxValue" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="cityValue">
        <xs:complexContent>
            <xs:extension base="value">
                <xs:sequence>
                    <xs:element type="geopoint" name="geopoint"/>
                    <xs:element type="translations" name="translations"/>
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="timeValue">
        <xs:complexContent>
            <xs:extension base="value"/>
        </xs:complexContent>
    </xs:complexType>

    <xs:element name="value" type="value"/>
    <xs:element name="textValue" type="textValue"/>
    <xs:element name="numberValue" type="numberValue"/>
    <xs:element name="dateValue" type="dateValue"/>
    <xs:element name="cityValue" type="cityValue"/>
    <xs:element name="timeValue" type="timeValue"/>

    <xs:element name="values">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="value" maxOccurs="unbounded" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <!-- END ValueVO --> 
</xs:schema>

UPDATE

I added the following to my spring config based on Sheetal Mohan Sharma's answer

<?xml version="1.0" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd


http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="be.smartask.api"/>

    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="marshallingHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            </list>
        </property>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    </bean>
    <bean id="marshallingHttpMessageConverter"
          class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
          p:marshaller-ref="jaxb2Marshaller" p:unmarshaller-ref="jaxb2Marshaller"/>

    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="schema" value="classpath:schema/xsd/smartaskRead.xsd"/>
        <property name="classesToBeBound">
            <list>
                <value>be.smartask.api.model.NumberValue</value>
                <value>be.smartask.api.model.TextValue</value>
            </list>
        </property>
    </bean>
</beans>

But NumberValue still accepts TextValue...

You can look at using jaxb marshaller and unmarshaller Use message converters and jaxmashaller to check against XSD. Error will be thrown if validation fails but it may not be specific. Check the links below.

<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"p:marshaller-ref="jaxb2Marshaller" p:unmarshaller-ref="jaxb2Marshaller" />

<bean id="jaxb2Marshaller">
    <property name="schema" value="classpath:/mySchema.xsd"/>
    <property name="classesToBeBound">
        <list>
            <value>com.xyz.RequestPojo</value>
            <value>com.xyz.ResponsePojo</value>
        </list>
    </property>
</bean>

Few good examples - here and here

We managed to do this even with API versioning by providing the namespace inside the XSD files

config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <context:component-scan base-package="be.smartask.api"/>

    <context:annotation-config/>

    <mvc:cors>
        <mvc:mapping path="/**"/>
    </mvc:cors>

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean id="marshallingHttpMessageConverter"
                  class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
                <property name="marshaller" ref="jaxb2Marshaller"/>
                <property name="unmarshaller" ref="jaxb2Marshaller"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="schemas">
          <list>
            <value>classpath:/schema/xsd/v1/smartAsk.xsd</value>
            <value>classpath:/schema/xsd/v2/smartAsk.xsd</value>
          </list>
        </property>
        <property name="packagesToScan">
          <list>
            <value>be.smartask.api.model.smartask.v1</value>
            <value>be.smartask.api.model.smartask.v2</value>
          </list>
        </property>
    </bean>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

First you provide the schema locations, second the the package location of the xsd objects will be generated and last override the marshallingHttpMessageConverter with your newly created Jaxb2Marshaller

If you plan on doing some versioning in your API it's best to supply a namespace so the marshaller know which xsd file to use for which package

<xs:schema xmlns="smartask:v1"
       xmlns:xs="http://www.w3.org/2001/XMLSchema"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
       xmlns:annox="http://annox.dev.java.net"
       attributeFormDefault="unqualified"
       elementFormDefault="qualified"
       targetNamespace="smartask:v1"
       jaxb:version="2.1"
       jaxb:extensionBindingPrefixes="annox">

<xs:schema xmlns="smartask:v2"
       xmlns:xs="http://www.w3.org/2001/XMLSchema"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
       xmlns:annox="http://annox.dev.java.net"
       attributeFormDefault="unqualified"
       elementFormDefault="qualified"
       targetNamespace="smartask:v2"
       jaxb:version="2.1"
       jaxb:extensionBindingPrefixes="annox">

in our case it was "smartask:v1" and "smartask:v2"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM