August 24, 2011

JAXB and Enums

Today I answered a question on Stack Overflow (feel free to up vote) about JAXB and generating Java enums from an XML Schema.  This is normally straight forward, but there are a couple "gotchas" to be aware of.  In this post I'll demonstrate an easy and a harder use case.


XML Schema (enums.xsd)

The following XML schema will be used for this example.  Note how it contains two enumerations:  status-type and education-level-type.

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

 <xs:element name="person">
  <xs:complexType>
   <xs:sequence>
    <xs:element name="membership" type="status-type" />
    <xs:element name="education-level" type="education-level-type" />
   </xs:sequence>
  </xs:complexType>
 </xs:element>

 <xs:simpleType name="status-type">
  <xs:restriction base="xs:string">
   <xs:enumeration value="active"/>
   <xs:enumeration value="inactive"/>
  </xs:restriction>
 </xs:simpleType>

 <xs:simpleType name="education-level-type">
  <xs:restriction base="xs:string">
   <xs:enumeration value="1-6"/>
   <xs:enumeration value="6-12"/>
   <xs:enumeration value="post secondary"/>
   <xs:enumeration value="college"/>
   <xs:enumeration value=""/>
  </xs:restriction>
 </xs:simpleType>

</xs:schema>

Generated Model

XJC Call

We will use JAXB to generate an object model from the XML schema.  The following command was used:

xjc -d out enums.xsd

Person

Below is one of the generated classes. Note that an enum was generated for status-type but not for education-level-type.

package org.example._enum;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "membership",
    "educationLevel"
})
@XmlRootElement(name = "person")
public class Person {

    @XmlElement(required = true)
    protected StatusType membership;

    @XmlElement(name = "education-level", required = true)
    protected String educationLevel;

    public StatusType getMembership() {
        return membership;
    }

    public void setMembership(StatusType value) {
        this.membership = value;
    }

    public String getEducationLevel() {
        return educationLevel;
    }

    public void setEducationLevel(String value) {
        this.educationLevel = value;
    }

}

What Happened?

JAXB did not know how to convert the some of the XML enumeration values to Java enum values so it made the field correspond to the base type of the enumeration. This can happen if the enumeration value starts with a number or is the empty String.

<xs:simpleType name="education-level-type">
  <xs:restriction base="xs:string">
   <xs:enumeration value="1-6"/>
   <xs:enumeration value="6-12"/>
   <xs:enumeration value="post secondary"/>
   <xs:enumeration value="college"/>
   <xs:enumeration value=""/>
  </xs:restriction>
 </xs:simpleType>

What Can be Done?

We can have JAXB generate a Java enum for the XML enumeration education-level-type, by providing a schema bindings file to supply valid enum values.

bindings.xml

<jxb:bindings 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    version="2.1">
    <jxb:bindings schemaLocation="enums.xsd">
        <jxb:bindings node="//xs:simpleType[@name='education-level-type']/xs:restriction/xs:enumeration[@value='1-6']">
            <jxb:typesafeEnumMember name="ONE_TO_SIX"/>
        </jxb:bindings>
        <jxb:bindings node="//xs:simpleType[@name='education-level-type']/xs:restriction/xs:enumeration[@value='6-12']">
            <jxb:typesafeEnumMember name="SIX_TO_TWELVE"/>
        </jxb:bindings>
        <jxb:bindings node="//xs:simpleType[@name='education-level-type']/xs:restriction/xs:enumeration[@value='']">
            <jxb:typesafeEnumMember name="BLANK"/>
        </jxb:bindings>
    </jxb:bindings>
</jxb:bindings>

XJC Call

The schema bindings file is included in the XJC call as follows:

xjc -d out -b bindings.xml enums.xsd

Person

Now the field corresponding to the XML enumeration education-level-type corresponds to the Java enum EducationLevelType.

package org.example._enum;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "membership",
    "educationLevel"
})
@XmlRootElement(name = "person")
public class Person {

    @XmlElement(required = true)
    protected StatusType membership;

    @XmlElement(name = "education-level", required = true)
    protected EducationLevelType educationLevel;

    public StatusType getMembership() {
        return membership;
    }

    public void setMembership(StatusType value) {
        this.membership = value;
    }

    public EducationLevelType getEducationLevel() {
        return educationLevel;
    }

    public void setEducationLevel(EducationLevelType value) {
        this.educationLevel = value;
    }

}

EducationLevelType

package org.example._enum;

import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "education-level-type")
@XmlEnum
public enum EducationLevelType {

    @XmlEnumValue("1-6")
    ONE_TO_SIX("1-6"),

    @XmlEnumValue("6-12")
    SIX_TO_TWELVE("6-12"),

    @XmlEnumValue("post secondary")
    POST_SECONDARY("post secondary"),

    @XmlEnumValue("college")
    COLLEGE("college"),

    @XmlEnumValue("")
    BLANK("");

    private final String value;

    EducationLevelType(String v) {
        value = v;
    }

    public String value() {
        return value;
    }

    public static EducationLevelType fromValue(String v) {
        for (EducationLevelType c: EducationLevelType.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }

}

Further Reading

If you enjoyed this post, then you may also be interested in:

4 comments:

  1. To solve the numeric enumeration values (eg: 1001, 1002 etc) used in the XSD, I used
    <globalBindings typesafeEnumMemberName="generateName" />
    In my bindings file (.xjb) and the generated enum constants came out VALUE_1001 , VALUE_1002, etc

    ReplyDelete
  2. mikej1688@gmail.comMay 9, 2012 at 11:53 PM

    This comment has been removed by a blog administrator.

    ReplyDelete
  3. Hi Blaise,

    I have a problem with Eclipse and conversion from xsd schema to java class.
    I've used your example below:











    The problem is, if I have only number in value, for example:

    The Enum class is not being generated. If I delete rows:



    than everything generates OK. Do you have any suggestion?

    Thank you in advance,
    Tomy

    ReplyDelete
    Replies
    1. Your XML schema didn't survive the comment section, but the same bindings file approach will work for use case. Is this your Stack Overflow question:
      - http://stackoverflow.com/questions/18617825/converting-xsd-enum-to-java-class-in-eclipse

      Delete

Note: Only a member of this blog may post a comment.