MOXy: DynamicJAXBContext неправильно обрабатывает литералы перечисления XSD (@XmlEnumValue) - PullRequest
0 голосов
/ 11 ноября 2019

Проблема: При использовании DynamicJAXBContext сопоставления для перечислений, сгенерированных MOXy (протестировано с 2.7.4, 2.7.5), показывают нежелательное (просто неверное) поведение:

  1. Значения, ожидаемые в источнике XML, соответствуют the java.lang.Enum.name() с, а не литералам, определенным в XSD. Например: учитывая литерал перечисления XSD fooValue, MOXy ожидает, что FOO_VALUE.
  2. Даже если java.lang.Enum.name() s используются в источнике XML (который уже взломан!), Динамически генерируемый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>

Дальнейшие исследования:

Самым поздним моментом, когда я все еще мог найти аннотации @XmlEnumValue на EnumTypeInfo s, является 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. Это довольно бесполезно, учитывая, что правильные значения будут доступны в этот момент, если вместо них использовать правильно аннотированные JEnumConstant s в com.sun.codemodel.JDefinedClass.enumConstantsByName. Поскольку отображение, созданное этим методом, теперь отображает только "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 s с другой стороны. Но поскольку в полях (то есть java.lang.Class.getFields()) java.lang.Enum s отсутствуют аннотации @XmlEnumValue, созданный маршаллером XML также будет содержать java.lang.Enum.name() s ( -> Problem 2 )

Изменение XML в EnumSchemaInstance.xml на java.lang.Enum.name(), то есть:

<?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>

РЕДАКТИРОВАТЬ: Ссылка на EclipseLink Bugzilla: https://bugs.eclipse.org/bugs/show_bug.cgi?id=552902

1 Ответ

0 голосов
/ 11 ноября 2019

TL; DR: Невозможно без настройки / перезаписи SchemaMetadata, DynamicClassLoader и DynamicClassWriter, что является большой работой.

Полная версия: org.eclipse.persistence.dynamic.DynamicClassWriter.createEnum(EnumInfo), в конечном счете, вызываемый DynamicClassLoader, знает только об объекте EnumInfo, который определен в первом. EnumInfo ничего не знает об аннотациях, только имя класса и литералы перечисления

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 sпотеряно:

        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) должна быть скорректирована для записи байтового кода, представляющего аннотации.

...