Skip to content

Commit

Permalink
Merge pull request #38217 from gsmet/jaxb-transient
Browse files Browse the repository at this point in the history
JAXB - Ignore @XmlTransient fields/methods when marking hierarchy for reflection
  • Loading branch information
gsmet authored Feb 7, 2024
2 parents 371234c + ea7a49a commit 59e1c2d
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ public static class DefaultIgnoreTypePredicate implements Predicate<DotName> {
public static final DefaultIgnoreTypePredicate INSTANCE = new DefaultIgnoreTypePredicate();

private static final List<String> DEFAULT_IGNORED_PACKAGES = Arrays.asList("java.", "io.reactivex.",
"org.reactivestreams.", "org.slf4j.", "jakarta.json.", "jakarta.json.",
"org.reactivestreams.", "org.slf4j.", "jakarta.", "jakarta.json.",
"javax.net.ssl.", "javax.xml.", "javax.management.", "reactor.core.",
"com.fasterxml.jackson.databind.", "io.vertx.core.json.", "kotlin.");
// if this gets more complicated we will need to move to some tree like structure
static final Set<String> ALLOWED_FROM_IGNORED_PACKAGES = new HashSet<>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.IOError;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Expand All @@ -14,6 +15,7 @@
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.annotation.XmlAccessOrder;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAnyAttribute;
import jakarta.xml.bind.annotation.XmlAnyElement;
Expand Down Expand Up @@ -49,8 +51,11 @@
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;

Expand All @@ -70,6 +75,7 @@
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
Expand Down Expand Up @@ -123,6 +129,8 @@ public class JaxbProcessor {
private static final DotName XML_JAVA_TYPE_ADAPTER = DotName.createSimple(XmlJavaTypeAdapter.class.getName());
private static final DotName XML_ANY_ELEMENT = DotName.createSimple(XmlAnyElement.class.getName());
private static final DotName XML_SEE_ALSO = DotName.createSimple(XmlSeeAlso.class.getName());
private static final DotName XML_TRANSIENT = DotName.createSimple(XmlTransient.class.getName());
private static final DotName XML_ACCESSOR_TYPE = DotName.createSimple(XmlAccessorType.class.getName());

private static final List<DotName> JAXB_ROOT_ANNOTATIONS = List.of(XML_ROOT_ELEMENT, XML_TYPE, XML_REGISTRY);

Expand Down Expand Up @@ -186,6 +194,7 @@ void processAnnotationsAndIndexFiles(
BuildProducer<NativeImageProxyDefinitionBuildItem> proxyDefinitions,
CombinedIndexBuildItem combinedIndexBuildItem,
List<JaxbFileRootBuildItem> fileRoots,
BuildProducer<ReflectiveHierarchyBuildItem> reflectiveHierarchies,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<NativeImageResourceBuildItem> resource,
BuildProducer<NativeImageResourceBundleBuildItem> resourceBundle,
Expand All @@ -202,10 +211,11 @@ void processAnnotationsAndIndexFiles(
for (DotName jaxbRootAnnotation : JAXB_ROOT_ANNOTATIONS) {
for (AnnotationInstance jaxbRootAnnotationInstance : index
.getAnnotations(jaxbRootAnnotation)) {
if (jaxbRootAnnotationInstance.target().kind() == Kind.CLASS) {
String className = jaxbRootAnnotationInstance.target().asClass().name().toString();
reflectiveClass.produce(ReflectiveClassBuildItem.builder(className).methods().fields().build());
classesToBeBound.add(className);
if (jaxbRootAnnotationInstance.target().kind() == Kind.CLASS
&& !JAXB_ANNOTATIONS.contains(jaxbRootAnnotationInstance.target().asClass().getClass())) {
ClassInfo targetClassInfo = jaxbRootAnnotationInstance.target().asClass();
addReflectiveHierarchyClass(targetClassInfo, reflectiveHierarchies, index);
classesToBeBound.add(targetClassInfo.name().toString());
jaxbRootAnnotationsDetected = true;
}
}
Expand Down Expand Up @@ -412,6 +422,22 @@ public static Stream<Path> safeWalk(Path p) {
}
}

private void addReflectiveHierarchyClass(ClassInfo classInfo,
BuildProducer<ReflectiveHierarchyBuildItem> reflectiveHierarchy,
IndexView index) {
Type jandexType = Type.create(classInfo.name(), Type.Kind.CLASS);

reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem.Builder()
.type(jandexType)
.index(index)
.ignoreTypePredicate(t -> ReflectiveHierarchyBuildItem.DefaultIgnoreTypePredicate.INSTANCE.test(t)
|| IGNORE_TYPES.contains(t))
.ignoreFieldPredicate(JaxbProcessor::isFieldIgnored)
.ignoreMethodPredicate(JaxbProcessor::isMethodIgnored)
.source(getClass().getSimpleName() + " > " + jandexType.name().toString())
.build());
}

private void addReflectiveClass(BuildProducer<ReflectiveClassBuildItem> reflectiveClass, boolean methods, boolean fields,
String... className) {
reflectiveClass.produce(new ReflectiveClassBuildItem(methods, fields, className));
Expand All @@ -421,4 +447,92 @@ private void addResourceBundle(BuildProducer<NativeImageResourceBundleBuildItem>
resourceBundle.produce(new NativeImageResourceBundleBuildItem(bundle));
}

private static boolean isFieldIgnored(FieldInfo fieldInfo) {
// see JakartaXmlBindingAnnotationIntrospector#isVisible(AnnotatedField f)
// and XmlAccessType
if (fieldInfo.hasAnnotation(XML_TRANSIENT)) {
return true;
}
if (Modifier.isStatic(fieldInfo.flags())) {
return true;
}

for (Class<? extends Annotation> jaxbAnnotation : JAXB_ANNOTATIONS) {
if (fieldInfo.hasAnnotation(jaxbAnnotation)) {
return true;
}
}

ClassInfo declaringClass = fieldInfo.declaringClass();
XmlAccessType xmlAccessType = getXmlAccessType(declaringClass);
switch (xmlAccessType) {
case FIELD:
return false;
case PROPERTY:
return true;
case PUBLIC_MEMBER:
return !Modifier.isPublic(fieldInfo.flags());
case NONE:
return true;
default:
return true;
}
}

private static boolean isMethodIgnored(MethodInfo methodInfo) {
// see JakartaXmlBindingAnnotationIntrospector#isVisible(AnnotatedMethod m)
// and XmlAccessType
MethodInfo getterSetterCounterpart = getGetterSetterCounterPart(methodInfo);

if (methodInfo.hasAnnotation(XML_TRANSIENT) ||
(getterSetterCounterpart != null && getterSetterCounterpart.hasAnnotation(XML_TRANSIENT))) {
return true;
}
if (Modifier.isStatic(methodInfo.flags())) {
return true;
}

// if method has a JAXB annotation, we consider it
for (Class<? extends Annotation> jaxbAnnotation : JAXB_ANNOTATIONS) {
if (methodInfo.hasAnnotation(jaxbAnnotation)) {
return false;
}
}

ClassInfo declaringClass = methodInfo.declaringClass();
XmlAccessType xmlAccessType = getXmlAccessType(declaringClass);
switch (xmlAccessType) {
case FIELD:
return true;
case PROPERTY:
case PUBLIC_MEMBER:
return !Modifier.isPublic(methodInfo.flags());
case NONE:
return true;
default:
return true;
}
}

private static MethodInfo getGetterSetterCounterPart(MethodInfo methodInfo) {
if (!methodInfo.name().startsWith("get") || methodInfo.parametersCount() > 0) {
return null;
}

return methodInfo.declaringClass().method(methodInfo.name().replaceFirst("get", "set"), methodInfo.returnType());
}

private static XmlAccessType getXmlAccessType(ClassInfo classInfo) {
AnnotationInstance xmlAccessorTypeAi = classInfo.annotation(XML_ACCESSOR_TYPE);
if (xmlAccessorTypeAi == null) {
return XmlAccessType.PUBLIC_MEMBER;
}

AnnotationValue xmlAccessorType = xmlAccessorTypeAi.value();
if (xmlAccessorType == null) {
return XmlAccessType.PUBLIC_MEMBER;
}

return XmlAccessType.valueOf(xmlAccessorTypeAi.value().asEnum());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.it.jaxb;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlTransient;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlTransient
public abstract class BookIBANField {
@XmlElement
private String IBAN;

public BookIBANField() {
}

public void setIBAN(String IBAN) {
this.IBAN = IBAN;
}

public String getIBAN() {
return IBAN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.it.jaxb;

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;

@XmlRootElement
@XmlType(propOrder = { "IBAN", "title" })
public class BookWithParent extends BookIBANField {
@XmlElement
private String title;

public BookWithParent() {
}

public BookWithParent(String title) {
this.title = title;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,19 @@ public io.quarkus.it.jaxb.Response seeAlso() {
return response;
}

//Test for Jaxb with parent class field
@Path("/bookwithparent")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getBookWithParent(@QueryParam("name") String name, @QueryParam("iban") String iban) throws JAXBException {
BookWithParent bookWithParent = new BookWithParent();
bookWithParent.setTitle(name);
bookWithParent.setIBAN(iban);
JAXBContext context = JAXBContext.newInstance(bookWithParent.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter sw = new StringWriter();
marshaller.marshal(bookWithParent, sw);
return sw.toString();
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
package io.quarkus.it.jaxb;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class JaxbIT extends JaxbTest {

//We have to test native executable of Jaxb
@Test
public void bookWithParent() {
given().when()
.param("name", "Foundation")
.param("iban", "4242")
.get("/jaxb/bookwithparent")
.then()
.statusCode(200)
.body(is(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><bookWithParent><IBAN>4242</IBAN><title>Foundation</title></bookWithParent>"));
}
}

0 comments on commit 59e1c2d

Please sign in to comment.