Skip to content

Commit

Permalink
Handle xjc XmlEnumValue in DynamicJAXB enum (#2273)
Browse files Browse the repository at this point in the history
  • Loading branch information
timtatt authored Nov 14, 2024
1 parent a3acc2c commit ac4dc50
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -96,17 +96,20 @@ public EclipseLinkClassWriter getClassWriter(String className) {
}

public void addEnum(String className, Object... literalLabels) {
Map<String, String> literalsMap = Arrays.stream(literalLabels)
.collect(Collectors.toMap(Object::toString, Object::toString));

addEnum(className, literalsMap);
}

public void addEnum(String className, Map<String, String> 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);
}
Expand Down Expand Up @@ -292,7 +295,7 @@ public static DynamicClassLoader lookup(Session session) {

public static class EnumInfo {
String className;
List<String> literalLabels = new ArrayList<>();
Map<String, String> literalLabels = new HashMap<>();

public EnumInfo(String className) {
this.className = className;
Expand All @@ -302,13 +305,17 @@ public String getClassName() {
return className;
}

public Map<String, String> 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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -223,7 +226,7 @@ protected void addMethods(ClassWriter cw, String parentClassType) {

protected byte[] createEnum(EnumInfo enumInfo) {

String[] enumValues = enumInfo.getLiteralLabels();
Map<String, String> enumValues = enumInfo.getEnumValues();
String className = enumInfo.getClassName();

String internalClassName = className.replace('.', '/');
Expand All @@ -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<String, String> 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
Expand Down Expand Up @@ -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);
Expand All @@ -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]);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Expand All @@ -92,6 +95,18 @@ public void initialize(DatabaseMapping mapping, Session session) {
super.initialize(mapping, session);
}

private String getEnumValue(Enum theEnum) {
try {
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();
}
}

/**
* PUBLIC:
* Returns true if this converter uses ordinal values for the enum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -216,9 +217,7 @@ private JavaClass[] createClassModelFromXJC(ArrayList<JDefinedClass> 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<String, JEnumConstant> enumConstants = definedClass.enumConstants();
Object[] enumValues = enumConstants.keySet().toArray();
dynamicClassLoader.addEnum(definedClass.fullName(), enumValues);
dynamicClassLoader.addEnum(definedClass.fullName(), getEnumValues(definedClass));
}
}

Expand All @@ -228,6 +227,23 @@ private JavaClass[] createClassModelFromXJC(ArrayList<JDefinedClass> xjcClasses,
}
}

private Map<String, String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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";
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2024, 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
http://www.eclipse.org/legal/epl-2.0,
or the Eclipse Distribution License v. 1.0 which is available at
http://www.eclipse.org/org/documents/edl-v10.php.
SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
-->

<xs:schema targetNamespace="myNamespace" xmlns:myns="myNamespace" xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified" elementFormDefault="qualified">

<xs:element name="tax-record">
<xs:complexType>
<xs:sequence>
<xs:element name="period" type="myns:period"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<!-- This test contains 4 enums with digits to test value conversion for xjc enums, i.e. QTR1 is converted to enum QTR_1 -->
<xs:simpleType name="period">
<xs:restriction base="xs:string">
<xs:enumeration value="QTR1"/>
<xs:enumeration value="QTR2"/>
<xs:enumeration value="QTR3"/>
<xs:enumeration value="QTR4"/>
</xs:restriction>
</xs:simpleType>

</xs:schema>

0 comments on commit ac4dc50

Please sign in to comment.