Handling invalid enum values while doing JAXB Unmarshalling
Asked Answered
G

3

11

My Jaxb has created a Enum class based on the XML schema set up.

**enum Fruit {
    APPLE,ORANGE;
}**

I am using a SOAP UI to check my web service. Since it is a free form entry, if i give a wrong fruit say "Guva" then instead of throwing an exception it is returning it as null after doing the UnMarshalling.

How can i avoid this? Should i use custom enum class instead of JAXB generated one. Please give some example. i.e.

regards sri

Geostatic answered 27/8, 2012 at 18:16 Comment(0)
N
22

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

By default your JAXB (JSR-222) implementation will not fail on any conversion exceptions. If you are using the JAXB APIs to do the unmarshalling then you can set a ValidationEventHandler to catch any problems. Below is an example.

Root

package forum12147306;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Root {

    private int number;
    private Fruit fruit;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public Fruit getFruit() {
        return fruit;
    }

    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }

}

Fruit

package forum12147306;

public enum Fruit {

    APPLE, 
    ORANGE;

}

Demo

package forum12147306;

import java.io.StringReader;
import javax.xml.bind.*;

public class Demo {

    private static final String XML = "<root><number>ABC</number><fruit>Guva</fruit></root>";
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setEventHandler(new ValidationEventHandler() {

            @Override
            public boolean handleEvent(ValidationEvent validationEvent) {
                 System.out.println(validationEvent.getMessage());
                 //validationEvent.getLinkedException().printStackTrace();
                 return true;
            }

        });

        Root root = (Root) unmarshaller.unmarshal(new StringReader(XML));
    }

}

JAXB REFERENCE IMPLEMENTATION

Unfortunately there appears to be a bug in the JAXB RI as a validation event is not being through for the invalid enum value.

Not a number: ABC

Work Around

Write your own XmlAdapter to handle to conversion to/from the Fruit enum:

FruitAdapter

package forum12147306;

import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class FruitAdapter extends XmlAdapter<String, Fruit> {

    @Override
    public String marshal(Fruit fruit) throws Exception {
        return fruit.name();
    }

    @Override
    public Fruit unmarshal(String string) throws Exception {
        try {
            return Fruit.valueOf(string);
        } catch(Exception e) {
            throw new JAXBException(e);
        }
    }

}

Fruit

Use the @XmlJavaTypeAdapter annotation to associate the XmlAdapter with the Fruit enumb.

package forum12147306;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlJavaTypeAdapter(FruitAdapter.class)
public enum Fruit {

    APPLE, 
    ORANGE;

}

New Output

Not a number: ABC
javax.xml.bind.JAXBException
 - with linked exception:
[java.lang.IllegalArgumentException: No enum const class forum12147306.Fruit.Guva]

EclipseLink JAXB (MOXy)

Using MOXy both validation events are thrown. To specify MOXy as your JAXB provider see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html.

Exception Description: The object [ABC], of class [class java.lang.String], from mapping [org.eclipse.persistence.oxm.mappings.XMLDirectMapping[number-->number/text()]] with descriptor [XMLDescriptor(forum12147306.Root --> [DatabaseTable(root)])], could not be converted to [class java.lang.Integer].
Internal Exception: java.lang.NumberFormatException: For input string: "ABC"

Exception Description: No conversion value provided for the value [Guva] in field [fruit/text()].
Mapping: org.eclipse.persistence.oxm.mappings.XMLDirectMapping[fruit-->fruit/text()]
Descriptor: XMLDescriptor(forum12147306.Root --> [DatabaseTable(root)])
Neurath answered 27/8, 2012 at 19:16 Comment(4)
thanks, very much for the detailed example. "...there appears to be a bug in the JAXB" - This behaviour still exists. Maybe mismatching enums are not critical errors :-) Your 'workaround' works fine. I am using this design for non-enums as well. Really great!Swiss
Blaise Doughan, had anything changed in MOXy since you posted this answer? I can't seem to get MOXy 2.6.2 to fire up events on unknown enum values in XML or Json. Do I have to use XmlJavaTypeAdapter with MOXy too?Winther
And the bug still exists in JAXB! ThanksUdo
How can I use this solution with Spring, using only annotations?Taurus
C
2

Short answer based on original reply. You need to do 2 things

  1. implement custom adapter to raise and exception
  2. add event handler to fail unmarshalling

Fruit.java defines and uses the adapter

package forum12147306;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlJavaTypeAdapter(FruitAdapter.class)
public enum Fruit {

    APPLE, 
    ORANGE;

}

class FruitAdapter extends XmlAdapter<String, Fruit> {

    @Override
    public String marshal(Fruit fruit) throws Exception {
        return fruit.name();
    }

    @Override
    public Fruit unmarshal(String string) throws Exception {
        try {
            return Fruit.valueOf(string);
        } catch(Exception e) {
            throw new JAXBException(e);
        }
    }
}

The event handler for unmarshaller that fails parsing on error - i.e it returns false (you might need to decide when to fail and when not to fail)

    JAXBContext jc = JAXBContext.newInstance(Root.class);
    Unmarshaller unmarshaller = jc.createUnmarshaller();
    unmarshaller.setEventHandler(new ValidationEventHandler() {
        @Override
        public boolean handleEvent(ValidationEvent validationEvent) {
             return false;
        }
    });
Cowles answered 30/5, 2014 at 15:40 Comment(0)
Y
0

An alternative consists in generating XSD schemas as presented in here: how can i unmarshall in jaxb and enjoy the schema validation without using an explicit schema file.

Here is the snippet I stole from dolbysurnd:

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.SAXException;

private static List<DOMResult> generateJaxbSchemas(JAXBContext context) throws IOException {
    final List<DOMResult> domResultList = new ArrayList<>();
    context.generateSchema(new SchemaOutputResolver() {
        @Override
        public Result createOutput(String ns, String file) throws IOException {
            DOMResult domResult = new DOMResult();
            domResult.setSystemId(file);
            domResultList.add(domResult);
            return domResult;
        }
    });
    return domResultList;
}

private static Unmarshaller createUnmarshaller(JAXBContext context) throws SAXException, IOException, JAXBException {
    Unmarshaller unmarshaller = context.createUnmarshaller();
    List<DOMSource> domSourceList = new ArrayList<>();
    for (DOMResult domResult : generateJaxbSchemas(context)) {
        domSourceList.add(new DOMSource(domResult.getNode()));
    }
    SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
    Schema schema = schemaFactory.newSchema(domSourceList.toArray(new DOMSource[0]));
    unmarshaller.setSchema(schema);
    return unmarshaller;
}

public void unmarshal(JAXBContext context, Reader reader) throws JAXBException, SAXException, IOException {
    Unmarshaller unmarshaller = createUnmarshaller(context);
    Object result = unmarshaller.unmarshal(reader);
}
Yielding answered 10/3, 2015 at 10:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.