From 580c7bd3fcd5a638f2bac1f67b19f9759bee7419 Mon Sep 17 00:00:00 2001 From: Tim Tattersall Date: Thu, 26 Sep 2024 16:17:11 +1000 Subject: [PATCH 1/2] Handle xjc XmlEnumValue in DynamicJAXB enum --- .../dynamic/DynamicClassLoader.java | 31 +++++---- .../dynamic/DynamicClassWriter.java | 20 ++++-- .../dynamic/DynamicEnumBuilder.java | 6 +- .../dynamic/DynamicTypeBuilder.java | 2 +- .../jaxb/JAXBEnumTypeConverter.java | 17 ++++- .../jaxb/dynamic/metadata/SchemaMetadata.java | 68 ++++++++++++------- .../dynamic/DynamicJAXBFromXSDTestCases.java | 23 +++++++ .../testing/jaxb/dynamic/xmlenum-numbers.xsd | 37 ++++++++++ 8 files changed, 154 insertions(+), 50 deletions(-) create mode 100644 moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java index 12cfa8f3b57..76a6761e219 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java @@ -29,10 +29,10 @@ import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.sessions.Session; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * This custom ClassLoader provides support for dynamically generating classes @@ -96,17 +96,20 @@ public EclipseLinkClassWriter getClassWriter(String className) { } public void addEnum(String className, Object... literalLabels) { + Map literalsMap = Arrays.stream(literalLabels) + .collect(Collectors.toMap(Object::toString, Object::toString)); + + addEnum(className, literalsMap); + } + + public void addEnum(String className, Map literalLabels) { EnumInfo enumInfo = enumInfoRegistry.get(className); if (enumInfo == null) { enumInfo = new EnumInfo(className); enumInfoRegistry.put(className, enumInfo); } if (literalLabels != null) { - for (Object literalLabel : literalLabels) { - if (literalLabel != null) { - enumInfo.addLiteralLabel(literalLabel.toString()); - } - } + literalLabels.forEach(enumInfo::addLiteralLabel); } addClass(className); } @@ -292,7 +295,7 @@ public static DynamicClassLoader lookup(Session session) { public static class EnumInfo { String className; - List literalLabels = new ArrayList<>(); + Map literalLabels = new HashMap<>(); public EnumInfo(String className) { this.className = className; @@ -302,13 +305,17 @@ public String getClassName() { return className; } + public Map getEnumValues() { + return literalLabels; + } + public String[] getLiteralLabels() { - return literalLabels.toArray(new String[0]); + return literalLabels.keySet().toArray(new String[0]); } - public void addLiteralLabel(String literalLabel) { - if (!literalLabels.contains(literalLabel) && literalLabel != null) { - literalLabels.add(literalLabel); + public void addLiteralLabel(String literalLabel, String value) { + if (!literalLabels.containsKey(literalLabel) && literalLabel != null) { + literalLabels.put(literalLabel, value); } } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java index 4d569450f0b..60a7535ed0a 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java @@ -20,8 +20,10 @@ //static imports +import org.eclipse.persistence.asm.AnnotationVisitor; import org.eclipse.persistence.asm.ClassWriter; import org.eclipse.persistence.asm.EclipseLinkASMClassWriter; +import org.eclipse.persistence.asm.FieldVisitor; import org.eclipse.persistence.asm.MethodVisitor; import org.eclipse.persistence.asm.Opcodes; import org.eclipse.persistence.asm.Type; @@ -33,6 +35,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static org.eclipse.persistence.internal.dynamic.DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD; @@ -223,7 +226,7 @@ protected void addMethods(ClassWriter cw, String parentClassType) { protected byte[] createEnum(EnumInfo enumInfo) { - String[] enumValues = enumInfo.getLiteralLabels(); + Map enumValues = enumInfo.getEnumValues(); String className = enumInfo.getClassName(); String internalClassName = className.replace('.', '/'); @@ -232,8 +235,10 @@ protected byte[] createEnum(EnumInfo enumInfo) { cw.visit(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER + Opcodes.ACC_ENUM, internalClassName, null, "java/lang/Enum", null); // Add the individual enum values - for (String enumValue : enumValues) { - cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC + Opcodes.ACC_ENUM, enumValue, "L" + internalClassName + ";", null, null); + for (Map.Entry enumValue : enumValues.entrySet()) { + FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC + Opcodes.ACC_ENUM, enumValue.getKey(), "L" + internalClassName + ";", null, null); + AnnotationVisitor av = fv.visitAnnotation("Ljakarta/xml/bind/annotation/XmlEnumValue;", true); + av.visit("value", enumValue.getValue()); } // add the synthetic "$VALUES" field @@ -270,8 +275,9 @@ protected byte[] createEnum(EnumInfo enumInfo) { mv = cw.visitMethod(Opcodes.ACC_STATIC, CLINIT, "()V", null, null); int lastCount = 0; - for (int i = 0; i < enumValues.length; i++) { - String enumValue = enumValues[i]; + String[] enumLiterals = enumInfo.getLiteralLabels(); + for (int i = 0; i < enumLiterals.length; i++) { + String enumValue = enumLiterals[i]; mv.visitTypeInsn(Opcodes.NEW, internalClassName); mv.visitInsn(Opcodes.DUP); mv.visitLdcInsn(enumValue); @@ -296,8 +302,8 @@ protected byte[] createEnum(EnumInfo enumInfo) { } mv.visitTypeInsn(Opcodes.ANEWARRAY, internalClassName); - for (int i = 0; i < enumValues.length; i++) { - String enumValue = enumValues[i]; + for (int i = 0; i < enumLiterals.length; i++) { + String enumValue = enumLiterals[i]; mv.visitInsn(Opcodes.DUP); if (i <= 5) { mv.visitInsn(ICONST[i]); diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java index 3877d956398..09381eaf309 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,6 +20,8 @@ import org.eclipse.persistence.mappings.converters.EnumTypeConverter; import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; +import java.util.Map; + public class DynamicEnumBuilder { protected String className; @@ -35,7 +37,7 @@ public DynamicEnumBuilder(String className, AbstractDirectMapping adm, } public void addEnumLiteral(String literalLabel) { - dcl.addEnum(className, literalLabel); + dcl.addEnum(className, Map.of(literalLabel, literalLabel)); EnumTypeConverter converter = (EnumTypeConverter)adm.getConverter(); converter.addConversionValue(literalLabel, literalLabel); } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java index cf6f7a9e9ce..c3f268d729c 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java @@ -515,7 +515,7 @@ public void configureSequencing(Sequence sequence, String numberName, String num public DynamicEnumBuilder addEnum(String fieldName, String className, String columnName, DynamicClassLoader dcl) { - dcl.addEnum(className, (Object)null); + dcl.addEnum(className, (Object) null); AbstractDirectMapping adm = addDirectMappingForEnum(fieldName, className, columnName); return new DynamicEnumBuilder(className, adm, dcl); } diff --git a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java index e5a457c0a53..5d4cadb3732 100644 --- a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java +++ b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,9 +14,12 @@ // Oracle - initial API and implementation from Oracle TopLink package org.eclipse.persistence.jaxb; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.util.EnumSet; import java.util.Iterator; +import jakarta.xml.bind.annotation.XmlEnumValue; import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.oxm.mappings.Mapping; @@ -83,7 +86,7 @@ public void initialize(DatabaseMapping mapping, Session session) { if (m_usesOrdinalValues) { addConversionValue(theEnum.ordinal(), theEnum); } else { - addConversionValue(theEnum.name(), theEnum); + addConversionValue(getEnumValue(theEnum), theEnum); } } } @@ -92,6 +95,16 @@ public void initialize(DatabaseMapping mapping, Session session) { super.initialize(mapping, session); } + private String getEnumValue(Enum theEnum) { + try { + Field field = theEnum.getClass().getField(theEnum.name()); + XmlEnumValue annotation = field.getAnnotation(XmlEnumValue.class); + return annotation != null ? annotation.value() : theEnum.name(); + } catch (NoSuchFieldException exc) { + return theEnum.name(); + } + } + /** * PUBLIC: * Returns true if this converter uses ordinal values for the enum diff --git a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java index 7c7182cbc46..b8380ea8b62 100644 --- a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java +++ b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java @@ -14,20 +14,18 @@ // Blaise Doughan - 2.2 - initial implementation package org.eclipse.persistence.jaxb.dynamic.metadata; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - +import com.sun.codemodel.ClassType; +import com.sun.codemodel.JAnnotationStringValue; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JPackage; +import com.sun.tools.xjc.Plugin; +import com.sun.tools.xjc.api.ErrorListener; +import com.sun.tools.xjc.api.S2JJAXBModel; +import com.sun.tools.xjc.api.SchemaCompiler; +import com.sun.tools.xjc.api.XJC; import jakarta.xml.bind.JAXBException; -import javax.xml.transform.Source; -import javax.xml.transform.TransformerException; -import javax.xml.transform.stream.StreamResult; - +import jakarta.xml.bind.annotation.XmlEnumValue; import org.eclipse.persistence.dynamic.DynamicClassLoader; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory; import org.eclipse.persistence.jaxb.javamodel.JavaClass; @@ -45,20 +43,23 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; -import com.sun.codemodel.ClassType; -import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JDefinedClass; -import com.sun.codemodel.JEnumConstant; -import com.sun.codemodel.JPackage; -import com.sun.tools.xjc.Plugin; -import com.sun.tools.xjc.api.ErrorListener; -import com.sun.tools.xjc.api.S2JJAXBModel; -import com.sun.tools.xjc.api.SchemaCompiler; -import com.sun.tools.xjc.api.XJC; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class SchemaMetadata extends Metadata { private static final String DEFAULT_SYSTEM_ID = "sysid"; + private static final String XML_ENUM_VALUE_VALUE = "value"; private SchemaCompiler schemaCompiler; @@ -216,9 +217,7 @@ private JavaClass[] createClassModelFromXJC(ArrayList xjcClasses, // If this is an enum, trigger a dynamic class generation, because we won't // be creating a descriptor for it if (definedClass.getClassType().equals(ClassType.ENUM)) { - Map enumConstants = definedClass.enumConstants(); - Object[] enumValues = enumConstants.keySet().toArray(); - dynamicClassLoader.addEnum(definedClass.fullName(), enumValues); + dynamicClassLoader.addEnum(definedClass.fullName(), getEnumValues(definedClass)); } } @@ -228,6 +227,23 @@ private JavaClass[] createClassModelFromXJC(ArrayList xjcClasses, } } + private Map getEnumValues(JDefinedClass definedClass) { + return definedClass.enumConstants() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> + entry.getValue() + .annotations() + .stream() + .filter(annotation -> XmlEnumValue.class.getName().equals(annotation.getAnnotationClass().binaryName())) + .map(annotation -> annotation.getAnnotationMembers().get(XML_ENUM_VALUE_VALUE)) + .filter(value -> value instanceof JAnnotationStringValue) + .map(Object::toString) + .findFirst() + .orElse(entry.getKey())) + ); + } + private static InputSource createInputSourceFromSource(Source aSource) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamResult result = new StreamResult(baos); diff --git a/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java b/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java index f77897a5cfb..c07b127094b 100644 --- a/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java +++ b/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java @@ -633,6 +633,26 @@ public void testXmlSchemaType() throws Exception { assertEquals("Unexpected date value.", "1976-02-17", node.getTextContent()); } + public void testXmlEnumWithNumbers() throws Exception { + // Tests XmlEnum and XmlEnumValue + + InputStream inputStream = ClassLoader.getSystemResourceAsStream(XMLENUM_NUMBERS); + jaxbContext = DynamicJAXBContextFactory.createContextFromXSD(inputStream, null, null, null); + + DynamicEntity taxRecord = jaxbContext.newDynamicEntity(PACKAGE + "." + TAX_RECORD); + assertNotNull("Could not create Dynamic Entity.", taxRecord); + + Object QTR_1 = jaxbContext.getEnumConstant(PACKAGE + "." + PERIOD, "QTR_1"); + assertNotNull("Could not find enum constant.", QTR_1); + + taxRecord.set("period", QTR_1); + + Document marshalDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + jaxbContext.createMarshaller().marshal(taxRecord, marshalDoc); + + assertEquals("QTR1", marshalDoc.getDocumentElement().getTextContent()); + } + public void testXmlEnum() throws Exception { // Tests XmlEnum and XmlEnumValue @@ -1068,6 +1088,7 @@ private void print(Object o) throws Exception { private static final String XMLELEMENTREF = RESOURCE_DIR + "xmlelementref.xsd"; private static final String XMLSCHEMATYPE = RESOURCE_DIR + "xmlschematype.xsd"; private static final String XMLENUM = RESOURCE_DIR + "xmlenum.xsd"; + private static final String XMLENUM_NUMBERS = RESOURCE_DIR + "xmlenum-numbers.xsd"; private static final String XMLENUM_BIG = RESOURCE_DIR + "xmlenum-big.xsd"; private static final String XMLELEMENTDECL = RESOURCE_DIR + "xmlelementdecl.xsd"; private static final String XMLELEMENTCOLLECTION = RESOURCE_DIR + "xmlelement-collection.xsd"; @@ -1093,6 +1114,8 @@ private void print(Object o) throws Exception { private static final String DEF_PACKAGE = "generated"; private static final String BANK_PACKAGE = "banknamespace"; private static final String PERSON = "Person"; + private static final String TAX_RECORD = "TaxRecord"; + private static final String PERIOD = "Period"; private static final String EMPLOYEE = "Employee"; private static final String INDIVIDUO = "Individuo"; private static final String CDN_CURRENCY = "CdnCurrency"; diff --git a/moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd b/moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd new file mode 100644 index 00000000000..02b1b8d06aa --- /dev/null +++ b/moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + From 1511458635d63560afd6eb8c50b6f29afe817493 Mon Sep 17 00:00:00 2001 From: Tim Tattersall Date: Mon, 4 Nov 2024 11:18:17 +1100 Subject: [PATCH 2/2] Using PrivilegedAccessHelper for getEnumValue --- .../persistence/jaxb/JAXBEnumTypeConverter.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java index 5d4cadb3732..0fda7e8b59f 100644 --- a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java +++ b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java @@ -97,10 +97,12 @@ public void initialize(DatabaseMapping mapping, Session session) { private String getEnumValue(Enum theEnum) { try { - Field field = theEnum.getClass().getField(theEnum.name()); - XmlEnumValue annotation = field.getAnnotation(XmlEnumValue.class); - return annotation != null ? annotation.value() : theEnum.name(); - } catch (NoSuchFieldException exc) { + return PrivilegedAccessHelper.callDoPrivilegedWithException(() -> { + Field field = theEnum.getClass().getField(theEnum.name()); + XmlEnumValue annotation = field.getAnnotation(XmlEnumValue.class); + return annotation != null ? annotation.value() : theEnum.name(); + }); + } catch (Exception exc) { return theEnum.name(); } }