MOXy:DynamicJAXBContext 不正确地处理 XSD 枚举文字 (@XmlEnumValue)
MOXy: DynamicJAXBContext handles XSD enum literals (@XmlEnumValue) incorrectly
问题: 使用 DynamicJAXBContext
时,MOXy 生成的枚举映射(使用 2.7.4、2.7.5 测试)显示出不希望的(明显错误的)行为:
- XML 源中预期的值对应于
the java.lang.Enum.name()
,而不是 XSD 中定义的文字。
例如:给定 XSD 枚举文字 fooValue
,MOXy 期望 FOO_VALUE
.
- 即使在 XML 源中使用了
java.lang.Enum.name()
(这已经很老套了!),动态生成的 java.lang.Enum
常量也缺少 @XmlEnumValue
注释。这导致在编组时生成无效的 XML:考虑到前面的示例,Marshaller
将写入 FOO_VALUE
而不是 fooValue
。
问题:有什么方法可以改善这种行为吗?我可以忍受问题 1,但问题 2 使 MOXy 对我来说完全无法使用。
复制:
JUnit-Test(为简洁起见省略了导入)(失败):
@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>
进一步研究:
我仍然可以在 EnumTypeInfo
上找到 @XmlEnumValue
注释的最新时间点是 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;
}
此时的问题似乎是,fieldNames
和 xmlEnumValues
都只包含一个值:value
。这是非常无用的,考虑到此时通过在 com.sun.codemodel.JDefinedClass.enumConstantsByName
中使用正确注释的 JEnumConstant
s 可以获得正确的值。由于此方法创建的映射现在只映射 "value"
到 "value"
,因此缺失值在稍后的时间点映射,这里在 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);
}
撇开序数值的使用,映射现在将在一侧使用 java.lang.Enum.name()
s(-> 问题 1)和 java.lang.Enum
它本身就在另一边。但是,由于 java.lang.Enum
的字段(即 java.lang.Class.getFields()
)缺少 @XmlEnumValue
注释,编组器创建的 XML 也将包含 java.lang.Enum.name()
(-> 问题2)
改变 EnumSchemaInstance.xml
中的 XML 以包含 java.lang.Enum.name()
,i。即:
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://acme.com/test">
<testEnumValue>FOO_VALUE</testEnumValue>
</test>
导致java.lang.Enum
被映射找到,进一步验证问题1。现在,如果JAXBElement<DynamicEntity> dynamicEntity
像这样再次编组:
JAXBMarshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(JAXBMarshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(dynamicEntity, System.out);
XML输出显示java.lang.Enum.name()
,进一步验证问题2:
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://acme.com/test">
<testEnumValue>FOO_VALUE</testEnumValue>
</test>
编辑: Link 到 EclipseLink Bugzilla:https://bugs.eclipse.org/bugs/show_bug.cgi?id=552902
TL;DR: 不可能没有 adjusting/rewriting SchemaMetadata
, DynamicClassLoader
和 DynamicClassWriter
, 这是很多工作。
完整版:
org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo)
最终被DynamicClassLoader
调用只知道一个EnumInfo
对象,这个对象是在前者中定义的。 EnumInfo
对注释一无所知,只知道 class 名称和枚举文字
public static class EnumInfo {
String className;
List<String> literalLabels = new ArrayList<String>();
...
}
MOXy 的(间接)调用部分是 org.eclipse.persistence.jaxb.dynamic.metadata.SchemaMetadata.createClassModelFromXJC(ArrayList<JDefinedClass>, JCodeModel, DynamicClassLoader)
,其中存储在 JEnumConstant
中的注释信息丢失了:
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);
}
为了保留注释,存储在 JEnumConstant
的注释信息需要在 DynamicClassWriter
中可用(例如通过扩展 EnumInfo
)和实现的 org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo)
需要调整以编写表示注释的字节代码。
问题: 使用 DynamicJAXBContext
时,MOXy 生成的枚举映射(使用 2.7.4、2.7.5 测试)显示出不希望的(明显错误的)行为:
- XML 源中预期的值对应于
the java.lang.Enum.name()
,而不是 XSD 中定义的文字。 例如:给定 XSD 枚举文字fooValue
,MOXy 期望FOO_VALUE
. - 即使在 XML 源中使用了
java.lang.Enum.name()
(这已经很老套了!),动态生成的java.lang.Enum
常量也缺少@XmlEnumValue
注释。这导致在编组时生成无效的 XML:考虑到前面的示例,Marshaller
将写入FOO_VALUE
而不是fooValue
。
问题:有什么方法可以改善这种行为吗?我可以忍受问题 1,但问题 2 使 MOXy 对我来说完全无法使用。
复制:
JUnit-Test(为简洁起见省略了导入)(失败):
@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>
进一步研究:
我仍然可以在 EnumTypeInfo
上找到 @XmlEnumValue
注释的最新时间点是 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;
}
此时的问题似乎是,fieldNames
和 xmlEnumValues
都只包含一个值:value
。这是非常无用的,考虑到此时通过在 com.sun.codemodel.JDefinedClass.enumConstantsByName
中使用正确注释的 JEnumConstant
s 可以获得正确的值。由于此方法创建的映射现在只映射 "value"
到 "value"
,因此缺失值在稍后的时间点映射,这里在 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);
}
撇开序数值的使用,映射现在将在一侧使用 java.lang.Enum.name()
s(-> 问题 1)和 java.lang.Enum
它本身就在另一边。但是,由于 java.lang.Enum
的字段(即 java.lang.Class.getFields()
)缺少 @XmlEnumValue
注释,编组器创建的 XML 也将包含 java.lang.Enum.name()
(-> 问题2)
改变 EnumSchemaInstance.xml
中的 XML 以包含 java.lang.Enum.name()
,i。即:
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://acme.com/test">
<testEnumValue>FOO_VALUE</testEnumValue>
</test>
导致java.lang.Enum
被映射找到,进一步验证问题1。现在,如果JAXBElement<DynamicEntity> dynamicEntity
像这样再次编组:
JAXBMarshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(JAXBMarshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(dynamicEntity, System.out);
XML输出显示java.lang.Enum.name()
,进一步验证问题2:
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://acme.com/test">
<testEnumValue>FOO_VALUE</testEnumValue>
</test>
编辑: Link 到 EclipseLink Bugzilla:https://bugs.eclipse.org/bugs/show_bug.cgi?id=552902
TL;DR: 不可能没有 adjusting/rewriting SchemaMetadata
, DynamicClassLoader
和 DynamicClassWriter
, 这是很多工作。
完整版:
org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo)
最终被DynamicClassLoader
调用只知道一个EnumInfo
对象,这个对象是在前者中定义的。 EnumInfo
对注释一无所知,只知道 class 名称和枚举文字
public static class EnumInfo {
String className;
List<String> literalLabels = new ArrayList<String>();
...
}
MOXy 的(间接)调用部分是 org.eclipse.persistence.jaxb.dynamic.metadata.SchemaMetadata.createClassModelFromXJC(ArrayList<JDefinedClass>, JCodeModel, DynamicClassLoader)
,其中存储在 JEnumConstant
中的注释信息丢失了:
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);
}
为了保留注释,存储在 JEnumConstant
的注释信息需要在 DynamicClassWriter
中可用(例如通过扩展 EnumInfo
)和实现的 org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo)
需要调整以编写表示注释的字节代码。