VYPR
High severity8.3NVD Advisory· Published Feb 13, 2025· Updated Apr 15, 2026

CVE-2025-1247

CVE-2025-1247

Description

A flaw was found in Quarkus REST that allows request parameters to leak between concurrent requests if endpoints use field injection without a CDI scope. This vulnerability allows attackers to manipulate request data, impersonate users, or access sensitive information.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
io.quarkus:quarkus-restMaven
>= 3.16.0.CR1, < 3.18.23.18.2
io.quarkus:quarkus-rest-deploymentMaven
>= 3.16.0.CR1, < 3.18.23.18.2
io.quarkus:quarkus-restMaven
>= 3.9.0.CR1, < 3.15.3.13.15.3.1
io.quarkus:quarkus-rest-deploymentMaven
>= 3.9.0.CR1, < 3.15.3.13.15.3.1
io.quarkus:quarkus-restMaven
< 3.8.6.13.8.6.1
io.quarkus:quarkus-rest-deploymentMaven
< 3.8.6.13.8.6.1

Patches

3
d8df15cec17d

Merge pull request #46178 from geoand/3.8-#45789

https://github.com/quarkusio/quarkusGuillaume SmetFeb 18, 2025via ghsa
14 files changed · +522 3
  • extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java+23 0 modified
    @@ -8,6 +8,8 @@
     import java.util.Map;
     import java.util.function.Predicate;
     
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.enterprise.inject.spi.DeploymentException;
     import jakarta.ws.rs.core.MediaType;
     
     import org.jboss.jandex.AnnotationInstance;
    @@ -28,6 +30,7 @@
     import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter;
     import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory;
     
    +import io.quarkus.arc.processor.BuiltinScope;
     import io.quarkus.builder.BuildException;
     import io.quarkus.deployment.Capabilities;
     import io.quarkus.deployment.annotations.BuildProducer;
    @@ -274,4 +277,24 @@ protected void warnAboutMissUsedBodyParameter(DotName httpMethod, MethodInfo met
             super.warnAboutMissUsedBodyParameter(httpMethod, methodInfo);
         }
     
    +    /**
    +     * At this point we know exactly which resources will require field injection and therefore are required to be
    +     * {@link RequestScoped}.
    +     * We can't change anything CDI related at this point (because it would create build cycles), so all we can do
    +     * is fail the build if the resource has not already been handled automatically (by the best effort approach performed
    +     * elsewhere)
    +     * or it's not manually set to be {@link RequestScoped}.
    +     */
    +    @Override
    +    protected void verifyClassThatRequiresFieldInjection(ClassInfo classInfo) {
    +        if (!alreadyHandledRequestScopedResources.contains(classInfo.name())) {
    +            BuiltinScope scope = BuiltinScope.from(classInfo);
    +            if (BuiltinScope.REQUEST != scope) {
    +                throw new DeploymentException(
    +                        "Resource classes that use field injection for REST parameters can only be @RequestScoped. Offending class is "
    +                                + classInfo.name());
    +            }
    +        }
    +    }
    +
     }
    
  • extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java+44 0 modified
    @@ -11,7 +11,10 @@
     import java.util.List;
     import java.util.Map;
     import java.util.Optional;
    +import java.util.Set;
     
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.enterprise.inject.spi.DeploymentException;
     import jakarta.ws.rs.BeanParam;
     
     import org.jboss.jandex.AnnotationInstance;
    @@ -67,6 +70,47 @@ void beanDefiningAnnotations(BuildProducer<BeanDefiningAnnotationBuildItem> bean
                             BuiltinScope.SINGLETON.getName()));
         }
     
    +    /**
    +     * The idea here is to make a best effort to find resources that need to be {@link RequestScoped}
    +     * and make them such if no scope has been defined.
    +     * If any other scope has been explicitly defined, the build will fail
    +     */
    +    @BuildStep
    +    void requestScopedResources(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
    +            BuildProducer<AnnotationsTransformerBuildItem> additionalBeanBuildItemBuildProducer) {
    +        if (resourceScanningResultBuildItem.isEmpty()) {
    +            return;
    +        }
    +        Set<DotName> requestScopedResources = resourceScanningResultBuildItem.get().getResult()
    +                .getRequestScopedResources();
    +
    +        additionalBeanBuildItemBuildProducer.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
    +            @Override
    +            public boolean appliesTo(AnnotationTarget.Kind kind) {
    +                return kind == AnnotationTarget.Kind.CLASS;
    +            }
    +
    +            @Override
    +            public void transform(TransformationContext transformationContext) {
    +                ClassInfo clazz = transformationContext.getTarget().asClass();
    +                if (requestScopedResources.contains(clazz.name())) {
    +                    BuiltinScope builtinScope = BuiltinScope.from(clazz);
    +                    if (builtinScope != null) {
    +                        if (builtinScope.getName() != BuiltinScope.REQUEST.getName()) {
    +                            throw new DeploymentException(
    +                                    "Resource classes that use field injection for REST parameters can only be @RequestScoped. Offending class is "
    +                                            + clazz.name());
    +                        } else {
    +                            // nothing to do as @RequestScoped was already present
    +                        }
    +                    } else {
    +                        transformationContext.transform().add(RequestScoped.class).done();
    +                    }
    +                }
    +            }
    +        }));
    +    }
    +
         @BuildStep
         void unremovableContextMethodParams(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
                 BuildProducer<UnremovableBeanBuildItem> producer) {
    
  • extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java+2 1 modified
    @@ -632,7 +632,8 @@ public Supplier<Boolean> apply(ClassInfo classInfo) {
                                 boolean disableIfMissing = disableIfMissingValue != null && disableIfMissingValue.asBoolean();
                                 return recorder.disableIfPropertyMatches(propertyName, propertyValue, disableIfMissing);
                             }
    -                    });
    +                    })
    +                    .alreadyHandledRequestScopedResources(result.getRequestScopedResources());
     
                 if (!serverDefaultProducesHandlers.isEmpty()) {
                     List<DefaultProducesHandler> handlers = new ArrayList<>(serverDefaultProducesHandlers.size());
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/FormFieldSingletonScopeTest.java+44 0 added
    @@ -0,0 +1,44 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.inject.Singleton;
    +import jakarta.ws.rs.FormParam;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class FormFieldSingletonScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Singleton
    +    public static class Resource {
    +
    +        @FormParam("foo")
    +        String foo;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassDependentScopeTest.java+50 0 added
    @@ -0,0 +1,50 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.context.Dependent;
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassDependentScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Dependent
    +    public static class Resource extends AbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassNoScopeTest.java+66 0 added
    @@ -0,0 +1,66 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassNoScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, AbstractAbstractResource.class, Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource extends AbstractAbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +
    +    public static class AbstractAbstractResource extends AbstractResource {
    +
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassRequestScopeTest.java+62 0 added
    @@ -0,0 +1,62 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassRequestScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource extends AbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldNoScopeTest.java+58 0 added
    @@ -0,0 +1,58 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldNoScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    public static class Resource {
    +
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/PathFieldApplicationScopeTest.java+45 0 added
    @@ -0,0 +1,45 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.inject.Singleton;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.jboss.resteasy.reactive.RestPath;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class PathFieldApplicationScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Singleton
    +    public static class Resource {
    +
    +        @RestPath
    +        String id;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        @Path("/{id}")
    +        public String hello() {
    +            return "id: " + id;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/QueryFieldRequestScopeTest.java+56 0 added
    @@ -0,0 +1,56 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.QueryParam;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestQuery;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class QueryFieldRequestScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class));
    +
    +    @Test
    +    public void test() {
    +        when()
    +                .get("/test?foo=f&bar=b")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource {
    +
    +        @QueryParam("foo")
    +        String foo;
    +
    +        @RestQuery
    +        String bar;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +}
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java+18 0 modified
    @@ -238,6 +238,7 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
     
         private final Predicate<Map<DotName, AnnotationInstance>> skipMethodParameter;
         private SerializerScanningResult serializerScanningResult;
    +    protected final Set<DotName> alreadyHandledRequestScopedResources;
     
         protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
             this.index = builder.index;
    @@ -262,6 +263,7 @@ protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
             this.targetJavaVersion = builder.targetJavaVersion;
             this.isDisabledCreator = builder.isDisabledCreator;
             this.skipMethodParameter = builder.skipMethodParameter;
    +        this.alreadyHandledRequestScopedResources = builder.alreadyHandledRequestScopedResources;
         }
     
         public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean considerApplication) {
    @@ -310,6 +312,9 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
                     }
                 }
                 if (injectableBean.isInjectionRequired()) {
    +                if (path != null) { // we don't want to verify subresources
    +                    verifyClassThatRequiresFieldInjection(classInfo);
    +                }
                     clazz.setPerRequestResource(true);
                 }
     
    @@ -319,6 +324,9 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
     
                 return Optional.of(clazz);
             } catch (Exception e) {
    +            if (e instanceof DeploymentException) {
    +                throw (DeploymentException) e;
    +            }
                 if (Modifier.isInterface(classInfo.flags()) || Modifier.isAbstract(classInfo.flags())) {
                     //kinda bogus, but we just ignore failed interfaces for now
                     //they can have methods that are not valid until they are actually extended by a concrete type
    @@ -329,6 +337,10 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
             }
         }
     
    +    protected void verifyClassThatRequiresFieldInjection(ClassInfo classInfo) {
    +
    +    }
    +
         private String sanitizePath(String path) {
             // this simply replaces the whitespace characters (not part of a path variable) with %20
             // TODO: this might have to be more complex, URL encoding maybe?
    @@ -1664,6 +1676,7 @@ public static abstract class Builder<T extends EndpointIndexer<T, ?, METHOD>, B
             private Consumer<ResourceMethodCallbackEntry> resourceMethodCallback;
             private Collection<AnnotationsTransformer> annotationsTransformers;
             private ApplicationScanningResult applicationScanningResult;
    +        private Set<DotName> alreadyHandledRequestScopedResources = new HashSet<>();
             private final Set<DotName> contextTypes = new HashSet<>(DEFAULT_CONTEXT_TYPES);
             private final Set<DotName> parameterContainerTypes = new HashSet<>();
             private MultipartReturnTypeIndexerExtension multipartReturnTypeIndexerExtension = new MultipartReturnTypeIndexerExtension() {
    @@ -1801,6 +1814,11 @@ public B setSkipMethodParameter(
                 return (B) this;
             }
     
    +        public B alreadyHandledRequestScopedResources(Set<DotName> alreadyHandledRequestScopedResources) {
    +            this.alreadyHandledRequestScopedResources = alreadyHandledRequestScopedResources;
    +            return (B) this;
    +        }
    +
             public abstract T build();
         }
     
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java+2 0 modified
    @@ -26,6 +26,7 @@
     
     import jakarta.annotation.Priority;
     import jakarta.enterprise.context.ApplicationScoped;
    +import jakarta.enterprise.context.Dependent;
     import jakarta.enterprise.context.RequestScoped;
     import jakarta.enterprise.inject.Typed;
     import jakarta.enterprise.inject.Vetoed;
    @@ -175,6 +176,7 @@ public final class ResteasyReactiveDotNames {
         public static final DotName APPLICATION_SCOPED = DotName.createSimple(ApplicationScoped.class.getName());
         public static final DotName SINGLETON = DotName.createSimple(Singleton.class.getName());
         public static final DotName REQUEST_SCOPED = DotName.createSimple(RequestScoped.class.getName());
    +    public static final DotName DEPENDENT = DotName.createSimple(Dependent.class.getName());
         public static final DotName WEB_APPLICATION_EXCEPTION = DotName.createSimple(WebApplicationException.class.getName());
     
         public static final DotName INVOCATION_CALLBACK = DotName.createSimple(InvocationCallback.class.getName());
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResourceScanningResult.java+8 1 modified
    @@ -21,13 +21,15 @@ public final class ResourceScanningResult {
         final Set<String> beanParams;
         final Map<DotName, String> httpAnnotationToMethod;
         final List<MethodInfo> classLevelExceptionMappers;
    +    final Set<DotName> requestScopedResources;
     
         public ResourceScanningResult(IndexView index, Map<DotName, ClassInfo> scannedResources,
                 Map<DotName, String> scannedResourcePaths,
                 Map<DotName, ClassInfo> possibleSubResources, Map<DotName, String> pathInterfaces,
                 Map<DotName, String> clientInterfaces,
                 Map<DotName, MethodInfo> resourcesThatNeedCustomProducer,
    -            Set<String> beanParams, Map<DotName, String> httpAnnotationToMethod, List<MethodInfo> classLevelExceptionMappers) {
    +            Set<String> beanParams, Map<DotName, String> httpAnnotationToMethod, List<MethodInfo> classLevelExceptionMappers,
    +            Set<DotName> requestScopedResources) {
             this.index = index;
             this.scannedResources = scannedResources;
             this.scannedResourcePaths = scannedResourcePaths;
    @@ -38,6 +40,7 @@ public ResourceScanningResult(IndexView index, Map<DotName, ClassInfo> scannedRe
             this.beanParams = beanParams;
             this.httpAnnotationToMethod = httpAnnotationToMethod;
             this.classLevelExceptionMappers = classLevelExceptionMappers;
    +        this.requestScopedResources = requestScopedResources;
         }
     
         public IndexView getIndex() {
    @@ -79,4 +82,8 @@ public Map<DotName, String> getHttpAnnotationToMethod() {
         public List<MethodInfo> getClassLevelExceptionMappers() {
             return classLevelExceptionMappers;
         }
    +
    +    public Set<DotName> getRequestScopedResources() {
    +        return requestScopedResources;
    +    }
     }
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java+44 1 modified
    @@ -1,12 +1,25 @@
     package org.jboss.resteasy.reactive.common.processor.scanning;
     
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.BEAN_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COOKIE_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.DELETE;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.FORM_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.GET;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEAD;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEADER_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MATRIX_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OPTIONS;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATCH;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.POST;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUT;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.QUERY_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_COOKIE_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_HEADER_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MATRIX_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_PATH_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_QUERY_PARAM;
     
     import java.lang.reflect.InvocationTargetException;
     import java.lang.reflect.Modifier;
    @@ -228,6 +241,7 @@ public static ResourceScanningResult scanResources(
             Map<DotName, String> pathInterfaces = new HashMap<>();
             Map<DotName, MethodInfo> resourcesThatNeedCustomProducer = new HashMap<>();
             List<MethodInfo> methodExceptionMappers = new ArrayList<>();
    +        Set<DotName> requestScopedResources = new HashSet<>();
     
             Set<DotName> interfacesWithPathOnMethods = new HashSet<>();
     
    @@ -244,6 +258,9 @@ public static ResourceScanningResult scanResources(
                     if (ctor != null) {
                         resourcesThatNeedCustomProducer.put(clazz.name(), ctor);
                     }
    +                if (hasJaxRsFieldInjection(clazz, index)) {
    +                    requestScopedResources.add(clazz.name());
    +                }
                     List<AnnotationInstance> exceptionMapperAnnotationInstances = clazz.annotationsMap()
                             .get(ResteasyReactiveDotNames.SERVER_EXCEPTION_MAPPER);
                     if (exceptionMapperAnnotationInstances != null) {
    @@ -430,7 +447,7 @@ public static ResourceScanningResult scanResources(
             return new ResourceScanningResult(index, scannedResources,
                     scannedResourcePaths, possibleSubResources, pathInterfaces, clientInterfaces, resourcesThatNeedCustomProducer,
                     beanParams,
    -                httpAnnotationToMethod, methodExceptionMappers);
    +                httpAnnotationToMethod, methodExceptionMappers, requestScopedResources);
         }
     
         private static void addClientSubInterfaces(DotName interfaceName, IndexView index,
    @@ -472,4 +489,30 @@ private static MethodInfo hasJaxRsCtorParams(ClassInfo classInfo) {
             return needsHandling ? ctor : null;
         }
     
    +    public static final Set<DotName> ANNOTATIONS_REQUIRING_FIELD_INJECTION = new HashSet<>(
    +            Arrays.asList(PATH_PARAM, QUERY_PARAM, HEADER_PARAM, FORM_PARAM, MATRIX_PARAM,
    +                    COOKIE_PARAM, REST_PATH_PARAM, REST_QUERY_PARAM, REST_HEADER_PARAM, REST_FORM_PARAM, REST_MATRIX_PARAM,
    +                    REST_COOKIE_PARAM, BEAN_PARAM));
    +
    +    private static boolean hasJaxRsFieldInjection(ClassInfo classInfo, IndexView index) {
    +        while (true) {
    +            for (FieldInfo field : classInfo.fields()) {
    +                List<AnnotationInstance> annotations = field.annotations();
    +                if (annotations.stream()
    +                        .anyMatch(an -> ANNOTATIONS_REQUIRING_FIELD_INJECTION.contains(an.name()))) {
    +                    return true;
    +                }
    +            }
    +            DotName parentDotName = classInfo.superName();
    +            if (parentDotName.equals(ResteasyReactiveDotNames.OBJECT)) {
    +                return false;
    +            }
    +            classInfo = index.getClassByName(parentDotName);
    +            if (classInfo == null) {
    +                return false;
    +            }
    +        }
    +
    +    }
    +
     }
    
f42166ee7041

Merge pull request #46175 from geoand/3.15-#45789

https://github.com/quarkusio/quarkusJan MartiskaFeb 17, 2025via ghsa
14 files changed · +530 3
  • extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java+23 0 modified
    @@ -8,6 +8,8 @@
     import java.util.Map;
     import java.util.function.Predicate;
     
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.enterprise.inject.spi.DeploymentException;
     import jakarta.ws.rs.core.MediaType;
     
     import org.jboss.jandex.AnnotationInstance;
    @@ -28,6 +30,7 @@
     import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter;
     import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory;
     
    +import io.quarkus.arc.processor.BuiltinScope;
     import io.quarkus.builder.BuildException;
     import io.quarkus.deployment.Capabilities;
     import io.quarkus.deployment.annotations.BuildProducer;
    @@ -274,4 +277,24 @@ protected void warnAboutMissUsedBodyParameter(DotName httpMethod, MethodInfo met
             super.warnAboutMissUsedBodyParameter(httpMethod, methodInfo);
         }
     
    +    /**
    +     * At this point we know exactly which resources will require field injection and therefore are required to be
    +     * {@link RequestScoped}.
    +     * We can't change anything CDI related at this point (because it would create build cycles), so all we can do
    +     * is fail the build if the resource has not already been handled automatically (by the best effort approach performed
    +     * elsewhere)
    +     * or it's not manually set to be {@link RequestScoped}.
    +     */
    +    @Override
    +    protected void verifyClassThatRequiresFieldInjection(ClassInfo classInfo) {
    +        if (!alreadyHandledRequestScopedResources.contains(classInfo.name())) {
    +            BuiltinScope scope = BuiltinScope.from(classInfo);
    +            if (BuiltinScope.REQUEST != scope) {
    +                throw new DeploymentException(
    +                        "Resource classes that use field injection for REST parameters can only be @RequestScoped. Offending class is "
    +                                + classInfo.name());
    +            }
    +        }
    +    }
    +
     }
    
  • extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java+52 0 modified
    @@ -11,12 +11,19 @@
     import java.util.List;
     import java.util.Map;
     import java.util.Optional;
    +import java.util.Set;
    +import java.util.function.Consumer;
    +import java.util.function.Predicate;
     
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.enterprise.inject.spi.DeploymentException;
     import jakarta.ws.rs.BeanParam;
     
     import org.jboss.jandex.AnnotationInstance;
     import org.jboss.jandex.AnnotationTarget;
    +import org.jboss.jandex.AnnotationTransformation;
     import org.jboss.jandex.ClassInfo;
    +import org.jboss.jandex.Declaration;
     import org.jboss.jandex.DotName;
     import org.jboss.jandex.MethodInfo;
     import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
    @@ -67,6 +74,51 @@ void beanDefiningAnnotations(BuildProducer<BeanDefiningAnnotationBuildItem> bean
                             BuiltinScope.SINGLETON.getName()));
         }
     
    +    /**
    +     * The idea here is to make a best effort to find resources that need to be {@link RequestScoped}
    +     * and make them such if no scope has been defined.
    +     * If any other scope has been explicitly defined, the build will fail
    +     */
    +    @BuildStep
    +    void requestScopedResources(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
    +            BuildProducer<AnnotationsTransformerBuildItem> additionalBeanBuildItemBuildProducer) {
    +        if (resourceScanningResultBuildItem.isEmpty()) {
    +            return;
    +        }
    +        Set<DotName> requestScopedResources = resourceScanningResultBuildItem.get().getResult()
    +                .getRequestScopedResources();
    +
    +        additionalBeanBuildItemBuildProducer.produce(new io.quarkus.arc.deployment.AnnotationsTransformerBuildItem(
    +                AnnotationTransformation.builder().whenDeclaration(
    +                        new Predicate<>() {
    +                            @Override
    +                            public boolean test(Declaration declaration) {
    +                                return declaration.kind() == AnnotationTarget.Kind.CLASS;
    +                            }
    +                        }).transform(new Consumer<>() {
    +                            @Override
    +                            public void accept(AnnotationTransformation.TransformationContext context) {
    +                                if (context.declaration().kind() == AnnotationTarget.Kind.CLASS) {
    +                                    ClassInfo clazz = context.declaration().asClass();
    +                                    if (requestScopedResources.contains(clazz.name())) {
    +                                        BuiltinScope builtinScope = BuiltinScope.from(clazz);
    +                                        if (builtinScope != null) {
    +                                            if (builtinScope.getName() != BuiltinScope.REQUEST.getName()) {
    +                                                throw new DeploymentException(
    +                                                        "Resource classes that use field injection for REST parameters can only be @RequestScoped. Offending class is "
    +                                                                + clazz.name());
    +                                            } else {
    +                                                // nothing to do as @RequestScoped was already present
    +                                            }
    +                                        } else {
    +                                            context.add(RequestScoped.class);
    +                                        }
    +                                    }
    +                                }
    +                            }
    +                        })));
    +    }
    +
         @BuildStep
         void unremovableContextMethodParams(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
                 BuildProducer<UnremovableBeanBuildItem> producer) {
    
  • extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java+2 1 modified
    @@ -627,7 +627,8 @@ public Supplier<Boolean> apply(ClassInfo classInfo) {
                                 boolean disableIfMissing = disableIfMissingValue != null && disableIfMissingValue.asBoolean();
                                 return recorder.disableIfPropertyMatches(propertyName, propertyValue, disableIfMissing);
                             }
    -                    });
    +                    })
    +                    .alreadyHandledRequestScopedResources(result.getRequestScopedResources());
     
                 serverEndpointIndexerBuilder.skipNotRestParameters(allowNotRestParametersBuildItem.isPresent());
     
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/FormFieldSingletonScopeTest.java+44 0 added
    @@ -0,0 +1,44 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.inject.Singleton;
    +import jakarta.ws.rs.FormParam;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class FormFieldSingletonScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Singleton
    +    public static class Resource {
    +
    +        @FormParam("foo")
    +        String foo;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassDependentScopeTest.java+50 0 added
    @@ -0,0 +1,50 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.context.Dependent;
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassDependentScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Dependent
    +    public static class Resource extends AbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassNoScopeTest.java+66 0 added
    @@ -0,0 +1,66 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassNoScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, AbstractAbstractResource.class, Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource extends AbstractAbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +
    +    public static class AbstractAbstractResource extends AbstractResource {
    +
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassRequestScopeTest.java+62 0 added
    @@ -0,0 +1,62 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassRequestScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource extends AbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldNoScopeTest.java+58 0 added
    @@ -0,0 +1,58 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldNoScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    public static class Resource {
    +
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/PathFieldApplicationScopeTest.java+45 0 added
    @@ -0,0 +1,45 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.inject.Singleton;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.jboss.resteasy.reactive.RestPath;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class PathFieldApplicationScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Singleton
    +    public static class Resource {
    +
    +        @RestPath
    +        String id;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        @Path("/{id}")
    +        public String hello() {
    +            return "id: " + id;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/QueryFieldRequestScopeTest.java+56 0 added
    @@ -0,0 +1,56 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.QueryParam;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestQuery;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class QueryFieldRequestScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class));
    +
    +    @Test
    +    public void test() {
    +        when()
    +                .get("/test?foo=f&bar=b")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource {
    +
    +        @QueryParam("foo")
    +        String foo;
    +
    +        @RestQuery
    +        String bar;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +}
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java+18 0 modified
    @@ -240,6 +240,7 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
     
         private final Predicate<Map<DotName, AnnotationInstance>> skipMethodParameter;
         private final boolean skipNotRestParameters;
    +    protected final Set<DotName> alreadyHandledRequestScopedResources;
     
         private SerializerScanningResult serializerScanningResult;
     
    @@ -267,6 +268,7 @@ protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
             this.isDisabledCreator = builder.isDisabledCreator;
             this.skipMethodParameter = builder.skipMethodParameter;
             this.skipNotRestParameters = builder.skipNotRestParameters;
    +        this.alreadyHandledRequestScopedResources = builder.alreadyHandledRequestScopedResources;
         }
     
         public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean considerApplication) {
    @@ -315,6 +317,9 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
                     }
                 }
                 if (injectableBean.isInjectionRequired()) {
    +                if (path != null) { // we don't want to verify subresources
    +                    verifyClassThatRequiresFieldInjection(classInfo);
    +                }
                     clazz.setPerRequestResource(true);
                 }
     
    @@ -324,6 +329,9 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
     
                 return Optional.of(clazz);
             } catch (Exception e) {
    +            if (e instanceof DeploymentException) {
    +                throw (DeploymentException) e;
    +            }
                 if (Modifier.isInterface(classInfo.flags()) || Modifier.isAbstract(classInfo.flags())) {
                     //kinda bogus, but we just ignore failed interfaces for now
                     //they can have methods that are not valid until they are actually extended by a concrete type
    @@ -334,6 +342,10 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
             }
         }
     
    +    protected void verifyClassThatRequiresFieldInjection(ClassInfo classInfo) {
    +
    +    }
    +
         private String sanitizePath(String path) {
             // this simply replaces the whitespace characters (not part of a path variable) with %20
             // TODO: this might have to be more complex, URL encoding maybe?
    @@ -1682,6 +1694,7 @@ public static abstract class Builder<T extends EndpointIndexer<T, ?, METHOD>, B
             private Consumer<ResourceMethodCallbackEntry> resourceMethodCallback;
             private Collection<AnnotationTransformation> annotationsTransformers;
             private ApplicationScanningResult applicationScanningResult;
    +        private Set<DotName> alreadyHandledRequestScopedResources = new HashSet<>();
             private final Set<DotName> contextTypes = new HashSet<>(DEFAULT_CONTEXT_TYPES);
             private final Set<DotName> parameterContainerTypes = new HashSet<>();
             private MultipartReturnTypeIndexerExtension multipartReturnTypeIndexerExtension = new MultipartReturnTypeIndexerExtension() {
    @@ -1837,6 +1850,11 @@ public B skipNotRestParameters(boolean skipNotRestParameters) {
                 return (B) this;
             }
     
    +        public B alreadyHandledRequestScopedResources(Set<DotName> alreadyHandledRequestScopedResources) {
    +            this.alreadyHandledRequestScopedResources = alreadyHandledRequestScopedResources;
    +            return (B) this;
    +        }
    +
             public abstract T build();
         }
     
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java+2 0 modified
    @@ -26,6 +26,7 @@
     
     import jakarta.annotation.Priority;
     import jakarta.enterprise.context.ApplicationScoped;
    +import jakarta.enterprise.context.Dependent;
     import jakarta.enterprise.context.RequestScoped;
     import jakarta.enterprise.inject.Typed;
     import jakarta.enterprise.inject.Vetoed;
    @@ -175,6 +176,7 @@ public final class ResteasyReactiveDotNames {
         public static final DotName APPLICATION_SCOPED = DotName.createSimple(ApplicationScoped.class.getName());
         public static final DotName SINGLETON = DotName.createSimple(Singleton.class.getName());
         public static final DotName REQUEST_SCOPED = DotName.createSimple(RequestScoped.class.getName());
    +    public static final DotName DEPENDENT = DotName.createSimple(Dependent.class.getName());
         public static final DotName WEB_APPLICATION_EXCEPTION = DotName.createSimple(WebApplicationException.class.getName());
     
         public static final DotName INVOCATION_CALLBACK = DotName.createSimple(InvocationCallback.class.getName());
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResourceScanningResult.java+8 1 modified
    @@ -21,13 +21,15 @@ public final class ResourceScanningResult {
         final Set<String> beanParams;
         final Map<DotName, String> httpAnnotationToMethod;
         final List<MethodInfo> classLevelExceptionMappers;
    +    final Set<DotName> requestScopedResources;
     
         public ResourceScanningResult(IndexView index, Map<DotName, ClassInfo> scannedResources,
                 Map<DotName, String> scannedResourcePaths,
                 Map<DotName, ClassInfo> possibleSubResources, Map<DotName, String> pathInterfaces,
                 Map<DotName, String> clientInterfaces,
                 Map<DotName, MethodInfo> resourcesThatNeedCustomProducer,
    -            Set<String> beanParams, Map<DotName, String> httpAnnotationToMethod, List<MethodInfo> classLevelExceptionMappers) {
    +            Set<String> beanParams, Map<DotName, String> httpAnnotationToMethod, List<MethodInfo> classLevelExceptionMappers,
    +            Set<DotName> requestScopedResources) {
             this.index = index;
             this.scannedResources = scannedResources;
             this.scannedResourcePaths = scannedResourcePaths;
    @@ -38,6 +40,7 @@ public ResourceScanningResult(IndexView index, Map<DotName, ClassInfo> scannedRe
             this.beanParams = beanParams;
             this.httpAnnotationToMethod = httpAnnotationToMethod;
             this.classLevelExceptionMappers = classLevelExceptionMappers;
    +        this.requestScopedResources = requestScopedResources;
         }
     
         public IndexView getIndex() {
    @@ -79,4 +82,8 @@ public Map<DotName, String> getHttpAnnotationToMethod() {
         public List<MethodInfo> getClassLevelExceptionMappers() {
             return classLevelExceptionMappers;
         }
    +
    +    public Set<DotName> getRequestScopedResources() {
    +        return requestScopedResources;
    +    }
     }
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java+44 1 modified
    @@ -1,12 +1,25 @@
     package org.jboss.resteasy.reactive.common.processor.scanning;
     
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.BEAN_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COOKIE_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.DELETE;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.FORM_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.GET;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEAD;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEADER_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MATRIX_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OPTIONS;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATCH;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.POST;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUT;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.QUERY_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_COOKIE_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_HEADER_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MATRIX_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_PATH_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_QUERY_PARAM;
     
     import java.lang.reflect.InvocationTargetException;
     import java.lang.reflect.Modifier;
    @@ -219,6 +232,7 @@ public static ResourceScanningResult scanResources(
             Map<DotName, String> pathInterfaces = new HashMap<>();
             Map<DotName, MethodInfo> resourcesThatNeedCustomProducer = new HashMap<>();
             List<MethodInfo> methodExceptionMappers = new ArrayList<>();
    +        Set<DotName> requestScopedResources = new HashSet<>();
     
             Set<DotName> interfacesWithPathOnMethods = new HashSet<>();
     
    @@ -235,6 +249,9 @@ public static ResourceScanningResult scanResources(
                     if (ctor != null) {
                         resourcesThatNeedCustomProducer.put(clazz.name(), ctor);
                     }
    +                if (hasJaxRsFieldInjection(clazz, index)) {
    +                    requestScopedResources.add(clazz.name());
    +                }
                     List<AnnotationInstance> exceptionMapperAnnotationInstances = clazz.annotationsMap()
                             .get(ResteasyReactiveDotNames.SERVER_EXCEPTION_MAPPER);
                     if (exceptionMapperAnnotationInstances != null) {
    @@ -421,7 +438,7 @@ public static ResourceScanningResult scanResources(
             return new ResourceScanningResult(index, scannedResources,
                     scannedResourcePaths, possibleSubResources, pathInterfaces, clientInterfaces, resourcesThatNeedCustomProducer,
                     beanParams,
    -                httpAnnotationToMethod, methodExceptionMappers);
    +                httpAnnotationToMethod, methodExceptionMappers, requestScopedResources);
         }
     
         private static void addClientSubInterfaces(DotName interfaceName, IndexView index,
    @@ -463,4 +480,30 @@ private static MethodInfo hasJaxRsCtorParams(ClassInfo classInfo) {
             return needsHandling ? ctor : null;
         }
     
    +    public static final Set<DotName> ANNOTATIONS_REQUIRING_FIELD_INJECTION = new HashSet<>(
    +            Arrays.asList(PATH_PARAM, QUERY_PARAM, HEADER_PARAM, FORM_PARAM, MATRIX_PARAM,
    +                    COOKIE_PARAM, REST_PATH_PARAM, REST_QUERY_PARAM, REST_HEADER_PARAM, REST_FORM_PARAM, REST_MATRIX_PARAM,
    +                    REST_COOKIE_PARAM, BEAN_PARAM));
    +
    +    private static boolean hasJaxRsFieldInjection(ClassInfo classInfo, IndexView index) {
    +        while (true) {
    +            for (FieldInfo field : classInfo.fields()) {
    +                List<AnnotationInstance> annotations = field.annotations();
    +                if (annotations.stream()
    +                        .anyMatch(an -> ANNOTATIONS_REQUIRING_FIELD_INJECTION.contains(an.name()))) {
    +                    return true;
    +                }
    +            }
    +            DotName parentDotName = classInfo.superName();
    +            if (parentDotName.equals(ResteasyReactiveDotNames.OBJECT)) {
    +                return false;
    +            }
    +            classInfo = index.getClassByName(parentDotName);
    +            if (classInfo == null) {
    +                return false;
    +            }
    +        }
    +
    +    }
    +
     }
    
02ff9ed45c39

Merge pull request #45950 from geoand/#45789

https://github.com/quarkusio/quarkusGeorgios AndrianakisJan 30, 2025via ghsa
14 files changed · +532 3
  • extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java+23 0 modified
    @@ -8,6 +8,8 @@
     import java.util.Map;
     import java.util.function.Predicate;
     
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.enterprise.inject.spi.DeploymentException;
     import jakarta.ws.rs.core.MediaType;
     
     import org.jboss.jandex.AnnotationInstance;
    @@ -28,6 +30,7 @@
     import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter;
     import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory;
     
    +import io.quarkus.arc.processor.BuiltinScope;
     import io.quarkus.builder.BuildException;
     import io.quarkus.deployment.Capabilities;
     import io.quarkus.deployment.annotations.BuildProducer;
    @@ -274,4 +277,24 @@ protected void warnAboutMissUsedBodyParameter(DotName httpMethod, MethodInfo met
             super.warnAboutMissUsedBodyParameter(httpMethod, methodInfo);
         }
     
    +    /**
    +     * At this point we know exactly which resources will require field injection and therefore are required to be
    +     * {@link RequestScoped}.
    +     * We can't change anything CDI related at this point (because it would create build cycles), so all we can do
    +     * is fail the build if the resource has not already been handled automatically (by the best effort approach performed
    +     * elsewhere)
    +     * or it's not manually set to be {@link RequestScoped}.
    +     */
    +    @Override
    +    protected void verifyClassThatRequiresFieldInjection(ClassInfo classInfo) {
    +        if (!alreadyHandledRequestScopedResources.contains(classInfo.name())) {
    +            BuiltinScope scope = BuiltinScope.from(classInfo);
    +            if (BuiltinScope.REQUEST != scope) {
    +                throw new DeploymentException(
    +                        "Resource classes that use field injection for REST parameters can only be @RequestScoped. Offending class is "
    +                                + classInfo.name());
    +            }
    +        }
    +    }
    +
     }
    
  • extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java+52 0 modified
    @@ -11,12 +11,19 @@
     import java.util.List;
     import java.util.Map;
     import java.util.Optional;
    +import java.util.Set;
    +import java.util.function.Consumer;
    +import java.util.function.Predicate;
     
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.enterprise.inject.spi.DeploymentException;
     import jakarta.ws.rs.BeanParam;
     
     import org.jboss.jandex.AnnotationInstance;
     import org.jboss.jandex.AnnotationTarget;
    +import org.jboss.jandex.AnnotationTransformation;
     import org.jboss.jandex.ClassInfo;
    +import org.jboss.jandex.Declaration;
     import org.jboss.jandex.DotName;
     import org.jboss.jandex.MethodInfo;
     import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
    @@ -67,6 +74,51 @@ void beanDefiningAnnotations(BuildProducer<BeanDefiningAnnotationBuildItem> bean
                             BuiltinScope.SINGLETON.getName()));
         }
     
    +    /**
    +     * The idea here is to make a best effort to find resources that need to be {@link RequestScoped}
    +     * and make them such if no scope has been defined.
    +     * If any other scope has been explicitly defined, the build will fail
    +     */
    +    @BuildStep
    +    void requestScopedResources(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
    +            BuildProducer<AnnotationsTransformerBuildItem> additionalBeanBuildItemBuildProducer) {
    +        if (resourceScanningResultBuildItem.isEmpty()) {
    +            return;
    +        }
    +        Set<DotName> requestScopedResources = resourceScanningResultBuildItem.get().getResult()
    +                .getRequestScopedResources();
    +
    +        additionalBeanBuildItemBuildProducer.produce(new io.quarkus.arc.deployment.AnnotationsTransformerBuildItem(
    +                AnnotationTransformation.builder().whenDeclaration(
    +                        new Predicate<>() {
    +                            @Override
    +                            public boolean test(Declaration declaration) {
    +                                return declaration.kind() == AnnotationTarget.Kind.CLASS;
    +                            }
    +                        }).transform(new Consumer<>() {
    +                            @Override
    +                            public void accept(AnnotationTransformation.TransformationContext context) {
    +                                if (context.declaration().kind() == AnnotationTarget.Kind.CLASS) {
    +                                    ClassInfo clazz = context.declaration().asClass();
    +                                    if (requestScopedResources.contains(clazz.name())) {
    +                                        BuiltinScope builtinScope = BuiltinScope.from(clazz);
    +                                        if (builtinScope != null) {
    +                                            if (builtinScope.getName() != BuiltinScope.REQUEST.getName()) {
    +                                                throw new DeploymentException(
    +                                                        "Resource classes that use field injection for REST parameters can only be @RequestScoped. Offending class is "
    +                                                                + clazz.name());
    +                                            } else {
    +                                                // nothing to do as @RequestScoped was already present
    +                                            }
    +                                        } else {
    +                                            context.add(RequestScoped.class);
    +                                        }
    +                                    }
    +                                }
    +                            }
    +                        })));
    +    }
    +
         @BuildStep
         void unremovableContextMethodParams(Optional<ResourceScanningResultBuildItem> resourceScanningResultBuildItem,
                 BuildProducer<UnremovableBeanBuildItem> producer) {
    
  • extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java+2 1 modified
    @@ -682,7 +682,8 @@ public Supplier<Boolean> apply(ClassInfo classInfo) {
                                 boolean disableIfMissing = disableIfMissingValue != null && disableIfMissingValue.asBoolean();
                                 return recorder.disableIfPropertyMatches(propertyName, propertyValue, disableIfMissing);
                             }
    -                    });
    +                    })
    +                    .alreadyHandledRequestScopedResources(result.getRequestScopedResources());
     
                 serverEndpointIndexerBuilder.skipNotRestParameters(allowNotRestParametersBuildItem.isPresent());
     
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/FormFieldSingletonScopeTest.java+44 0 added
    @@ -0,0 +1,44 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.inject.Singleton;
    +import jakarta.ws.rs.FormParam;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class FormFieldSingletonScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Singleton
    +    public static class Resource {
    +
    +        @FormParam("foo")
    +        String foo;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassDependentScopeTest.java+50 0 added
    @@ -0,0 +1,50 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.context.Dependent;
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassDependentScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Dependent
    +    public static class Resource extends AbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassNoScopeTest.java+66 0 added
    @@ -0,0 +1,66 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassNoScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, AbstractAbstractResource.class, Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource extends AbstractAbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +
    +    public static class AbstractAbstractResource extends AbstractResource {
    +
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldInSuperClassRequestScopeTest.java+62 0 added
    @@ -0,0 +1,62 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldInSuperClassRequestScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(AbstractResource.class, Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource extends AbstractResource {
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +
    +    public static class AbstractResource {
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/HeaderFieldNoScopeTest.java+58 0 added
    @@ -0,0 +1,58 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.given;
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.HeaderParam;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestHeader;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class HeaderFieldNoScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class));
    +
    +    @Test
    +    public void test() {
    +        given()
    +                .header("foo", "f")
    +                .header("bar", "b")
    +                .when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    public static class Resource {
    +
    +        @HeaderParam("foo")
    +        String foo;
    +
    +        @RestHeader
    +        String bar;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/PathFieldApplicationScopeTest.java+45 0 added
    @@ -0,0 +1,45 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import jakarta.enterprise.inject.spi.DeploymentException;
    +import jakarta.inject.Singleton;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.assertj.core.api.Assertions;
    +import org.jboss.resteasy.reactive.RestPath;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class PathFieldApplicationScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class))
    +            .assertException(t -> {
    +                org.junit.jupiter.api.Assertions.assertEquals(DeploymentException.class, t.getClass());
    +            });
    +
    +    @Test
    +    public void test() {
    +        Assertions.fail("should never have run");
    +    }
    +
    +    @Path("/test")
    +    @Singleton
    +    public static class Resource {
    +
    +        @RestPath
    +        String id;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        @Path("/{id}")
    +        public String hello() {
    +            return "id: " + id;
    +        }
    +    }
    +}
    
  • extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/injection/QueryFieldRequestScopeTest.java+56 0 added
    @@ -0,0 +1,56 @@
    +package io.quarkus.resteasy.reactive.server.test.injection;
    +
    +import static io.restassured.RestAssured.when;
    +import static org.hamcrest.CoreMatchers.is;
    +
    +import jakarta.enterprise.context.RequestScoped;
    +import jakarta.ws.rs.GET;
    +import jakarta.ws.rs.Path;
    +import jakarta.ws.rs.Produces;
    +import jakarta.ws.rs.QueryParam;
    +import jakarta.ws.rs.core.MediaType;
    +
    +import org.jboss.resteasy.reactive.RestQuery;
    +import org.junit.jupiter.api.Test;
    +import org.junit.jupiter.api.extension.RegisterExtension;
    +
    +import io.quarkus.test.QuarkusUnitTest;
    +
    +public class QueryFieldRequestScopeTest {
    +
    +    @RegisterExtension
    +    static QuarkusUnitTest runner = new QuarkusUnitTest()
    +            .withApplicationRoot(jar -> jar.addClasses(Resource.class));
    +
    +    @Test
    +    public void test() {
    +        when()
    +                .get("/test?foo=f&bar=b")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: f, bar: b"));
    +
    +        when()
    +                .get("/test")
    +                .then()
    +                .statusCode(200)
    +                .body(is("foo: null, bar: null"));
    +    }
    +
    +    @Path("/test")
    +    @RequestScoped
    +    public static class Resource {
    +
    +        @QueryParam("foo")
    +        String foo;
    +
    +        @RestQuery
    +        String bar;
    +
    +        @GET
    +        @Produces(MediaType.TEXT_PLAIN)
    +        public String hello() {
    +            return "foo: " + foo + ", bar: " + bar;
    +        }
    +    }
    +}
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java+18 0 modified
    @@ -243,6 +243,7 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
         private final Predicate<Map<DotName, AnnotationInstance>> skipMethodParameter;
         private final List<Predicate<ClassInfo>> validateEndpoint;
         private final boolean skipNotRestParameters;
    +    protected final Set<DotName> alreadyHandledRequestScopedResources;
     
         private SerializerScanningResult serializerScanningResult;
     
    @@ -271,6 +272,7 @@ protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
             this.skipMethodParameter = builder.skipMethodParameter;
             this.skipNotRestParameters = builder.skipNotRestParameters;
             this.validateEndpoint = builder.defaultPredicate;
    +        this.alreadyHandledRequestScopedResources = builder.alreadyHandledRequestScopedResources;
         }
     
         public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean considerApplication) {
    @@ -319,6 +321,9 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
                     }
                 }
                 if (injectableBean.isInjectionRequired()) {
    +                if (path != null) { // we don't want to verify subresources
    +                    verifyClassThatRequiresFieldInjection(classInfo);
    +                }
                     clazz.setPerRequestResource(true);
                 }
     
    @@ -328,6 +333,9 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
     
                 return Optional.of(clazz);
             } catch (Exception e) {
    +            if (e instanceof DeploymentException) {
    +                throw (DeploymentException) e;
    +            }
                 for (Predicate<ClassInfo> predicate : validateEndpoint) {
                     if (predicate.test(classInfo)) {
                         //kinda bogus, but we just ignore failed interfaces for now
    @@ -342,6 +350,10 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
     
         }
     
    +    protected void verifyClassThatRequiresFieldInjection(ClassInfo classInfo) {
    +
    +    }
    +
         private String sanitizePath(String path) {
             // this simply replaces the whitespace characters (not part of a path variable) with %20
             // TODO: this might have to be more complex, URL encoding maybe?
    @@ -1692,6 +1704,7 @@ public static abstract class Builder<T extends EndpointIndexer<T, ?, METHOD>, B
             private Consumer<ResourceMethodCallbackEntry> resourceMethodCallback;
             private Collection<AnnotationTransformation> annotationsTransformers;
             private ApplicationScanningResult applicationScanningResult;
    +        private Set<DotName> alreadyHandledRequestScopedResources = new HashSet<>();
             private final Set<DotName> contextTypes = new HashSet<>(DEFAULT_CONTEXT_TYPES);
             private final Set<DotName> parameterContainerTypes = new HashSet<>();
             private MultipartReturnTypeIndexerExtension multipartReturnTypeIndexerExtension = new MultipartReturnTypeIndexerExtension() {
    @@ -1862,6 +1875,11 @@ public B skipNotRestParameters(boolean skipNotRestParameters) {
                 return (B) this;
             }
     
    +        public B alreadyHandledRequestScopedResources(Set<DotName> alreadyHandledRequestScopedResources) {
    +            this.alreadyHandledRequestScopedResources = alreadyHandledRequestScopedResources;
    +            return (B) this;
    +        }
    +
             public abstract T build();
         }
     
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java+2 0 modified
    @@ -28,6 +28,7 @@
     
     import jakarta.annotation.Priority;
     import jakarta.enterprise.context.ApplicationScoped;
    +import jakarta.enterprise.context.Dependent;
     import jakarta.enterprise.context.RequestScoped;
     import jakarta.enterprise.inject.Typed;
     import jakarta.enterprise.inject.Vetoed;
    @@ -177,6 +178,7 @@ public final class ResteasyReactiveDotNames {
         public static final DotName APPLICATION_SCOPED = DotName.createSimple(ApplicationScoped.class.getName());
         public static final DotName SINGLETON = DotName.createSimple(Singleton.class.getName());
         public static final DotName REQUEST_SCOPED = DotName.createSimple(RequestScoped.class.getName());
    +    public static final DotName DEPENDENT = DotName.createSimple(Dependent.class.getName());
         public static final DotName WEB_APPLICATION_EXCEPTION = DotName.createSimple(WebApplicationException.class.getName());
     
         public static final DotName INVOCATION_CALLBACK = DotName.createSimple(InvocationCallback.class.getName());
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResourceScanningResult.java+9 1 modified
    @@ -2,6 +2,7 @@
     
     import java.util.List;
     import java.util.Map;
    +import java.util.Set;
     
     import org.jboss.jandex.ClassInfo;
     import org.jboss.jandex.DotName;
    @@ -19,13 +20,15 @@ public final class ResourceScanningResult {
         final Map<DotName, MethodInfo> resourcesThatNeedCustomProducer;
         final Map<DotName, String> httpAnnotationToMethod;
         final List<MethodInfo> classLevelExceptionMappers;
    +    final Set<DotName> requestScopedResources;
     
         public ResourceScanningResult(IndexView index, Map<DotName, ClassInfo> scannedResources,
                 Map<DotName, String> scannedResourcePaths,
                 Map<DotName, ClassInfo> possibleSubResources, Map<DotName, String> pathInterfaces,
                 Map<DotName, String> clientInterfaces,
                 Map<DotName, MethodInfo> resourcesThatNeedCustomProducer,
    -            Map<DotName, String> httpAnnotationToMethod, List<MethodInfo> classLevelExceptionMappers) {
    +            Map<DotName, String> httpAnnotationToMethod, List<MethodInfo> classLevelExceptionMappers,
    +            Set<DotName> requestScopedResources) {
             this.index = index;
             this.scannedResources = scannedResources;
             this.scannedResourcePaths = scannedResourcePaths;
    @@ -35,6 +38,7 @@ public ResourceScanningResult(IndexView index, Map<DotName, ClassInfo> scannedRe
             this.resourcesThatNeedCustomProducer = resourcesThatNeedCustomProducer;
             this.httpAnnotationToMethod = httpAnnotationToMethod;
             this.classLevelExceptionMappers = classLevelExceptionMappers;
    +        this.requestScopedResources = requestScopedResources;
         }
     
         public IndexView getIndex() {
    @@ -72,4 +76,8 @@ public Map<DotName, String> getHttpAnnotationToMethod() {
         public List<MethodInfo> getClassLevelExceptionMappers() {
             return classLevelExceptionMappers;
         }
    +
    +    public Set<DotName> getRequestScopedResources() {
    +        return requestScopedResources;
    +    }
     }
    
  • independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java+45 1 modified
    @@ -1,12 +1,25 @@
     package org.jboss.resteasy.reactive.common.processor.scanning;
     
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.BEAN_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COOKIE_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.DELETE;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.FORM_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.GET;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEAD;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEADER_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MATRIX_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OPTIONS;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATCH;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH_PARAM;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.POST;
     import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUT;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.QUERY_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_COOKIE_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_HEADER_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MATRIX_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_PATH_PARAM;
    +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_QUERY_PARAM;
     
     import java.lang.reflect.InvocationTargetException;
     import java.lang.reflect.Modifier;
    @@ -32,6 +45,7 @@
     import org.jboss.jandex.AnnotationTarget;
     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;
    @@ -226,6 +240,7 @@ public static ResourceScanningResult scanResources(
             Map<DotName, String> pathInterfaces = new HashMap<>();
             Map<DotName, MethodInfo> resourcesThatNeedCustomProducer = new HashMap<>();
             List<MethodInfo> methodExceptionMappers = new ArrayList<>();
    +        Set<DotName> requestScopedResources = new HashSet<>();
     
             Set<DotName> interfacesWithPathOnMethods = new HashSet<>();
     
    @@ -242,6 +257,9 @@ public static ResourceScanningResult scanResources(
                     if (ctor != null) {
                         resourcesThatNeedCustomProducer.put(clazz.name(), ctor);
                     }
    +                if (hasJaxRsFieldInjection(clazz, index)) {
    +                    requestScopedResources.add(clazz.name());
    +                }
                     List<AnnotationInstance> exceptionMapperAnnotationInstances = clazz.annotationsMap()
                             .get(ResteasyReactiveDotNames.SERVER_EXCEPTION_MAPPER);
                     if (exceptionMapperAnnotationInstances != null) {
    @@ -377,7 +395,7 @@ public static ResourceScanningResult scanResources(
     
             return new ResourceScanningResult(index, scannedResources,
                     scannedResourcePaths, possibleSubResources, pathInterfaces, clientInterfaces, resourcesThatNeedCustomProducer,
    -                httpAnnotationToMethod, methodExceptionMappers);
    +                httpAnnotationToMethod, methodExceptionMappers, requestScopedResources);
         }
     
         private static void addClientSubInterfaces(DotName interfaceName, IndexView index,
    @@ -419,4 +437,30 @@ private static MethodInfo hasJaxRsCtorParams(ClassInfo classInfo) {
             return needsHandling ? ctor : null;
         }
     
    +    public static final Set<DotName> ANNOTATIONS_REQUIRING_FIELD_INJECTION = new HashSet<>(
    +            Arrays.asList(PATH_PARAM, QUERY_PARAM, HEADER_PARAM, FORM_PARAM, MATRIX_PARAM,
    +                    COOKIE_PARAM, REST_PATH_PARAM, REST_QUERY_PARAM, REST_HEADER_PARAM, REST_FORM_PARAM, REST_MATRIX_PARAM,
    +                    REST_COOKIE_PARAM, BEAN_PARAM));
    +
    +    private static boolean hasJaxRsFieldInjection(ClassInfo classInfo, IndexView index) {
    +        while (true) {
    +            for (FieldInfo field : classInfo.fields()) {
    +                List<AnnotationInstance> annotations = field.annotations();
    +                if (annotations.stream()
    +                        .anyMatch(an -> ANNOTATIONS_REQUIRING_FIELD_INJECTION.contains(an.name()))) {
    +                    return true;
    +                }
    +            }
    +            DotName parentDotName = classInfo.superName();
    +            if (parentDotName.equals(ResteasyReactiveDotNames.OBJECT)) {
    +                return false;
    +            }
    +            classInfo = index.getClassByName(parentDotName);
    +            if (classInfo == null) {
    +                return false;
    +            }
    +        }
    +
    +    }
    +
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.