Problem: When using DynamicJAXBContext
, mappings for enums generated by MOXy (tested with 2.7.4, 2.7.5) show undesired (plain wrong) behaviour:
the java.lang.Enum.name()
s, not the literals defined in the XSD.
E. g.: Given the XSD enum literal fooValue
, MOXy expects FOO_VALUE
.java.lang.Enum.name()
s are used in the XML source (which is already hacky!), the dynamically generated java.lang.Enum
constants lack @XmlEnumValue
annotations. This leads to invalid XML being generated when marshalling: Considering the previous example, the Marshaller
would write FOO_VALUE
instead of fooValue
.Question: Is there any way to change this behaviour for the better? I could live with Problem 1, but Problem 2 renders MOXy completely unusable for me.
Reproduction:
JUnit-Test (imports omitted for brevity) (failing):
@Test
public void test_xmlEnumValue() throws Exception {
String resourcesBasePath = "src/test/resources/enums/";
FileInputStream xsdInputStream = new FileInputStream(resourcesBasePath + "EnumSchema.xsd");
DynamicJAXBContext jaxbContext = DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, null, null, null);
JAXBUnmarshaller unmarshaller = jaxbContext.createUnmarshaller();
File inputFile = new File(resourcesBasePath + "EnumSchemaInstance.xml");
StreamSource xmlInputStreamSource = new StreamSource(inputFile);
JAXBElement<DynamicEntity> dynamicEntity = (JAXBElement<DynamicEntity>) unmarshaller.unmarshal(xmlInputStreamSource);
Enum testEnumValue = (Enum) dynamicEntity.getValue().get("testEnumValue");
assertThat(testEnumValue.name(), IsEqual.equalTo("FOO_VALUE"));
}
EnumSchema.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://acme.com/test" targetNamespace="http://acme.com/test"
elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0">
<xs:element name="test" type="tns:TestType" />
<xs:complexType name="TestType">
<xs:sequence>
<xs:element name="testEnumValue" type="tns:TestEnum" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name="TestEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="fooValue" />
<xs:enumeration value="Bar_Value" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
EnumSchemaInstance.xml:
<test xmlns="http://acme.com/test">
<testEnumValue>fooValue</testEnumValue>
</test>
Further research:
The latest point in time, at which I could still find the @XmlEnumValue
annotations on EnumTypeInfo
s is org.eclipse.persistence.jaxb.compiler.MappingsGenerator.buildJAXBEnumTypeConverter(Mapping, EnumTypeInfo)
:
private JAXBEnumTypeConverter buildJAXBEnumTypeConverter(Mapping mapping, EnumTypeInfo enumInfo){
JAXBEnumTypeConverter converter = new JAXBEnumTypeConverter(mapping, enumInfo.getClassName(), false);
List<String> fieldNames = enumInfo.getFieldNames();
List<Object> xmlEnumValues = enumInfo.getXmlEnumValues();
for (int i=0; i< fieldNames.size(); i++) {
converter.addConversionValue(xmlEnumValues.get(i), fieldNames.get(i));
}
return converter;
}
the problem at this point seems to be, that fieldNames
and xmlEnumValues
both only contain exactly one value: value
. This is pretty useless, considering that the correct values would be available at this point by using the correctly annotated JEnumConstant
s in com.sun.codemodel.JDefinedClass.enumConstantsByName
instead. Since the mapping created by this method now only maps "value"
to "value"
, the missing values are mapped at a later point in time, here in org.eclipse.persistence.jaxb.JAXBEnumTypeConverter.initialize(DatabaseMapping, Session)
:
public void initialize(DatabaseMapping mapping, Session session) {
Iterator<Enum> i = EnumSet.allOf(m_enumClass).iterator();
while (i.hasNext()) {
Enum theEnum = i.next();
if (this.getAttributeToFieldValues().get(theEnum) == null) {
Object existingVal = this.getAttributeToFieldValues().get(theEnum.name());
if (existingVal != null) {
this.getAttributeToFieldValues().remove(theEnum.name());
addConversionValue(existingVal, theEnum);
} else {
// if there's no user defined value, create a default
if (m_usesOrdinalValues) {
addConversionValue(theEnum.ordinal(), theEnum);
} else {
addConversionValue(theEnum.name(), theEnum);
}
}
}
}
super.initialize(mapping, session);
}
Leaving the use of ordinal values aside, the mapping will now use java.lang.Enum.name()
s on one side (-> Problem 1) and the java.lang.Enum
s itself on the other side. But, since the fields (i. e. java.lang.Class.getFields()
) of the java.lang.Enum
s lack @XmlEnumValue
annotations, the XML created by the marshaller will also contain java.lang.Enum.name()
s (-> Problem 2)
Chaning the XML in EnumSchemaInstance.xml
to contain the java.lang.Enum.name()
, i. e.:
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://acme.com/test">
<testEnumValue>FOO_VALUE</testEnumValue>
</test>
leads to the java.lang.Enum
being found by the mapping, further verifying Problem 1. Now, if the JAXBElement<DynamicEntity> dynamicEntity
is marshalled again like this:
JAXBMarshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(JAXBMarshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(dynamicEntity, System.out);
The XML output shows the java.lang.Enum.name()
, further verifying Problem 2:
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://acme.com/test">
<testEnumValue>FOO_VALUE</testEnumValue>
</test>
EDIT: Link to EclipseLink Bugzilla: https://bugs.eclipse.org/bugs/show_bug.cgi?id=552902
TL;DR: It's not possible without adjusting/rewriting SchemaMetadata
, DynamicClassLoader
and DynamicClassWriter
, which is a lot of work.
Full version:
org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo)
ultimately invoked by DynamicClassLoader
only knows about an EnumInfo
object, which is defined in the former. The EnumInfo
knows nothing about annotations, only the class name and the enum literals
public static class EnumInfo {
String className;
List<String> literalLabels = new ArrayList<String>();
...
}
The (indirectly) invoking part of MOXy is org.eclipse.persistence.jaxb.dynamic.metadata.SchemaMetadata.createClassModelFromXJC(ArrayList<JDefinedClass>, JCodeModel, DynamicClassLoader)
, where the annotation information stored in the JEnumConstant
s is lost:
if (definedClass.getClassType().equals(ClassType.ENUM)) {
Map<String, JEnumConstant> enumConstants = (Map<String, JEnumConstant>) PrivilegedAccessHelper.getValueFromField(JDEFINEDCLASS_ENUMCONSTANTS, definedClass);
Object[] enumValues = enumConstants.keySet().toArray();
dynamicClassLoader.addEnum(definedClass.fullName(), enumValues);
}
In order for the annotations to be preserverd, the annotation information stored at the JEnumConstant
would need to be available in the DynamicClassWriter
(e. g. by extending EnumInfo
) and the implementation of org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo)
would need to be adjusted to write the byte code representing the annotations.