Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle xjc XmlEnumValue in DynamicJAXB enum #2273

Merged
merged 2 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading