Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jaxb2Marshaller schema validation doesn't seem to be working

I experienced that Jaxb2Marshaller failed to validate my invalid XML against the XSD while doing the unmarshalling. I'm using Spring 4.0.0.RELEASE and Java 7.

Here is an example:

The XSD file: fruit.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFormDefault="qualified">

    <xs:attribute name="quantity" type="xs:string" />

    <xs:element name="fruit">
        <xs:complexType>
            <xs:sequence>
                <xs:choice>
                    <xs:element ref="banana" />
                    <xs:element ref="apple" />
                </xs:choice>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="banana">
        <xs:complexType>
            <xs:attribute ref="quantity" use="required" />
        </xs:complexType>
    </xs:element>

    <xs:element name="apple">
        <xs:complexType>
            <xs:attribute ref="quantity" use="required" />
        </xs:complexType>
    </xs:element>

</xs:schema>

I generated the JAXB POJOs from this XSD with Spring Tool Suite to com.testraptor.xml.jaxb package.

My invalid XML file: invalid.xml

<?xml version="1.0" encoding="UTF-8"?>
<fruit>
    <banana quantity="5" />
    <apple quantity="3" />
</fruit>

As you can see I broke the schema because there is a choice in the fruit tag and I used banana and apple at the same time.

My Spring configuration file: app-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:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/oxm 
    http://www.springframework.org/schema/oxm/spring-oxm-4.0.xsd">

    <oxm:jaxb2-marshaller id="marshaller" context-path="com.fruit.xml.jaxb" />

</beans>

The main class to test: Main.java

package com.fruit.test;

import java.io.IOException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.xml.sax.SAXException;

import com.fruit.xml.jaxb.Fruit;

public class Main {

    public static void main(String[] args) {
        ApplicationContext appContext = new ClassPathXmlApplicationContext(
                "app-context.xml");

        //--------------------------------SCHEMA VALIDATION IS OMITTED

        Jaxb2Marshaller jaxb2Unmarshaller = (Jaxb2Marshaller) appContext
                .getBean("marshaller");
        Resource schemaResource = appContext
                .getResource("classpath:fruit.xsd");
        jaxb2Unmarshaller.setSchema(schemaResource);

        Resource xml = appContext.getResource("classpath:invalid.xml");

        try {
            Fruit fruit = (Fruit) jaxb2Unmarshaller.unmarshal(new StreamSource(xml.getInputStream()));
            System.out.println("SCHEMA VALIDATION IS OMITTED:");
            System.out.println("Apple quantity is " + fruit.getApple().getQuantity());
            System.out.println("Banana quantity is " + fruit.getBanana().getQuantity());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //--------------------------------SCHEMA VALIDATION IS PASSED
        SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema;
        try {
            schema = sf.newSchema(schemaResource.getURL());
            JAXBContext context = JAXBContext.newInstance(Fruit.class);
            Unmarshaller unmarshaller = (Unmarshaller) context.createUnmarshaller(); 
            unmarshaller.setSchema(schema); 
            Fruit fruit2 = (Fruit) unmarshaller.unmarshal(xml.getInputStream());
            System.out.println("SCHEMA VALIDATION IS PASSED:");
            System.out.println("Apple quantity is " + fruit2.getApple().getQuantity());
            System.out.println("Banana quantity is " + fruit2.getBanana().getQuantity());
        } catch (SAXException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (JAXBException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }

}

When I run the code I get the following result:

INFO: Loading XML bean definitions from class path resource [app-context.xml]
dec. 26, 2013 9:33:22 DE org.springframework.oxm.jaxb.Jaxb2Marshaller createJaxbContextFromContextPath
INFO: Creating JAXBContext with context path [com.fruit.xml.jaxb]
SCHEMA VALIDATION IS OMITTED:
Apple quantity is 3
Banana quantity is 5
javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 24; cvc-complex-type.2.4.d: Invalid content was found starting with element 'apple'. No child element is expected at this point.]
    at ...

As you can see the Jaxb2Marshaller forgot to validate my invalid XML file while the Unmarshaller could validate it.

Can anyone give me a clue what could be wrong with my code?

like image 857
attila-fazekas Avatar asked Dec 26 '13 09:12

attila-fazekas


1 Answers

I had the same problem when configuring Jaxb2Marshaller at runtime. The proposed solution to set the schema via Spring XML got me thinking: What's the difference between my code setting the property and Spring?

The answer is simple: Jaxb2Marshaller implements org.springframework.beans.factory.InitializingBean which defines the lifecycle method afterPropertiesSet() and this method is called by Springs BeanFactory after the properties have been set.

So the Solution is to call afterPropertiesSet() after setSchema() has been used to set the schema property. Because in that method the schema property (which is a org.springframework.core.io.Resource array) is actually used to set the internal schema field of type javax.xml.validation.Schema which is later used for schema validation.

Example:

Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
Resource schema = new ClassPathResource("fruit.xsd");
marshaller.setSchema(schema);
marshaller.setContextPath("com.fruit.xml.jaxb");
// manually call Spring lifecycle method which actually loads the schema resource
marshaller.afterPropertiesSet();
// use marshaller...
like image 75
Tobias Meyer Avatar answered Oct 20 '22 09:10

Tobias Meyer