The Case for Enums
Enums are a common case in Web service API design. The need is to specify a set of valid values for a attribute or element.
For example, consider how one might define a 'Car' type. This is how it might look in XML Schema:
This is easy to support in Java using standard JAXB annotations:
The Problem
The problem surfaces when the need arises to add an additional enum value to an existing API. It's easy enough to add into the code and schema, but it would break backwards compatibility because consumers of the API would be required to "know about" the new enum value. What's needed is an "extensible" way to add enum values such that adding a new value doesn't break backwards compatibility.
QName: The Established Pattern
The W3C had the same problem when they were designing XML (and XML Schema). The pattern that was established was to leverage the use of QNames. For the case of XML Schema, there was an established set of "known" schema types (e.g. "string", "int", "anyType", etc.). The specification of these types was defined using QNames.
A QName is really just a namespace-name pairing. "Known" types can be listed in an enumeration, but the attribute or element is defined of type '{http://www.w3.org/2001/XMLSchema}QName'. Then, the list of "known" types can be updated without breaking backwards compatibility.
Unfortunately, JAXB doesn't define a good way to supply QName enums. And it would be really unfortunate to lose the convenience of consuming the Java objects using type-safe enums.
Enunciate Provisions
Enunciate provides a means to specify QName enums in a way that is compatible with JAXB and allows API providers to work with type-safe enums.
A standard Java type-safe enum is annotated with org.codehaus.enunciate.qname.XmlQNameEnum, indicating to Enunciate that this enum is defining a list of "known" QNames. Each enum value can optionally be annotated with org.codehaus.enunciate.qname.XmlQNameEnumValue to futher customize the QName value, such as indicating a different namespace and/or local part. The XmlQNameEnumValue annotation can also be used to exclude an enum from the "known" QNames.
To support conversion to type-safe enums from QNames that are unknown, the annotation org.codehaus.enunciate.qname.XmlUnknownQNameEnumValue can be applied to an enum constant, indicating that this enum constant should be used for all QNames that are "unknown".
The referencing class defines a property that is exposed as a QName that is (optionally) annotated with org.codehaus.enunciate.qname.XmlQNameEnumRef to indicate to Enunciate that the type of the property is to be treated as a special QName enum with a list of known QNames. The @XmlQNameEnumRef will tell Enunciate to document the property with the known QName types and to generate special accessors in the client-side code that map to the appropriate enum value.
For convenience, developers can add an additional property, marked with @XmlTransient that exposes the type-safe enum. Enunciate provides a utility class, org.codehaus.enunciate.XmlQNameEnumUtil that will convert a QName enum to/from a QName. When converting from a QName to an enum constant, the utility class will return the known enum constant if the QName is a "known" value, otherwise it will return the enum constant that is annotated with @XmlUnknownQNameEnumValue. If no constant is annotated with @XmlUnknownQNameEnumValue it will return null.
It is illegal to try to convert an enum constant that is annotated with |
Here is how the Java code would look:
And this is how the Enunciate-generated XML Schema would look:
And here's a table of how the conversions to QName would go:
enum constant |
QName |
|---|---|
Make.chevy |
{urn:cars}chevrolet |
Make.ford |
{urn:cars}ford |
Make.toyota |
{urn:cars:foreign}toyota |
Make.honda |
{urn:cars:foreign}honda |
Make.hyundai |
{urn:cars:foreign}hyundai |
Make.gm |
{urn:cars}gm |
Make.other |
illegal |
And here's a table of how QNames would be converted to the enum constants:
QName |
enum constant |
|---|---|
{urn:cars}chevrolet |
Make.chevy |
{urn:cars}ford |
Make.ford |
{urn:cars:foreign}toyota |
Make.toyota |
{urn:cars:foreign}honda |
Make.honda |
{urn:cars:foreign}hyundai |
Make.hyundai |
{urn:cars}gm |
Make.gm |
{urn:cars:foreign}bmw |
Make.other |
{urn:cars}tesla |
Make.other |
{urn:cars}chevy |
Make.other |
QNames as URI
As of Enunciate 1.24, you don't have to use java.util.QName to represent your QName. You can also use java.net.URI, which is a common pattern among APIs that define non-XML representations (e.g. RDF . XmlQNameEnumUtil provides methods for converting to/from a URI, which will manage the URI in the form of the QName localPart appended to the namespaceURI.
Just make sure your annotation specifies the right QName representation:
