Merge pull request #1340 from jamezp/cdi-updates Introduce CDI requirements for deprecated `@Context` injection
diff --git a/jaxrs-api/pom.xml b/jaxrs-api/pom.xml index 765f14b..522c6be 100644 --- a/jaxrs-api/pom.xml +++ b/jaxrs-api/pom.xml
@@ -86,6 +86,14 @@ <dependencies> <dependency> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> + </dependency> + <dependency> + <groupId>jakarta.inject</groupId> + <artifactId>jakarta.inject-api</artifactId> + </dependency> + <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope>
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/ApplicationPath.java b/jaxrs-api/src/main/java/jakarta/ws/rs/ApplicationPath.java index f4ef062..b5743c1 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/ApplicationPath.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/ApplicationPath.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.util.AnnotationLiteral; + /** * Identifies the application path that serves as the base URI for all resource URIs provided by * {@link jakarta.ws.rs.Path}. May only be applied to a subclass of {@link jakarta.ws.rs.core.Application}. @@ -40,6 +45,8 @@ @Documented @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) +@ApplicationScoped +@Stereotype public @interface ApplicationPath { /** @@ -55,4 +62,41 @@ * @return base URI. */ String value(); + + /** + * Supports inline instantiation of the {@link ApplicationPath} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<ApplicationPath> implements ApplicationPath { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default ApplicationPath literal + */ + public static final ApplicationPath INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a ApplicationPath annotation. + * + * @param value the base path value + * + * @return the new literal ApplicationPath + */ + public static ApplicationPath of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/BeanParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/BeanParam.java index 5c22a49..064cbc3 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/BeanParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/BeanParam.java
@@ -16,20 +16,29 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Qualifier; + /** * The annotation that may be used to inject custom JAX-RS "parameter aggregator" value object into a resource class * field, property or resource method parameter. * <p> - * The JAX-RS runtime will instantiate the object and inject all its fields and properties annotated with either one of - * the {@code @XxxParam} annotation ({@link PathParam @PathParam}, {@link FormParam @FormParam} ...) or the - * {@link jakarta.ws.rs.core.Context @Context} annotation. For the POJO classes same instantiation and injection rules - * apply as in case of instantiation and injection of request-scoped root resource classes. + * When running with an active CDI container, the bean is created and injected by CDI like any other CDI-managed + * type: fields and properties annotated with {@link jakarta.inject.Inject @Inject} are injected normally, and + * fields and properties annotated with one of the {@code @XxxParam} annotations ({@link PathParam @PathParam}, + * {@link FormParam @FormParam} ...) are resolved from the current request context using the same CDI qualifier + * mechanism used for resource method parameters. + * <p> + * Without an active CDI container, the JAX-RS runtime instantiates the object and injects all its fields and + * properties annotated with one of the {@code @XxxParam} annotations. For the POJO classes the same instantiation + * and injection rules apply as in the case of instantiation and injection of request-scoped root resource classes. * </p> * For example: * @@ -68,5 +77,22 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface BeanParam { + + /** + * Supports inline instantiation of the {@link BeanParam} annotation. + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<BeanParam> implements BeanParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default BeanParam literal + */ + public static final BeanParam INSTANCE = new Literal(); + + private Literal() {} + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/CookieParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/CookieParam.java index 8ec0326..1d8f247 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/CookieParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/CookieParam.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + /** * Binds the value of a HTTP cookie to a resource method parameter, resource class field, or resource class bean * property. A default value can be specified using the {@link DefaultValue} annotation. @@ -55,6 +60,7 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface CookieParam { /** @@ -63,5 +69,43 @@ * * @return HTTP cookie name. */ + @Nonbinding String value(); + + /** + * Supports inline instantiation of the {@link CookieParam} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<CookieParam> implements CookieParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default CookieParam literal + */ + public static final CookieParam INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a CookieParam annotation. + * + * @param value the cookie value + * + * @return the new literal cookie param + */ + public static CookieParam of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/FormParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/FormParam.java index 1df6ae1..8285a49 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/FormParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/FormParam.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + /** * Binds the value(s) of a form parameter contained within a request entity body to a resource method parameter. Values * are URL decoded unless this is disabled using the {@link Encoded} annotation. A default value can be specified using @@ -66,6 +71,7 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface FormParam { /** @@ -76,5 +82,43 @@ * * @return form parameter name. */ + @Nonbinding String value(); + + /** + * Supports inline instantiation of the {@link FormParam} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<FormParam> implements FormParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default FormParam literal + */ + public static final FormParam INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a FormParam annotation. + * + * @param value the form value + * + * @return the new literal form param + */ + public static FormParam of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/HeaderParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/HeaderParam.java index 2bdc640..c046055 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/HeaderParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/HeaderParam.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + /** * Binds the value(s) of a HTTP header to a resource method parameter, resource class field, or resource class bean * property. A default value can be specified using the {@link DefaultValue} annotation. @@ -58,6 +63,7 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface HeaderParam { /** @@ -66,5 +72,43 @@ * * @return HTTP header name. */ + @Nonbinding String value(); + + /** + * Supports inline instantiation of the {@link HeaderParam} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<HeaderParam> implements HeaderParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default HeaderParam literal + */ + public static final HeaderParam INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a HeaderParam annotation. + * + * @param value the header value + * + * @return the new literal header param + */ + public static HeaderParam of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/MatrixParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/MatrixParam.java index 1abd05e..62a584d 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/MatrixParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/MatrixParam.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + /** * Binds the value(s) of a URI matrix parameter to a resource method parameter, resource class field, or resource class * bean property. Values are URL decoded unless this is disabled using the {@link Encoded} annotation. A default value @@ -66,6 +71,7 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface MatrixParam { /** @@ -76,5 +82,43 @@ * * @return URI matrix parameter name. */ + @Nonbinding String value(); + + /** + * Supports inline instantiation of the {@link MatrixParam} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<MatrixParam> implements MatrixParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default MatrixParam literal + */ + public static final MatrixParam INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a MatrixParam annotation. + * + * @param value the matrix value + * + * @return the new literal matrix param + */ + public static MatrixParam of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/Path.java b/jaxrs-api/src/main/java/jakarta/ws/rs/Path.java index 5245dc6..2f72263 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/Path.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/Path.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.util.AnnotationLiteral; + /** * Identifies the URI path that a resource class or class method will serve requests for. * @@ -65,6 +70,8 @@ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@RequestScoped +@Stereotype public @interface Path { /** @@ -105,4 +112,41 @@ * @return URI template. */ String value(); + + /** + * Supports inline instantiation of the {@link Path} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<Path> implements Path { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default Path literal + */ + public static final Path INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a Path annotation. + * + * @param value the base path value + * + * @return the new literal Path + */ + public static Path of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/PathParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/PathParam.java index 93850e8..0b9775e 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/PathParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/PathParam.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + /** * Binds the value of a URI template parameter or a path segment containing the template parameter to a resource method * parameter, resource class field, or resource class bean property. The value is URL decoded unless this is disabled @@ -67,6 +72,7 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface PathParam { /** @@ -80,5 +86,43 @@ * * @return resource URI template parameter name. */ + @Nonbinding String value(); + + /** + * Supports inline instantiation of the {@link PathParam} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<PathParam> implements PathParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default PathParam literal + */ + public static final PathParam INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a PathParam annotation. + * + * @param value the path value + * + * @return the new literal path param + */ + public static PathParam of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/QueryParam.java b/jaxrs-api/src/main/java/jakarta/ws/rs/QueryParam.java index 423258b..e737379 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/QueryParam.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/QueryParam.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + /** * Binds the value(s) of a HTTP query parameter to a resource method parameter, resource class field, or resource class * bean property. Values are URL decoded unless this is disabled using the {@link Encoded} annotation. A default value @@ -60,6 +65,7 @@ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Qualifier public @interface QueryParam { /** @@ -70,5 +76,43 @@ * * @return HTTP query parameter name. */ + @Nonbinding String value(); + + /** + * Supports inline instantiation of the {@link QueryParam} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<QueryParam> implements QueryParam { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default QueryParam literal + */ + public static final QueryParam INSTANCE = of(""); + + private final String value; + + private Literal(final String value) { + this.value = value; + } + + /** + * Obtain the literal of a QueryParam annotation. + * + * @param value the query value + * + * @return the new literal query param + */ + public static QueryParam of(final String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/container/ResourceContext.java b/jaxrs-api/src/main/java/jakarta/ws/rs/container/ResourceContext.java index 0cd23fe..577878d 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/container/ResourceContext.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/container/ResourceContext.java
@@ -16,12 +16,10 @@ package jakarta.ws.rs.container; -import jakarta.ws.rs.core.Context; - /** * The resource context provides access to instances of resource classes. * <p> - * This interface can be injected using the {@link Context} annotation. + * This interface can be injected using the {@link jakarta.inject.Inject Inject} annotation. * </p> * <p> * The resource context can be utilized when instances of managed resource classes are to be returned by sub-resource
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/container/Suspended.java b/jaxrs-api/src/main/java/jakarta/ws/rs/container/Suspended.java index fba0a46..2edd66c 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/container/Suspended.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/container/Suspended.java
@@ -76,9 +76,11 @@ * * @author Marek Potociar * @since 2.0 + * @deprecated use Jakarta Context and Dependency injection */ @Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Deprecated(forRemoval = true, since = "5.0") public @interface Suspended { }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Application.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Application.java index 6e4ea70..4d69bcd 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Application.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Application.java
@@ -25,7 +25,7 @@ * implementation supplies a concrete subclass of this abstract class. * <p> * The implementation-created instance of an Application subclass may be injected into resource classes and providers - * using {@link jakarta.ws.rs.core.Context}. + * using {@link jakarta.inject.Inject}. * </p> * <p> * In case any of the {@code Application} subclass methods or its constructor throws a {@link RuntimeException}, the
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configurable.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configurable.java index df9bba7..f05912f 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configurable.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configurable.java
@@ -261,7 +261,7 @@ * As opposed to components registered via {@link #register(Class)} method, the lifecycle of providers registered using * this instance-based {@code register(...)} is not managed by JAX-RS runtime. The same registered component instance is * used during the whole lifespan of the configurable context. Fields and properties of all registered JAX-RS component - * instances are injected with their declared dependencies (see {@link Context}) by the JAX-RS runtime prior to use. + * instances are injected with their declared dependencies by the Jakarta REST runtime prior to use. * </p> * * @param component JAX-RS component instance to be configured in the scope of this configurable context.
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configuration.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configuration.java index 54a91ae..069f562 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configuration.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Configuration.java
@@ -32,7 +32,7 @@ * component classes and/or instances. * </p> * <p> - * This interface can be injected using the {@link Context} annotation. + * This interface can be injected using the {@link jakarta.inject.Inject} annotation. * </p> * * @author Marek Potociar @@ -174,7 +174,7 @@ /** * Get the immutable set of registered JAX-RS component (such as provider or {@link Feature feature}) instances to be * utilized by the configurable instance. Fields and properties of returned instances are injected with their declared - * dependencies (see {@link Context}) by the runtime prior to use. + * dependencies by the runtime prior to use. * <p> * For each component type, there can be only a single class-based or instance-based registration present in the * configuration context at any given time.
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Context.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Context.java index 10fd7b6..fdf6417 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Context.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Context.java
@@ -38,9 +38,11 @@ * @see SecurityContext * @see jakarta.ws.rs.ext.Providers * @since 1.0 + * @deprecated use Jakarta Context and Dependency injection */ @Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Deprecated(forRemoval = true, since = "5.0") public @interface Context { }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/HttpHeaders.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/HttpHeaders.java index 1f27f82..e20a231 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/HttpHeaders.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/HttpHeaders.java
@@ -28,7 +28,6 @@ * * @author Paul Sandoz * @author Marc Hadley - * @see Context * @since 1.0 */ public interface HttpHeaders {
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/SecurityContext.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/SecurityContext.java index aeabef6..c582e6f 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/SecurityContext.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/SecurityContext.java
@@ -23,7 +23,6 @@ * * @author Paul Sandoz * @author Marc Hadley - * @see Context * @since 1.0 */ public interface SecurityContext {
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java index 2fad294..2d6b2ef 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java
@@ -32,7 +32,6 @@ * * @author Paul Sandoz * @author Marc Hadley - * @see Context * @since 1.0 */ public interface UriInfo {
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Provider.java b/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Provider.java index b8d1ae7..15c0762 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Provider.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Provider.java
@@ -16,12 +16,17 @@ package jakarta.ws.rs.ext; +import java.io.Serial; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.util.AnnotationLiteral; + /** * Marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider * scanning phase. @@ -33,5 +38,25 @@ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@ApplicationScoped +@Stereotype public @interface Provider { + + /** + * Supports inline instantiation of the {@link Provider} annotation. + * + * @since 5.0 + */ + final class Literal extends AnnotationLiteral<Provider> implements Provider { + @Serial + private static final long serialVersionUID = 1L; + + /** + * Default Provider literal + */ + public static final Provider INSTANCE = new Literal(); + + private Literal() { + } + } }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Providers.java b/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Providers.java index ff518b5..e404526 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Providers.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/ext/Providers.java
@@ -26,7 +26,6 @@ * * @author Paul Sandoz * @author Marc Hadley - * @see jakarta.ws.rs.core.Context * @see MessageBodyReader * @see MessageBodyWriter * @see ContextResolver
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java b/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java index b245435..5101501 100644 --- a/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java +++ b/jaxrs-api/src/main/java/jakarta/ws/rs/sse/SseEventSink.java
@@ -28,7 +28,7 @@ * @GET * @Path("eventStream") * @Produces(MediaType.SERVER_SENT_EVENTS) - * public void eventStream(@Context SseEventSink eventSink) { + * public void eventStream(SseEventSink eventSink) { * // ... * } * </pre>
diff --git a/jaxrs-api/src/main/java/module-info.java b/jaxrs-api/src/main/java/module-info.java index b0dd78b..4a659ae 100644 --- a/jaxrs-api/src/main/java/module-info.java +++ b/jaxrs-api/src/main/java/module-info.java
@@ -20,6 +20,8 @@ module jakarta.ws.rs { requires java.logging; + requires jakarta.cdi; + requires jakarta.inject; exports jakarta.ws.rs; exports jakarta.ws.rs.client;
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/appendix/_bibliography.adoc b/jaxrs-spec/src/main/asciidoc/chapters/appendix/_bibliography.adoc index 4248312..b1446a9 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/appendix/_bibliography.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/appendix/_bibliography.adoc
@@ -47,8 +47,8 @@ - [[[bib13,13]]] Jakarta^®^ Concurrency Specification, Version 2.0. Available at: https://jakarta.ee/specifications/concurrency/2.0/ -- [[[bib14,14]]] Jakarta^®^ Contexts and Dependency Injection Specification, Version 2.0. Available at: - https://jakarta.ee/specifications/cdi/2.0/ +- [[[bib14,14]]] Jakarta^®^ Contexts and Dependency Injection Specification, Version 5.0. Available at: + https://jakarta.ee/specifications/cdi/5.0/ - [[[bib15,15]]] Jakarta^®^ Annotations Specification, Version 2.0. Available at: https://jakarta.ee/specifications/annotations/2.0/
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-4.0-release.adoc b/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-4.0-release.adoc index ed3c003..4f30a6a 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-4.0-release.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-4.0-release.adoc
@@ -12,3 +12,25 @@ === Changes Since 4.0 Release * <<standard_entity_providers>>: Added entity provider for `java.nio.file.Path` +* <<cdi>>: Required CDI 5.0 or later for proper integration of Jakarta REST and CDI injection (https://github.com/jakartaee/rest/issues/569[#569]) +* <<cdi-injection-model>>: Documented comprehensive CDI injection model for resources and providers (https://github.com/jakartaee/rest/issues/569[#569]) +* <<cdi>>: Updated `@ApplicationPath`, `@Path`, and `@Provider` annotations to be CDI stereotype +annotations with default scopes (`@ApplicationScoped` for `@ApplicationPath` and `@Provider`, +`@RequestScoped` for `@Path`) (https://github.com/jakartaee/rest/issues/556[#556], https://github.com/jakartaee/rest/issues/952[#952]) +* <<cdi-injection-model>>: Parameter extraction annotations (`@QueryParam`, `@PathParam`, +`@HeaderParam`, `@MatrixParam`, `@CookieParam`, `@FormParam`) now act as CDI qualifiers (https://github.com/jakartaee/rest/issues/569[#569]) +* <<cdi-injection-model>>: Fields in CDI-managed beans must use `@Inject` instead of the +deprecated `@Context` annotation (https://github.com/jakartaee/rest/issues/569[#569], https://github.com/jakartaee/rest/issues/1209[#1209]) +* <<cdi-injection-model>>: Resource method parameters support only Jakarta REST context types, +parameter extraction annotations, entity parameters, `AsyncResponse`, and `SseEventSink`; +arbitrary CDI beans must be injected via fields or constructors (https://github.com/jakartaee/rest/issues/569[#569]) +* <<cdi-injection-model>>: Clarified that `AsyncResponse` and `SseEventSink` can only be +used as resource method parameters, not as injectable fields (https://github.com/jakartaee/rest/issues/569[#569]) +* <<cdi-producers>>: Required CDI producers for Jakarta REST context types and parameter +extraction annotations (https://github.com/jakartaee/rest/issues/569[#569]) +* Deprecated `@Context` annotation for removal in a future release (https://github.com/jakartaee/rest/issues/569[#569], https://github.com/jakartaee/rest/issues/1209[#1209]) +* Deprecated `@Suspended` annotation in 5.0 for removal in a future release; use type-alone approach +for `AsyncResponse` in CDI environments (https://github.com/jakartaee/rest/issues/1209[#1209]) +* <<servlet_container>>: Deprecated `@Context` injection for Servlet-defined types; use CDI +injection (`@Inject`) as specified by Jakarta Servlet and Jakarta EE Platform specifications (https://github.com/jakartaee/rest/issues/1214[#1214]) +* Scoped Additional Requirements section to non-CDI environments; CDI specification defines bean lifecycle behaviors for CDI-managed beans (https://github.com/jakartaee/rest/issues/1215[#1215])
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/context/_contexttypes.adoc b/jaxrs-spec/src/main/asciidoc/chapters/context/_contexttypes.adoc index 13a1a05..3d76bda 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/context/_contexttypes.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/context/_contexttypes.adoc
@@ -17,12 +17,27 @@ which are injectable in both client and server-side providers, all the other types are server-side only. +[NOTE] +==== +In CDI-enabled environments, these context types MUST be made available +for injection via CDI producers as specified in <<cdi-producers>>. The +use of `@Context` annotation is deprecated in favor of CDI injection using +`@Inject` (for fields and constructors) or type-based injection (for +resource method parameters). See <<cdi-injection-model>> for details on +the CDI injection model for Jakarta REST. + +For `Client` instances and `SeBootstrap`-based deployments, `@Inject` for +`Configuration` and `Providers` MUST be supported when CDI is available. +Where no CDI container is present, the deprecated `@Context` annotation +remains fully supported for these types. +==== + [[application]] ==== Application The instance of the application-supplied `Application` subclass can be -injected into a class field or method parameter using the -`@Context` annotation. Access to the `Application` subclass instance +injected into a class field using the `@Inject` annotation or as a resource +method parameter. Access to the `Application` subclass instance allows configuration information to be centralized in that class. Note that this cannot be injected into the `Application` subclass itself since this would create a circular dependency. @@ -30,9 +45,9 @@ [[uris-and-uri-templates]] ==== URIs and URI Templates -An instance of `UriInfo` can be injected into a class field or method -parameter using the `@Context` annotation. `UriInfo` provides both -static and dynamic, per-request information, about the components of a +An instance of `UriInfo` can be injected into a class field using the +`@Inject` annotation or as a resource method parameter. +`UriInfo` provides both static and dynamic, per-request information, about the components of a request URI. E.g. the following would return the names of any query parameters in a request: @@ -40,11 +55,29 @@ ---- @GET @Produces("text/plain") -public String listQueryParamNames(@Context UriInfo info) { +public String listQueryParamNames(UriInfo info) { StringBuilder buf = new StringBuilder(); for (String param: info.getQueryParameters().keySet()) { buf.append(param); - buf.append("\n"); + buf.append('\n'); + } + return buf.toString(); +} +---- + +[source,java] +---- + +@Inject +UriInfo info; + +@GET +@Produces("text/plain") +public String listQueryParamNames() { + StringBuilder buf = new StringBuilder(); + for (String param: info.getQueryParameters().keySet()) { + buf.append(param); + buf.append('\n'); } return buf.toString(); } @@ -56,8 +89,8 @@ [[httpheaders]] ==== Headers -An instance of `HttpHeaders` can be injected into a class field or -method parameter using the `@Context` annotation. `HttpHeaders` provides +An instance of `HttpHeaders` can be injected into a class field using the +`@Inject` annotation or as a resource method parameter. `HttpHeaders` provides access to request header information either in map form or via strongly typed convenience methods. E.g. the following would return the names of all the headers in a request: @@ -66,11 +99,28 @@ ---- @GET @Produces("text/plain") -public String listHeaderNames(@Context HttpHeaders headers) { +public String listHeaderNames(HttpHeaders headers) { StringBuilder buf = new StringBuilder(); for (String header: headers.getRequestHeaders().keySet()) { buf.append(header); - buf.append("\n"); + buf.append('\n'); + } + return buf.toString(); +} +---- + +[source,java] +---- +@Inject +HttpHeaders headers; + +@GET +@Produces("text/plain") +public String listHeaderNames() { + StringBuilder buf = new StringBuilder(); + for (String header: headers.getRequestHeaders().keySet()) { + buf.append(header); + buf.append('\n'); } return buf.toString(); } @@ -86,8 +136,9 @@ ==== Content Negotiation and Preconditions JAX-RS simplifies support for content negotiation and preconditions -using the `Request` interface. An instance of `Request` can be injected -into a class field or method parameter using the `@Context` annotation. +using the `Request` interface. An instance of `Request` can be injected +into a class field using the `@Inject` annotation or as a resource method +parameter. The methods of `Request` allow a caller to determine the best matching representation variant and to evaluate whether the current state of the resource matches any preconditions in the request. Precondition support @@ -99,7 +150,23 @@ [source,java] ---- @PUT -public Response updateFoo(@Context Request request, Foo foo) { +public Response updateFoo(Request request, Foo foo) { + EntityTag tag = getCurrentTag(); + ResponseBuilder responseBuilder = request.evaluatePreconditions(tag); + if (responseBuilder != null) + return responseBuilder.build(); + else + return doUpdate(foo); +} +---- + +[source,java] +---- +@Inject +Request request; + +@PUT +public Response updateFoo(Foo foo) { EntityTag tag = getCurrentTag(); ResponseBuilder responseBuilder = request.evaluatePreconditions(tag); if (responseBuilder != null) @@ -118,23 +185,59 @@ The `SecurityContext` interface provides access to information about the security context of the current request. An instance of -`SecurityContext` can be injected into a class field or method parameter -using the `@Context` annotation. The methods of -`SecurityContext` provide access to the current user principal, +`SecurityContext` can be injected into a class field +using the `@Inject` annotation or as a resource method parameter. +The methods of `SecurityContext` provide access to the current user principal, information about roles assumed by the requester, whether the request arrived over a secure channel and the authentication scheme used. +[source,java] +---- +@Inject +SecurityContext securityContext; + +@GET +@Produces("text/plain") +public String getSecureData() { + if (securityContext.isUserInRole("admin")) { + return "Sensitive admin data"; + } + return "Public data"; +} +---- + [[providercontext]] ==== Providers The `Providers` interface allows for lookup of provider instances based on a set of search criteria. An instance of `Providers` can be injected -into a class field or method parameter using the `@Context` annotation. +into a class field using the `@Inject` annotation or as a resource method +parameter. This interface is expected to be primarily of interest to provider authors wishing to use other providers functionality. It is injectable in both client and server providers. +[source,java] +---- +@Provider +public class CustomMessageBodyWriter implements MessageBodyWriter<MyType> { + @Inject + Providers providers; + + @Override + public void writeTo(MyType t, Class<?> type, Type genericType, + Annotation[] annotations, MediaType mediaType, + MultivaluedMap<String, Object> httpHeaders, + OutputStream entityStream) throws IOException { + MessageBodyWriter<String> stringWriter = + providers.getMessageBodyWriter(String.class, String.class, + annotations, mediaType); + // Use the string writer to serialize MyType as a string + } +} +---- + [[resource_context]] ==== Resource Context @@ -151,7 +254,7 @@ ---- @Path("widgets") public class WidgetsResource { - @Context + @Inject private ResourceContext rc; @Path("{id}") @@ -161,7 +264,7 @@ } public class WidgetResource { - @Context + @Inject private HttpHeaders headers; public WidgetResource(String id) {...} @@ -180,7 +283,7 @@ ==== Configuration Both the client and the server runtime configurations are available for -injection via `@Context`. These configurations are available for +injection via `@Inject`. These configurations are available for injection in providers (client or server) and resource classes (server only). @@ -190,9 +293,10 @@ [source,java] ---- +@Provider public class LoggingFilter implements ClientRequestFilter { - @Context + @Inject private Configuration config; @Override
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/environment/_javaee.adoc b/jaxrs-spec/src/main/asciidoc/chapters/environment/_javaee.adoc index cb76746..aa72655 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/environment/_javaee.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/environment/_javaee.adoc
@@ -31,7 +31,7 @@ a Servlet container whose version is prior to 3. As explained in <<servlet_container>>, injection of -Servlet-defined types is possible using the `@Context` annotation. +Servlet-defined types is possible using the `@Inject` annotation. Additionally, web application’s `<context-param>` and servlet’s `<init-param>` can be used to define application properties passed to server-side features or injected into server-side JAX-RS components. See @@ -42,8 +42,14 @@ In a product that supports CDI, implementations MUST support the use of CDI-style Beans as root resource classes, providers and `Application` -subclasses. Providers and `Application` subclasses MUST be singletons or -use application scope. +subclasses. Providers and `Application` subclasses default to application +scope (see <<cdi-injection-model>>); it is RECOMMENDED that these classes +remain application-scoped, since they are typically stateless, but +applications MAY override the default with an explicit CDI scope. + +Implementations MUST support Jakarta CDI 5.0 or later. This version of CDI +is required for proper integration of Jakarta REST injection and CDI dependency +injection as described in <<cdi-injection-model>>. For example, assuming CDI is enabled via the inclusion of a `beans.xml` file, a CDI-style bean that can be defined as a JAX-RS resource as @@ -66,8 +72,372 @@ The example above takes advantage of the type-safe dependency injection provided in CDI by using another bean, of type `MyOtherCdiBean`, in -order to return a resource representation. See <<additional_reqs>> -for additional requirements on CDI-style Beans. +order to return a resource representation. See <<cdi-injection-model>> +for details on the CDI injection model. + +[[cdi-injection-model]] +===== Injection Model for CDI-Managed Resources + +When Jakarta REST resources and providers are managed as CDI beans, a hybrid +injection model applies that combines Jakarta REST parameter extraction with +CDI dependency injection. + +====== Bean Lifecycle + +CDI manages the bean lifecycle, including bean instantiation and destruction. +Jakarta REST defines the following CDI stereotype annotations that enable automatic +CDI discovery: + +* `@ApplicationPath` - annotated with `@ApplicationScoped`, applied to `Application` + subclasses to define the base URI for all resource URIs +* `@Path` - annotated with `@RequestScoped`, applied to resource classes to define + URI path templates +* `@Provider` - annotated with `@ApplicationScoped`, applied to provider classes + (message body readers/writers, exception mappers, filters, interceptors, etc.) + +The defined scopes ensure proper lifecycle management: `@ApplicationPath` and +`@Provider` annotated classes are application-scoped singletons, while `@Path` +annotated resource classes are request-scoped and created per-request by default. +Applications MAY override these default scopes by explicitly annotating classes +with different CDI scope annotations. + +====== Field Injection + +Fields in CDI-managed beans MUST use `@Inject` instead of the deprecated `@Context` +annotation. This applies to both Jakarta REST context types and Jakarta REST parameter +extraction: + +[source,java] +---- +@Path("/items") +public class ItemResource { + @Inject // Context type injection + UriInfo uriInfo; + + @Inject // CDI bean injection + ItemService service; + + @Inject + @QueryParam("filter") // Parameter extraction + String filter; + + @Inject + @PathParam("id") // Parameter extraction + String id; +} +---- + +All field injection is handled by CDI. Jakarta REST parameter annotations (`@QueryParam`, +`@PathParam`, `@HeaderParam`, `@MatrixParam`, `@CookieParam`, `@FormParam`, `@BeanParam`) +act as CDI qualifiers. Implementations MUST make these qualified injection points +resolvable through CDI, extracting the value from the current request context at +injection time, for any type a resource or provider declares—including +application-defined converter types and generic collections. Implementations MAY +satisfy this requirement using CDI producer methods, synthetic beans, or portable +extensions, as appropriate to the declared type. Where a producer method is used, it +MUST be `@Dependent` scoped and MAY use the CDI `InjectionPoint` API to determine the +parameter name from the qualifier annotation. + +====== Constructor Injection + +Constructor parameters in CDI-managed beans are injected using CDI. The constructor +MUST be annotated with `@Inject`. Jakarta REST-specific types (UriInfo, HttpHeaders, +etc.) and parameter annotations (`@QueryParam`, `@PathParam`, etc.) are made available +via CDI producers as described in <<cdi-producers>>. + +[source,java] +---- +@Path("/items") +public class ItemResource { + private final ItemService service; + private final UriInfo uriInfo; + private final String category; + + @Inject + public ItemResource(ItemService service, + UriInfo uriInfo, + @QueryParam("category") String category) { + this.service = service; + this.uriInfo = uriInfo; + this.category = category; + } +} +---- + +NOTE: Jakarta REST parameter annotations (`@QueryParam`, `@PathParam`, etc.) MUST +only be used on injection points of a bean whose scope guarantees a new bean +instance per Jakarta REST request (the default `@RequestScoped` scope for +`@Path`-annotated classes satisfies this). Applications that violate this +constraint have undefined results, including the possibility of a stale value +being retained and reused across unrelated requests. Implementations MAY detect +and reject known-unsafe usages at deployment time. + +====== Setter Method Injection + +CDI-managed beans MAY use setter methods annotated with `@Inject` to receive +Jakarta REST parameter values. The parameter annotation (`@PathParam`, `@QueryParam`, +etc.) may be placed on either the method or the method parameter: + +[source,java] +---- +@Path("/items/{id}") +public class ItemResource { + + private String id; + private String filter; + + // Annotation on parameter (recommended for CDI) + @Inject + public void setId(@PathParam("id") String id) { + this.id = id; + } + + // Annotation on method (legacy form) + @Inject + @QueryParam("filter") + public void setFilter(String filter) { + this.filter = filter; + } +} +---- + +When a parameter annotation is placed on the method rather than the parameter, +implementations MUST treat the annotation as if it were present on the method's +parameter for CDI qualifier resolution purposes. Only a single parameter is allowed +if annotations are placed on a method. Both forms are valid; the +parameter-level placement is the natural fit for CDI environments since CDI resolves +qualifiers from injection points (parameters), not from the enclosing method. + +====== Resource Method Parameter Injection + +Resource method parameters are resolved using a combination of Jakarta REST +parameter extraction and CDI producers. Implementations MAY use CDI to invoke +resource methods, but only the following parameter types are supported: + +Parameters are categorized as follows: + +* **Path, query, header, matrix, cookie, and form parameters**: Annotated with + `@PathParam`, `@QueryParam`, `@HeaderParam`, `@MatrixParam`, `@CookieParam`, + or `@FormParam`. These annotations are CDI qualifiers. Implementations MUST + provide CDI producers (typically `@Dependent` scoped) that use the `InjectionPoint` + API to extract the parameter name from the qualifier and retrieve the value from + the request context. + +* **Bean parameters**: Annotated with `@BeanParam` to inject a custom parameter + aggregator object. The `@BeanParam` annotation is a CDI qualifier. When running + with an active CDI container, the aggregator bean is created and injected by CDI + like any other CDI-managed type: `@Inject` fields are injected normally, and + fields annotated with parameter extraction annotations are resolved via the same + qualifier mechanism used for resource method parameters (see <<cdi-producers>>). + +* **Jakarta REST context types**: `Application`, `Configuration`, `HttpHeaders`, + `Providers`, `Request`, `ResourceContext`, `ResourceInfo`, `SecurityContext`, + `Sse`, and `UriInfo`. These types are identified by type alone. Implementations MUST + provide CDI producers as specified in <<cdi-producers>>. + +* **Entity parameter**: The request entity body, deserialized using a `MessageBodyReader`. + Identified as a parameter that is NOT annotated with any Jakarta REST parameter annotation, + is NOT a Jakarta REST context type (listed above), and is NOT `AsyncResponse` or `SseEventSink`. + Only one entity parameter is allowed per resource method. + +* **Jakarta REST-managed special parameters**: The following parameters are NOT + CDI beans and are handled directly by the Jakarta REST implementation: + ** **AsyncResponse**: Used for asynchronous processing. Identified by type alone + (no annotation required). Can only be injected as a resource method parameter. + ** **SseEventSink**: Used for server-sent events. Identified by type alone + (no annotation required). Can only be injected as a resource method parameter. + +NOTE: In previous versions, `AsyncResponse` required the `@Suspended` annotation. +This annotation is deprecated for removal in a future release. In CDI environments, +the type-alone approach (no annotation) is the recommended pattern. The `@Suspended` +annotation continues to work for backward compatibility. + +IMPORTANT: Application-provided CDI beans CANNOT be injected as resource method +parameters. To inject CDI beans into resources, use field injection with `@Inject` +or constructor injection as shown in <<cdi-injection-model>>. + +[source,java] +---- +@GET +@Path("{id}") +public Item get(@PathParam("id") String id, + @QueryParam("expand") String expand, + UriInfo uriInfo) { + return findItem(id, expand, uriInfo); +} + +@POST +@Consumes(MediaType.APPLICATION_JSON) +public Response create(Customer customer, // Jakarta REST deserializes from request body + UriInfo uriInfo) { + return Response.created(uriInfo.getAbsolutePath()).build(); +} + +@POST +@Path("async") +public void createAsync(Customer customer, + AsyncResponse asyncResponse) { // Type-checked, no annotation needed + asyncResponse.resume(Response.ok().build()); +} + +// Bean parameter example +public class SearchParams { + @Inject + @QueryParam("q") + String query; + + @Inject + @QueryParam("limit") + @DefaultValue("10") + int limit; + + @Inject + @HeaderParam("Accept-Language") + String language; +} + +@GET +@Path("search") +public List<Item> search(@BeanParam SearchParams params) { // CDI creates and injects params + return searchItems(params.query, params.limit, params.language); +} +---- + +NOTE: Jakarta CDI 5.0 provides method invokers (`jakarta.enterprise.invoke.Invoker`) +that enable frameworks to invoke methods with automatic CDI-based parameter injection. +Implementations MAY use this API to integrate Jakarta REST parameter conversion with +CDI injection. + +[[cdi-producers]] +====== Required CDI Producers + +Implementations MUST provide CDI producers for Jakarta REST context types and +parameter extraction to enable their injection into CDI-managed beans. + +**Context Type Producers** + +The following context types MUST be made available for CDI injection. The suggested +scope for each type is listed, though implementations MAY use different scopes if +appropriate: + +.Required CDI Producers for Jakarta REST Types +|=== +|Type |Suggested Scope |Description + +|`Application` +|`@ApplicationScoped` +|Application configuration and metadata + +|`Configuration` +|`@Dependent` +|Runtime configuration + +|`HttpHeaders` +|`@RequestScoped` +|Request HTTP headers + +|`Providers` +|`@ApplicationScoped` +|Provider instance lookup + +|`Request` +|`@RequestScoped` +|Request information for content negotiation + +|`ResourceContext` +|`@ApplicationScoped` +|Resource instance creation and initialization + +|`ResourceInfo` +|`@RequestScoped` +|Matched resource class and method information + +|`SecurityContext` +|`@RequestScoped` +|Security context of current request + +|`Sse` +|`@ApplicationScoped` +|Server-sent events support + +|`UriInfo` +|`@RequestScoped` +|URI and path information +|=== + +NOTE: The types `AsyncResponse` and `SseEventSink` are method-parameter-only types +and do NOT require CDI producers. These types are only available for injection as +resource method parameters and cannot be injected into fields or constructors. The +Jakarta REST implementation handles these types directly. + +**Parameter Extraction Producers** + +Implementations MUST provide CDI producers for Jakarta REST parameter annotations +that act as CDI qualifiers: + +* `@PathParam` - Extract path parameter values from URI path templates +* `@QueryParam` - Extract query parameter values from the request URI +* `@HeaderParam` - Extract HTTP header values +* `@MatrixParam` - Extract matrix parameter values from URI path segments +* `@CookieParam` - Extract cookie values +* `@FormParam` - Extract form parameter values from request entity +* `@BeanParam` - Create and populate a parameter aggregator bean + +These producers MUST be `@Dependent` scoped and could use the CDI `InjectionPoint` +API to: + +1. Inspect the qualifier annotation to determine the parameter name +2. Extract the value from the current request context +3. Convert the value to the target type using the same conversion rules as + specified in <<resource_field>> + +NOTE: Entity parameters do NOT require a CDI producer. They are handled directly +by the Jakarta REST implementation using `MessageBodyReader` instances to deserialize +the request body. + +NOTE: Parameter extraction producers are `@Dependent` scoped and extract values +from the current request context. CDI manages injection of these values into beans +according to CDI specification scope compatibility rules. Resource method parameters +are the recommended pattern as they are always evaluated during request processing, +regardless of bean scope. For advanced scenarios involving non-request-scoped beans, +consult the CDI specification regarding scope compatibility and the `Instance<T>` +pattern for deferred resolution. + +[[cdi-application-methods]] +===== CDI Bean Discovery and Application Methods + +In CDI-enabled environments, `@Path` and `@Provider` annotated classes are +CDI-managed beans, independent of what `Application.getClasses()` returns. +The `getClasses()` method controls which classes are used for Jakarta REST +request processing, not which classes are managed by CDI. Implementations MUST +obtain resource and provider instances from CDI for classes that are CDI beans. +For classes excluded from CDI bean management (e.g., via `@Vetoed`), +implementations MAY instantiate them directly. + +Instances from `Application.getSingletons()` are not CDI-managed. + +[[additional_reqs]] +==== Additional Requirements for Non-CDI Environments + +In environments that do not support CDI, the following additional requirements +apply when using Jakarta REST resource classes, providers or `Application` +subclasses: + +* For Jakarta REST resources and providers where the Jakarta REST implementation +participates in their creation and initialization, field and property +injection in these resources and providers MUST be performed prior to the +container invoking any `@PostConstruct` annotated method. For resources +and providers created by the application (e.g. instances returned via the +`Application.getSingletons()` method, or instances passed via +`Configurable.register`), this requirement does not apply. +* Support for constructor injection of Jakarta REST resources is OPTIONAL. +Portable applications MUST instead use fields or bean properties in +conjunction with a `@PostConstruct` annotated method. Implementations +SHOULD warn users about use of non-portable constructor injection. +* Implementations MUST NOT require use of `@Inject` or `@Resource` to +trigger injection of Jakarta REST annotated fields or properties. +Implementations MAY support such usage but SHOULD warn users about +non-portability. [[ejbs]] ==== Enterprise Java Beans (EJBs) @@ -115,9 +485,7 @@ with an application then exceptions thrown by an EJB resource class or provider method MUST be unwrapped and processed as described in <<method_exc>>. -See <<async_ejbs>> for more information on asynchronous EJB -methods and <<additional_reqs>> for additional requirements on -EJBs. +See <<async_ejbs>> for more information on asynchronous EJB methods. [[bv_support]] ==== Bean Validation @@ -163,24 +531,3 @@ `text/xml` and `application/xml` and media types of the form `application/*+xml`. -[[additional_reqs]] -==== Additional Requirements - -The following additional requirements apply when using CDI-style Beans or EJBs as resource classes, providers or `Application` -subclasses: - -* For JAX-RS resources and providers where the JAX-RS implementation -participates in their creation and initialization, field and property -injection in these resources and providers MUST be performed prior to the -container invoking any `@PostConstruct` annotated method. For resources -and providers created by the application (e.g. instances returned via the -`Application.getSingletons()` method, or instances passed via -`Configurable.register`), this requirement does not apply. -* Support for constructor injection of JAX-RS resources is OPTIONAL. -Portable applications MUST instead use fields or bean properties in -conjunction with a `@PostConstruct` annotated method. Implementations -SHOULD warn users about use of non-portable constructor injection. -* Implementations MUST NOT require use of `@Inject` or `@Resource` to -trigger injection of JAX-RS annotated fields or properties. -Implementations MAY support such usage but SHOULD warn users about -non-portability.
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/environment/_servlet_container.adoc b/jaxrs-spec/src/main/asciidoc/chapters/environment/_servlet_container.adoc index e6a1914..cb74bae 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/environment/_servlet_container.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/environment/_servlet_container.adoc
@@ -11,6 +11,22 @@ [[servlet_container]] === Servlet Container + +[NOTE] +==== +The use of `@Context` for injecting Servlet-defined types is deprecated. +In environments that support CDI and Jakarta Servlet, applications should use +CDI injection (`@Inject`) for Servlet-defined types as specified by the Jakarta +Servlet and Jakarta EE Platform specifications. Consult those specifications for +details on which Servlet types are available as CDI built-in beans. + +Implementations MUST NOT register a CDI bean for a Servlet-defined type if a +bean satisfying that type is already available (for example, one provided by +the Jakarta Servlet or Jakarta EE Platform CDI integration), to avoid ambiguous +dependency resolution. Where no such bean is available, implementations MAY +register their own CDI producer for these types as a fallback. +==== + The `@Context` annotation can be used to indicate a dependency on a Servlet-defined resource. A Servlet-based implementation MUST support injection of the following Servlet-defined types: `ServletConfig`,
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/providers/_lifecycle_and_environment.adoc b/jaxrs-spec/src/main/asciidoc/chapters/providers/_lifecycle_and_environment.adoc index 122159c..7a1ec93 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/providers/_lifecycle_and_environment.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/providers/_lifecycle_and_environment.adoc
@@ -63,16 +63,21 @@ parameter values. Note that a zero argument constructor is permissible under this rule. -A public constructor MAY include parameters annotated with -`@Context`—Chapter <<context>> defines the parameter types permitted for -this annotation. Since providers may be created outside the scope of a -particular request, only deployment-specific properties may be available -from injected interfaces at construction time; request-specific -properties are available when a provider method is called. If more than -one public constructor can be used then an implementation MUST use the -one with the most parameters. Choosing amongst constructors with the -same number of parameters is implementation specific, implementations -SHOULD generate a warning about such ambiguity. +In CDI environments, a constructor that declares parameters MUST be annotated +with `@Inject` and MAY include parameters for Jakarta REST context types and +parameter extraction as described in <<cdi-injection-model>>. A public +zero-argument constructor remains valid without `@Inject`, consistent with +normal CDI bean constructor rules. In non-CDI environments, a public constructor +MAY include parameters annotated with `@Context`—Chapter <<context>> defines the +parameter types permitted for this annotation. + +Since providers may be created outside the scope of a particular request, only +deployment-specific properties may be available from injected interfaces at +construction time; request-specific properties are available when a provider +method is called. If more than one public constructor can be used then an +implementation MUST use the one with the most parameters in non-CDI environments. +Choosing amongst constructors with the same number of parameters is +implementation specific, implementations SHOULD generate a warning about such ambiguity. [[provider_priorities]] ==== Priorities
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource-classes.adoc b/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource-classes.adoc index 27ac195..cd94f2a 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource-classes.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource-classes.adoc
@@ -39,15 +39,26 @@ parameter values. Note that a zero argument constructor is permissible under this rule. -A public constructor MAY include parameters annotated with one of the -following: `@Context`, `@HeaderParam`, `@CookieParam`, `@MatrixParam`, -`@QueryParam` or `@PathParam`. However, depending on the resource class -lifecycle and concurrency, per-request information may not make sense in -a constructor. If more than one public constructor is suitable then an -implementation MUST use the one with the most parameters. Choosing -amongst suitable constructors with the same number of parameters is -implementation specific, implementations SHOULD generate a warning about -such ambiguity. +In CDI-enabled environments where the resource class is a CDI-managed bean, +the CDI container manages instantiation and constructor injection follows +CDI rules as described in <<cdi-injection-model>>. Constructor parameters +are injected by CDI, and Jakarta REST-specific types are made available +via CDI producers as specified in <<cdi-producers>>. + +In non-CDI environments, or for resources not managed by CDI, a public +constructor MAY include parameters annotated with one of the following: +`@HeaderParam`, `@CookieParam`, `@MatrixParam`, `@QueryParam` or `@PathParam`. +However, depending on the resource class lifecycle and concurrency, per-request +information may not make sense in a constructor. + +NOTE: The use of `@Context` in constructors is deprecated. Applications should +use CDI constructor injection in CDI-enabled environments, or use field/property +injection in non-CDI environments. + +In non-CDI environments if more than one public constructor is suitable then an +implementation MUST use the one with the most parameters. Choosing amongst suitable +constructors with the same number of parameters is implementation specific, +implementations SHOULD generate a warning about such ambiguity. Non-root resource classes are instantiated by an application and do not require the above-described public constructor.
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_field.adoc b/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_field.adoc index ab07dc3..01aeb15 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_field.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_field.adoc
@@ -15,6 +15,10 @@ properties annotated with one the following annotations are set according to the semantics of the annotation: +NOTE: In CDI environments, field and bean property injection is handled by CDI +using the injection model described in <<cdi-injection-model>>. The parameter +extraction annotations listed below apply in both CDI and non-CDI environments. + `@MatrixParam`:: Extracts the value of a URI matrix parameter. `@QueryParam`:: @@ -25,25 +29,26 @@ Extracts the value of a cookie. `@HeaderParam`:: Extracts the value of a header. -`@Context`:: +`@Inject`:: Injects an instance of a supported resource, see chapters <<context>> - and <<environment>> for more details. + and <<environment>> for more details. In non-CDI environments, the + deprecated `@Context` annotation may be used instead; see + <<additional_reqs>> for additional requirements. Because injection occurs at object creation time, use of these -annotations (with the exception of `@Context`) on resource class fields -and bean properties is only supported for the default per-request -resource class lifecycle. An implementation SHOULD warn if resource -classes with other lifecycles use these annotations on resource class -fields or bean properties. +annotations on resource class fields and bean properties may depend on +resource class lifecycle and scope. See <<cdi-injection-model>> for +details on scope considerations in CDI environments. -A JAX-RS implementation is only required to set the annotated field and -bean property values of instances created by its runtime. Objects -returned by sub-resource locators (see <<sub_resources>>) are -expected to be initialized by their creator. +In non-CDI environments, a JAX-RS implementation is only required to +set the annotated field and bean property values of instances created +by its runtime. Objects returned by sub-resource locators (see <<sub_resources>>) +are expected to be initialized by their creator. In CDI environments, CDI manages +injection for all CDI beans including sub-resources. Valid parameter types for each of the above annotations are listed in -the corresponding Javadoc, however in general (excluding `@Context`) the -following types are supported: +the corresponding Javadoc, however in general the following types are +supported: 1. Types for which a `ParamConverter` is available via a registered `ParamConverterProvider`. See Javadoc for these classes for
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_method.adoc b/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_method.adoc index b8185c7..0d892f4 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_method.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/resources/_resource_method.adoc
@@ -53,12 +53,29 @@ [[entity_parameters]] ===== Entity Parameters -The value of a parameter not annotated with `@FormParam` or any of the -annotations listed in in <<resource_field>>, called the entity -parameter, is mapped from the request entity body. Conversion between an -entity body and a Java type is the responsibility of an entity provider, -see <<entity_providers>>. Resource methods MUST have at most one -entity parameter. +The entity parameter represents the request entity body and is mapped from the +request entity body using an entity provider. Conversion between an entity body +and a Java type is the responsibility of an entity provider, see +<<entity_providers>>. + +The entity parameter is identified as a parameter that is NOT annotated with `@FormParam` +or any of the annotations listed in <<resource_field>>, is NOT a Jakarta REST context +type (such as `UriInfo`, `HttpHeaders`, `Request`, etc.), and is NOT `AsyncResponse` +or `SseEventSink`. + +Resource methods MUST have at most one entity parameter. + +Example: + +[source,java] +---- +@POST +@Consumes(MediaType.APPLICATION_JSON) +public Response create(final Customer customer, final HttpHeaders headers) { + // customer is deserialized from request body + return Response.ok().build(); +} +---- [[resource_method_return]] ==== Return Type
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_broadcasting.adoc b/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_broadcasting.adoc index 9c43da1..0a0b0d7 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_broadcasting.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_broadcasting.adoc
@@ -27,7 +27,7 @@ @Singleton public class SseResource { - @Context + @Inject private Sse sse; private volatile SseBroadcaster sseBroadcaster; @@ -40,7 +40,7 @@ @GET @Path("register") @Produces(MediaType.SERVER_SENT_EVENTS) - public void register(@Context SseEventSink eventSink) { + public void register(SseEventSink eventSink) { eventSink.send(sse.newEvent("welcome!")); sseBroadcaster.register(eventSink); }
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_server_api.adoc b/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_server_api.adoc index 341c951..f605a68 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_server_api.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/sse/_sse_server_api.adoc
@@ -24,8 +24,8 @@ @GET @Path("eventStream") @Produces(MediaType.SERVER_SENT_EVENTS) -public void eventStream(@Context SseEventSink eventSink, - @Context Sse sse) { +public void eventStream(SseEventSink eventSink, + Sse sse) { executor.execute(() -> { try (SseEventSink sink = eventSink) { eventSink.send(sse.newEvent("event1"));
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/validation/_validation_and_error_reporting.adoc b/jaxrs-spec/src/main/asciidoc/chapters/validation/_validation_and_error_reporting.adoc index 9d30568..5ed1830 100644 --- a/jaxrs-spec/src/main/asciidoc/chapters/validation/_validation_and_error_reporting.adoc +++ b/jaxrs-spec/src/main/asciidoc/chapters/validation/_validation_and_error_reporting.adoc
@@ -13,7 +13,7 @@ Constraint annotations are allowed in the same locations as the following annotations: `@MatrixParam`, `@QueryParam`, `@PathParam`, -`@CookieParam`, `@HeaderParam` and `@Context`, _except_ in class +`@CookieParam`, `@HeaderParam` and `@Inject`, _except_ in class constructors and property setters. Specifically, they are allowed in resource method parameters, fields and property getters as well as resource classes, entity parameters and resource methods (return
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/common/util/CdiSupport.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/common/util/CdiSupport.java new file mode 100644 index 0000000..619c2fb --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/common/util/CdiSupport.java
@@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.common.util; + +import org.jboss.shrinkwrap.api.asset.StringAsset; + +/** + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +public class CdiSupport { + + public static final StringAsset BEANS_XML = new StringAsset(""" + <?xml version="1.0" encoding="UTF-8"?> + <beans xmlns="https://jakarta.ee/xml/ns/jakartaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd" + version="4.0" bean-discovery-mode="annotated"> + </beans>"""); +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectFilter.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectFilter.java new file mode 100644 index 0000000..6fc5158 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectFilter.java
@@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.client.inject; + +import jakarta.inject.Inject; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Providers; + +/** + * A client-side filter that verifies {@code @Inject} works for {@link Configuration} and {@link Providers} as a + * replacement for the deprecated {@code @Context} annotation on the client side. The Jakarta REST runtime is + * responsible for resolving these injection points regardless of whether a CDI container is present. + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +public class ClientInjectFilter implements ClientRequestFilter { + + @Inject + private Configuration configuration; + + @Inject + private Providers providers; + + @Override + public void filter(final ClientRequestContext requestContext) { + final ClientInjectionDescriptor descriptor = ClientInjectionDescriptor.of(configuration, providers); + requestContext.abortWith(Response.ok(descriptor).type(MediaType.APPLICATION_JSON_TYPE).build()); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectionDescriptor.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectionDescriptor.java new file mode 100644 index 0000000..b9db055 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectionDescriptor.java
@@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.client.inject; + +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.ext.Providers; + +public record ClientInjectionDescriptor(String configuration, String providers) { + + public static ClientInjectionDescriptor of(final Configuration configuration, final Providers providers) { + return new ClientInjectionDescriptor( + configuration == null ? null : String.valueOf(configuration.getRuntimeType()), + providers == null ? null : providers.getClass().getName()); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectionIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectionIT.java new file mode 100644 index 0000000..8d9bed7 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/client/inject/ClientInjectionIT.java
@@ -0,0 +1,54 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.client.inject; + +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.MediaType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests that {@code @Inject} works for {@link jakarta.ws.rs.core.Configuration} and + * {@link jakarta.ws.rs.ext.Providers} in client-side providers. The Jakarta REST runtime is responsible for + * resolving these injection points regardless of whether a CDI container is present. + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@Tag("client") +public class ClientInjectionIT { + + @Test + void clientFilterInjection() { + try (Client client = ClientBuilder.newBuilder().register(ClientInjectFilter.class).build()) { + // We abort the client response so any URI should be acceptable. + final ClientInjectionDescriptor descriptor = client.target("http://localhost:8080") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(ClientInjectionDescriptor.class); + Assertions.assertAll( + () -> Assertions.assertNotNull(descriptor.configuration(), + "Configuration not injected into client filter"), + () -> Assertions.assertEquals(RuntimeType.CLIENT.name(), descriptor.configuration(), + "Configuration runtime type should be CLIENT"), + () -> Assertions.assertNotNull(descriptor.providers(), + "Providers not injected into client filter") + ); + } + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/ContextInjectionDescriptor.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/ContextInjectionDescriptor.java new file mode 100644 index 0000000..3696af7 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/ContextInjectionDescriptor.java
@@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi; + +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Providers; +import jakarta.ws.rs.sse.Sse; + +public record ContextInjectionDescriptor( + String application, + String configuration, + String httpHeaders, + String providers, + String request, + String resourceContext, + String resourceInfo, + String securityContext, + String sse, + String uriInfo) { + + public static ContextInjectionDescriptor of(final Application application, + final Configuration configuration, + final HttpHeaders httpHeaders, + final Providers providers, + final Request request, + final ResourceContext resourceContext, + final ResourceInfo resourceInfo, + final SecurityContext securityContext, + final Sse sse, + final UriInfo uriInfo) { + return new ContextInjectionDescriptor( + application == null ? null : application.getClass().getName(), + configuration == null ? null : String.valueOf(configuration.getRuntimeType()), + httpHeaders == null ? null : httpHeaders.getHeaderString("test-header"), + providers == null ? null : providers.getClass().getName(), + request == null ? null : request.getMethod(), + resourceContext == null ? null : resourceContext.getClass().getName(), + resourceInfo == null ? null : resourceInfo.getResourceMethod().getName(), + securityContext == null ? null : String.valueOf(securityContext.isSecure()), + sse == null ? null : sse.getClass().getName(), + uriInfo == null ? null : uriInfo.getPath()); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextApplication.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextApplication.java new file mode 100644 index 0000000..25fe628 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextApplication.java
@@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi; + +import java.util.Collections; +import java.util.Map; + +import jakarta.inject.Inject; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Providers; + +@ApplicationPath("/") +public class RequiredContextApplication extends Application { + + @Inject + UriInfo info; + + @Inject + Request request; + + @Inject + HttpHeaders headers; + + @Inject + SecurityContext security; + + @Inject + Providers providers; + + @Inject + ResourceContext resources; + + @Override + public Map<String, Object> getProperties() { + return Collections.singletonMap("test.property", "test value"); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextConstructorResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextConstructorResource.java new file mode 100644 index 0000000..e18989f --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextConstructorResource.java
@@ -0,0 +1,91 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Providers; +import jakarta.ws.rs.sse.Sse; + +@Path("/inject-constructor") +public class RequiredContextConstructorResource { + + private final Application application; + private final Configuration configuration; + private final HttpHeaders httpHeaders; + private final Providers providers; + private final Request request; + private final ResourceContext resourceContext; + private final ResourceInfo resourceInfo; + private final SecurityContext securityContext; + private final Sse sse; + private final UriInfo uriInfo; + + RequiredContextConstructorResource() { + this.application = null; + this.configuration = null; + this.httpHeaders = null; + this.providers = null; + this.request = null; + this.resourceContext = null; + this.resourceInfo = null; + this.securityContext = null; + this.sse = null; + this.uriInfo = null; + } + + @Inject + public RequiredContextConstructorResource(final Application application, + final Configuration configuration, + final HttpHeaders httpHeaders, + final Providers providers, + final Request request, + final ResourceContext resourceContext, + final ResourceInfo resourceInfo, + final SecurityContext securityContext, + final Sse sse, + final UriInfo uriInfo) { + this.application = application; + this.configuration = configuration; + this.httpHeaders = httpHeaders; + this.providers = providers; + this.request = request; + this.resourceContext = resourceContext; + this.resourceInfo = resourceInfo; + this.securityContext = securityContext; + this.sse = sse; + this.uriInfo = uriInfo; + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public ContextInjectionDescriptor get() { + return ContextInjectionDescriptor.of(application, configuration, httpHeaders, providers, + request, resourceContext, resourceInfo, securityContext, sse, uriInfo); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextInjectionIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextInjectionIT.java new file mode 100644 index 0000000..596466b --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextInjectionIT.java
@@ -0,0 +1,279 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import ee.jakarta.tck.ws.rs.common.provider.PrintingErrorHandler; +import ee.jakarta.tck.ws.rs.common.provider.StringBean; +import ee.jakarta.tck.ws.rs.common.provider.StringBeanEntityProvider; +import ee.jakarta.tck.ws.rs.common.util.CdiSupport; +import ee.jakarta.tck.ws.rs.lib.util.TestUtil; +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; +import jakarta.ws.rs.sse.SseEventSource; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.container.annotation.ArquillianTest; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +@ArquillianTest +@Tag("cdi") +class RequiredContextInjectionIT { + + @Deployment(testable = false) + public static WebArchive createDeployment() throws IOException { + return ShrinkWrap.create(WebArchive.class, "rest-cdi-injection.war") + .addClasses(RequiredContextApplication.class, + RequiredContextResource.class, + RequiredContextConstructorResource.class, + ContextInjectionDescriptor.class, + StringBeanEntityProviderWithInjectables.class, + StringBeanEntityProvider.class, + PrintingErrorHandler.class, + StringBean.class) + .addAsWebInfResource(CdiSupport.BEANS_XML, "beans.xml"); + } + + private static Client client; + + @ArquillianResource + private URI baseUri; + + @BeforeAll + static void createClient() { + client = ClientBuilder.newBuilder() + .build(); + } + + @AfterAll + static void closeClient() { + if (client != null) { + client.close(); + } + } + + @BeforeEach + void logStartTest(final TestInfo testInfo) { + TestUtil.logMsg("STARTING TEST : " + testInfo.getDisplayName()); + } + + @AfterEach + void logFinishTest(final TestInfo testInfo) { + TestUtil.logMsg("FINISHED TEST : " + testInfo.getDisplayName()); + } + + @Test + void constructorInjection() { + final ContextInjectionDescriptor descriptor = getDescriptor("inject-constructor"); + assertAllInjected(descriptor, "constructor"); + Assertions.assertEquals("GET", descriptor.request(), + () -> "Request method incorrect via constructor injection: %s".formatted(descriptor)); + Assertions.assertEquals(RuntimeType.SERVER.name(), descriptor.configuration(), + () -> "Configuration runtime type incorrect via constructor injection: %s".formatted(descriptor)); + Assertions.assertEquals("test-value", descriptor.httpHeaders(), + () -> "HttpHeaders value incorrect via constructor injection: %s".formatted(descriptor)); + Assertions.assertEquals("get", descriptor.resourceInfo(), + () -> "ResourceInfo method name incorrect via constructor injection: %s".formatted(descriptor)); + } + + @Test + void fieldInjection() { + final ContextInjectionDescriptor descriptor = getDescriptor("inject/field"); + assertAllInjected(descriptor, "field"); + Assertions.assertEquals("GET", descriptor.request(), + () -> "Request method incorrect via field injection: %s".formatted(descriptor)); + Assertions.assertEquals(RuntimeType.SERVER.name(), descriptor.configuration(), + () -> "Configuration runtime type incorrect via field injection: %s".formatted(descriptor)); + Assertions.assertEquals("test-value", descriptor.httpHeaders(), + () -> "HttpHeaders value incorrect via field injection: %s".formatted(descriptor)); + Assertions.assertEquals("field", descriptor.resourceInfo(), + () -> "ResourceInfo method name incorrect via field injection: %s".formatted(descriptor)); + } + + @Test + void methodInjection() { + final ContextInjectionDescriptor descriptor = getDescriptor("inject/method"); + assertAllInjected(descriptor, "method parameter"); + Assertions.assertEquals("GET", descriptor.request(), + () -> "Request method incorrect via method parameter injection: %s".formatted(descriptor)); + Assertions.assertEquals(RuntimeType.SERVER.name(), descriptor.configuration(), + () -> "Configuration runtime type incorrect via method parameter injection: %s".formatted(descriptor)); + Assertions.assertEquals("test-value", descriptor.httpHeaders(), + () -> "HttpHeaders value incorrect via method parameter injection: %s".formatted(descriptor)); + Assertions.assertEquals("method", descriptor.resourceInfo(), + () -> "ResourceInfo method name incorrect via method parameter injection: %s".formatted(descriptor)); + } + + @Test + void providerInjection() { + final Response response = getJson("inject/providers"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), + () -> "Failed to look up provider: " + response.readEntity(String.class)); + final ContextInjectionDescriptor descriptor = response.readEntity(ContextInjectionDescriptor.class); + Assertions.assertAll( + () -> Assertions.assertNotNull(descriptor.application(), () -> "Application not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.configuration(), () -> "Configuration not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.httpHeaders(), () -> "HttpHeaders not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.providers(), () -> "Providers not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.request(), () -> "Request not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.resourceContext(), () -> "ResourceContext not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.securityContext(), () -> "SecurityContext not injected into provider: %s".formatted(descriptor)), + () -> Assertions.assertNotNull(descriptor.uriInfo(), () -> "UriInfo not injected into provider: %s".formatted(descriptor)) + ); + } + + @Test + void application() { + final Response response = get("inject/application/test.property"); + final var found = response.readEntity(String.class); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), found); + Assertions.assertEquals("test value", found); + } + + @Test + void configuration() { + final Response response = get("inject/configuration"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(RuntimeType.SERVER.name(), response.readEntity(String.class)); + } + + @Test + void httpHeader() { + final Response response = get("inject/httpHeaders/test-header"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals("test-value", response.readEntity(String.class)); + } + + @Test + void request() { + final Response response = get("inject/request"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals("GET", response.readEntity(String.class)); + } + + @Test + void resourceContext() { + final Response response = get("inject/resourceContext"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertTrue(response.readEntity(String.class) + .startsWith(RequiredContextResource.class.getCanonicalName())); + } + + @Test + void resourceInfo() { + final Response response = get("inject/resourceInfo"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals("resourceInfo", response.readEntity(String.class)); + } + + @Test + void securityContext() { + final Response response = get("inject/securityContext"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals("false", response.readEntity(String.class)); + } + + @Test + void uriInfo() { + final Response response = get("inject/uriInfo"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals("/inject/uriInfo", response.readEntity(String.class)); + } + + @Test + void sse() throws Exception { + final WebTarget target = client.target(generateUri("inject/sse")); + final CompletableFuture<String> cf = new CompletableFuture<>(); + try (SseEventSource source = SseEventSource.target(target).build()) { + source.register(event -> { + try { + cf.complete(event.readData()); + } catch (Throwable t) { + cf.completeExceptionally(t); + } + }); + source.open(); + Assertions.assertEquals("test", cf.get(5, TimeUnit.SECONDS)); + } + } + + @Test + void asyncResponseByTypeAlone() { + final Response response = get("inject/async"); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), + () -> "AsyncResponse by type alone failed: %s".formatted(response.readEntity(String.class))); + Assertions.assertEquals("async-test", response.readEntity(String.class)); + } + + private static void assertAllInjected(final ContextInjectionDescriptor descriptor, final String injectionType) { + Assertions.assertAll( + () -> Assertions.assertNotNull(descriptor.application(), () -> "Application not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.configuration(), () -> "Configuration not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.httpHeaders(), () -> "HttpHeaders not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.providers(), () -> "Providers not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.request(), () -> "Request not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.resourceContext(), () -> "ResourceContext not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.resourceInfo(), () -> "ResourceInfo not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.securityContext(), () -> "SecurityContext not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.sse(), () -> "Sse not injected via %s: %s".formatted(injectionType, descriptor)), + () -> Assertions.assertNotNull(descriptor.uriInfo(), () -> "UriInfo not injected via %s: %s".formatted(injectionType, descriptor)) + ); + } + + private ContextInjectionDescriptor getDescriptor(final String path) { + final Response response = getJson(path); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), + () -> "Unexpected status for " + path + ": " + response.readEntity(String.class)); + return response.readEntity(ContextInjectionDescriptor.class); + } + + private Response get(final String path) { + return client.target(generateUri(path)) + .request() + .header("test-header", "test-value") + .get(); + } + + private Response getJson(final String path) { + return client.target(generateUri(path)) + .request(MediaType.APPLICATION_JSON_TYPE) + .header("test-header", "test-value") + .get(); + } + + private URI generateUri(final String path) { + return UriBuilder.fromUri(baseUri).path(path).build(); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextResource.java new file mode 100644 index 0000000..ec12e62 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/RequiredContextResource.java
@@ -0,0 +1,227 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; + +import ee.jakarta.tck.ws.rs.common.provider.StringBean; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Providers; +import jakarta.ws.rs.sse.Sse; +import jakarta.ws.rs.sse.SseEventSink; + +@Path("/inject") +public class RequiredContextResource { + + @Inject + Application application; + + @Inject + UriInfo uriInfo; + + @Inject + Request request; + + @Inject + HttpHeaders httpHeaders; + + @Inject + SecurityContext securityContext; + + @Inject + Providers providers; + + @Inject + ResourceContext resourceContext; + + @Inject + Configuration configuration; + + @Inject + ResourceInfo resourceInfo; + + @Inject + Sse sse; + + @GET + @Path("field") + @Produces(MediaType.APPLICATION_JSON) + public ContextInjectionDescriptor field() { + return ContextInjectionDescriptor.of(application, configuration, httpHeaders, providers, + request, resourceContext, resourceInfo, securityContext, sse, uriInfo); + } + + @GET + @Path("method") + @Produces(MediaType.APPLICATION_JSON) + public ContextInjectionDescriptor method(final Application application, final UriInfo uriInfo, + final Request request, final HttpHeaders httpHeaders, + final SecurityContext securityContext, final Providers providers, + final ResourceContext resourceContext, final Configuration configuration, + final ResourceInfo resourceInfo, final Sse sse) { + return ContextInjectionDescriptor.of(application, configuration, httpHeaders, providers, + request, resourceContext, resourceInfo, securityContext, sse, uriInfo); + } + + @GET + @Path("/application/{propertyName}") + @Produces(MediaType.TEXT_PLAIN) + public Response application(@PathParam("propertyName") final String propertyName) { + final var properties = application.getProperties(); + if (properties.containsKey(propertyName)) { + return Response.ok(properties.get(propertyName)).build(); + } + return Response.status(Response.Status.BAD_REQUEST) + .entity("Failed to find property %s in %s".formatted(propertyName, properties)) + .build(); + } + + @GET + @Path("/configuration") + @Produces(MediaType.TEXT_PLAIN) + public Response configuration() { + if (configuration == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The configuration field is null.").build(); + } + return Response.ok(configuration.getRuntimeType().toString()).build(); + } + + @GET + @Path("/httpHeaders/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response httpHeaders(@PathParam("name") final String name) { + if (httpHeaders == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The httpHeaders field is null.").build(); + } + return Response.ok(httpHeaders.getHeaderString(name)).build(); + } + + @GET + @Path("/providers") + @Produces(MediaType.APPLICATION_JSON) + public Response providers() { + final var mbw = providers.getMessageBodyWriter(StringBean.class, null, null, MediaType.WILDCARD_TYPE); + if (!(mbw instanceof StringBeanEntityProviderWithInjectables instance)) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Failed to find MessageBodyWriter for StringBean. This should be %s, but was %s" + .formatted(StringBeanEntityProviderWithInjectables.class, mbw)) + .build(); + } + return Response.ok(instance.describe()).build(); + } + + @GET + @Path("/request") + @Produces(MediaType.TEXT_PLAIN) + public Response request() { + if (request == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The request field is null.").build(); + } + return Response.ok(request.getMethod()).build(); + } + + @GET + @Path("resourceContext") + @Produces(MediaType.TEXT_PLAIN) + public Response resourceContext() { + if (resourceContext == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The resourceContext field is null.").build(); + } + return Response.ok(resourceContext.getResource(getClass()).getClass().getCanonicalName()).build(); + } + + @GET + @Path("resourceInfo") + @Produces(MediaType.TEXT_PLAIN) + public Response resourceInfo() { + if (resourceInfo == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The resourceInfo field is null.").build(); + } + return Response.ok(resourceInfo.getResourceMethod().getName()).build(); + } + + @GET + @Path("/securityContext") + @Produces(MediaType.TEXT_PLAIN) + public Response securityContext() { + if (securityContext == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The securityContext field is null.").build(); + } + return Response.ok(securityContext.isSecure()).build(); + } + + @GET + @Path("/sse") + @Produces(MediaType.SERVER_SENT_EVENTS) + public CompletionStage<?> sse(final SseEventSink eventSink) throws IOException { + if (eventSink == null) { + return CompletableFuture.failedFuture(new WebApplicationException("The eventSink parameter is null")); + } + if (sse == null) { + return CompletableFuture.failedFuture(new WebApplicationException("The sse field is null")); + } + return eventSink.send(sse.newEvent("test")) + .whenComplete((BiConsumer<Object, Throwable>) (unused, throwable) -> { + try { + eventSink.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + @GET + @Path("/async") + @Produces(MediaType.TEXT_PLAIN) + public void async(final AsyncResponse asyncResponse) { + if (asyncResponse == null) { + throw new WebApplicationException("The asyncResponse parameter is null"); + } + asyncResponse.resume("async-test"); + } + + @GET + @Path("/uriInfo") + @Produces(MediaType.TEXT_PLAIN) + public Response uriInfo() { + if (uriInfo == null) { + return Response.status(Response.Status.BAD_REQUEST).entity("The uriInfo field is null.").build(); + } + return Response.ok(uriInfo.getPath()).build(); + } + +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/StringBeanEntityProviderWithInjectables.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/StringBeanEntityProviderWithInjectables.java new file mode 100644 index 0000000..7ab9de0 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/StringBeanEntityProviderWithInjectables.java
@@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi; + +import ee.jakarta.tck.ws.rs.common.provider.StringBeanEntityProvider; +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ResourceContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.Providers; + +@Provider +public class StringBeanEntityProviderWithInjectables extends StringBeanEntityProvider { + + @Inject + Application application; + + @Inject + UriInfo uriInfo; + + @Inject + Request request; + + @Inject + HttpHeaders httpHeaders; + + @Inject + SecurityContext securityContext; + + @Inject + Providers providers; + + @Inject + ResourceContext resourceContext; + + @Inject + Configuration configuration; + + public ContextInjectionDescriptor describe() { + return ContextInjectionDescriptor.of(application, configuration, httpHeaders, providers, + request, resourceContext, null, securityContext, null, uriInfo); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/AbstractParamIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/AbstractParamIT.java new file mode 100644 index 0000000..c543299 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/AbstractParamIT.java
@@ -0,0 +1,163 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.net.URI; + +import ee.jakarta.tck.ws.rs.common.util.CdiSupport; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Form; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriBuilder; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@Tag("cdi") +abstract class AbstractParamIT { + + private final String contextPath; + + @ArquillianResource + protected URI baseUri; + + AbstractParamIT(final String contextPath) { + this.contextPath = contextPath; + } + + static WebArchive defaultDeployment(final Class<? extends AbstractParamIT> testClass) { + return ShrinkWrap.create(WebArchive.class, testClass.getSimpleName() + ".war") + .addClasses(ParamApplication.class, ParamDescriptor.class, ParamResource.class) + .addAsWebInfResource(CdiSupport.BEANS_XML, "beans.xml"); + } + + @Test + void constructorParameters() { + final ParamDescriptor param = invokeRequest("constructor"); + // Check the parameters + Assertions.assertEquals("cookieParamValue", param.getCookieParam(), + () -> "@CookieParam parameter was not set on the constructor: %s".formatted(param)); + Assertions.assertEquals("formParamValue", param.getFormParam(), + () -> "@FormParam parameter was not set on the constructor: %s".formatted(param)); + Assertions.assertEquals("headerParamValue", param.getHeaderParam(), + () -> "@HeaderParam parameter was not set on the constructor: %s".formatted(param)); + Assertions.assertEquals(10, param.getMatrixParam(), + () -> "@MatrixParam parameter was not set on the constructor: %s".formatted(param)); + Assertions.assertEquals("constructorParameters", param.getPathParam(), + () -> "@PathParam parameter was not set on the constructor: %s".formatted(param)); + Assertions.assertEquals(100, param.getQueryParam(), + () -> "@QueryParam parameter was not set on the constructor: %s".formatted(param)); + } + + @Test + void fieldParameters() { + final ParamDescriptor param = invokeRequest("field/fieldParamValue"); + // Check the parameters + Assertions.assertEquals("fieldCookieParamValue", param.getCookieParam(), + () -> "@CookieParam parameter was not set on the field: %s".formatted(param)); + Assertions.assertEquals("fieldFormParamValue", param.getFormParam(), + () -> "@FormParam parameter was not set on the field: %s".formatted(param)); + Assertions.assertEquals("fieldHeaderParamValue", param.getHeaderParam(), + () -> "@HeaderParam parameter was not set on the field: %s".formatted(param)); + Assertions.assertEquals(20, param.getMatrixParam(), + () -> "@MatrixParam parameter was not set on the field: %s".formatted(param)); + Assertions.assertEquals("fieldParamValue", param.getPathParam(), + () -> "@PathParam parameter was not set on the field: %s".formatted(param)); + Assertions.assertEquals(200, param.getQueryParam(), + () -> "@QueryParam parameter was not set on the field: %s".formatted(param)); + } + + @Test + void methodParameters() { + final ParamDescriptor param = invokeRequest("method/methodParamValue"); + // Check the parameters + Assertions.assertEquals("methodCookieParamValue", param.getCookieParam(), + () -> "@CookieParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals("methodFormParamValue", param.getFormParam(), + () -> "@FormParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals("methodHeaderParamValue", param.getHeaderParam(), + () -> "@HeaderParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals(30, param.getMatrixParam(), + () -> "@MatrixParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals("methodParamValue", param.getPathParam(), + () -> "@PathParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals(300, param.getQueryParam(), + () -> "@QueryParam parameter was not set on the method: %s".formatted(param)); + } + + @Test + void methodParametersAnnotated() { + final ParamDescriptor param = invokeRequest("method-param/methodParamValuePa"); + // Check the parameters + Assertions.assertEquals("methodCookieParamValuePa", param.getCookieParam(), + () -> "@CookieParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals("methodFormParamValuePa", param.getFormParam(), + () -> "@FormParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals("methodHeaderParamValuePa", param.getHeaderParam(), + () -> "@HeaderParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals(40, param.getMatrixParam(), + () -> "@MatrixParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals("methodParamValuePa", param.getPathParam(), + () -> "@PathParam parameter was not set on the method: %s".formatted(param)); + Assertions.assertEquals(400, param.getQueryParam(), + () -> "@QueryParam parameter was not set on the method: %s".formatted(param)); + } + + private ParamDescriptor invokeRequest(final String path) { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path(contextPath + "/constructorParameters/" + path) + .matrixParam("matrixParam", 10) + .matrixParam("fieldMatrixParam", 20) + .matrixParam("methodMatrixParam", 30) + .matrixParam("methodMatrixParamPa", 40) + .queryParam("queryParam", 100) + .queryParam("fieldQueryParam", 200) + .queryParam("methodQueryParam", 300) + .queryParam("methodQueryParamPa", 400); + final Form form = new Form() + .param("formParam", "formParamValue") + .param("fieldFormParam", "fieldFormParamValue") + .param("methodFormParam", "methodFormParamValue") + .param("methodFormParamPa", "methodFormParamValuePa"); + final WebTarget target = client.target(uriBuilder); + final ParamDescriptor param = target.request(MediaType.APPLICATION_JSON_TYPE) + .cookie("cookieParam", "cookieParamValue") + .cookie("fieldCookieParam", "fieldCookieParamValue") + .cookie("methodCookieParam", "methodCookieParamValue") + .cookie("methodCookieParamPa", "methodCookieParamValuePa") + .header("headerParam", "headerParamValue") + .header("fieldHeaderParam", "fieldHeaderParamValue") + .header("methodHeaderParam", "methodHeaderParamValue") + .header("methodHeaderParamPa", "methodHeaderParamValuePa") + .post(Entity.form(form), ParamDescriptor.class); + Assertions.assertNotNull(param, () -> "Failed to find the parameters with URI %s".formatted(uriBuilder.build())); + return param; + } + } + +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiBeanParamIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiBeanParamIT.java new file mode 100644 index 0000000..3998cac --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiBeanParamIT.java
@@ -0,0 +1,92 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.net.URI; + +import ee.jakarta.tck.ws.rs.common.util.CdiSupport; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriBuilder; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.container.annotation.ArquillianTest; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests that a {@link jakarta.ws.rs.BeanParam @BeanParam} works with CDI. + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@ArquillianTest +@Tag("cdi") +class CdiBeanParamIT { + + @ArquillianResource + private URI baseUri; + + @Deployment(testable = false) + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, CdiBeanParamIT.class.getSimpleName() + ".war") + .addClasses(ParamApplication.class, CdiBeanParamResource.class, SearchParams.class) + .addAsWebInfResource(CdiSupport.BEANS_XML, "beans.xml"); + } + + @Test + void beanParam() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("bean-param") + .queryParam("q", "testValue") + .queryParam("limit", 25); + final SearchParams result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .header("Accept-Language", "en-US") + .get(SearchParams.class); + Assertions.assertNotNull(result); + Assertions.assertEquals("testValue", result.getQuery(), + () -> "Expected a result of 'testValue' in the query parameter: %s".formatted(result)); + Assertions.assertEquals(25, result.getLimit(), + () -> "Expected a result of '25' in the limit parameter: %s".formatted(result)); + Assertions.assertEquals("en-US", result.getLanguage(), + () -> "Expected a result of 'en-US' in the Accepted-Language parameter: %s".formatted(result)); + } + } + + @Test + void beanParamDefaults() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("bean-param") + .queryParam("q", "test"); + final SearchParams result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchParams.class); + Assertions.assertNotNull(result); + Assertions.assertEquals("test", result.getQuery(), + () -> "Expected a result of 'test' in the query parameter: %s".formatted(result)); + Assertions.assertEquals(10, result.getLimit(), + () -> "Expected a result of '10', @DefaultValue, in the limit parameter: %s".formatted(result)); + Assertions.assertNull(result.getLanguage(), () -> "Accept-Language header should be null: %s".formatted(result)); + } + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiBeanParamResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiBeanParamResource.java new file mode 100644 index 0000000..a4fe70f --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiBeanParamResource.java
@@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/bean-param") +@RequestScoped +public class CdiBeanParamResource { + + @Inject + @BeanParam + private SearchParams params; + + CdiBeanParamResource() { + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public SearchParams get() { + return params; + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamEncodedIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamEncodedIT.java new file mode 100644 index 0000000..21af788 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamEncodedIT.java
@@ -0,0 +1,92 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.net.URI; + +import ee.jakarta.tck.ws.rs.common.util.CdiSupport; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriBuilder; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.container.annotation.ArquillianTest; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests that CDI injected parameters work with {@link jakarta.ws.rs.Encoded @Encoded} + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@ArquillianTest +@Tag("cdi") +class CdiParamEncodedIT { + + @ArquillianResource + private URI baseUri; + + @Deployment(testable = false) + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, CdiParamEncodedIT.class.getSimpleName() + ".war") + .addClasses(ParamApplication.class, CdiParamEncodedResource.class, + CdiParamNotEncodedResource.class, EncodedDescriptor.class) + .addAsWebInfResource(CdiSupport.BEANS_XML, "beans.xml"); + } + + @Test + void classLevelEncoded() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("encoded/hello%20world") + .queryParam("q", "hello%20world"); + final EncodedDescriptor result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(EncodedDescriptor.class); + Assertions.assertNotNull(result); + Assertions.assertTrue(result.getQueryValue().contains("%20"), + () -> "Class-level @Encoded query should retain %%20, got: %s".formatted(result.getQueryValue())); + Assertions.assertTrue(result.getPathValue().contains("%20"), + () -> "Class-level @Encoded path should retain %%20, got: %s".formatted(result.getPathValue())); + } + } + + @Test + void fieldLevelEncoded() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("not-encoded/hello%20world") + .queryParam("q", "hello%20world") + .queryParam("q2", "hello%20world"); + final EncodedDescriptor result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(EncodedDescriptor.class); + Assertions.assertNotNull(result); + Assertions.assertTrue(result.getQueryValue().contains("%20"), + () -> "Field-level @Encoded query should retain %%20, got: %s".formatted(result.getQueryValue())); + Assertions.assertEquals("hello world", result.getDecodedQuery(), + () -> "non-encoded query should decode %%20 to space: %s".formatted(result)); + Assertions.assertEquals("hello world", result.getPathValue(), + () -> "non-encoded path should decode %%20 to space: %s".formatted(result)); + } + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamEncodedResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamEncodedResource.java new file mode 100644 index 0000000..64d1102 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamEncodedResource.java
@@ -0,0 +1,52 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Encoded; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; + +@Path("/encoded/{p}") +@RequestScoped +@Encoded +public class CdiParamEncodedResource { + + @Inject + @QueryParam("q") + private String queryValue; + + @Inject + @PathParam("p") + private String pathValue; + + CdiParamEncodedResource() { + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public EncodedDescriptor get() { + return EncodedDescriptor.of() + .setQueryValue(queryValue) + .setPathValue(pathValue); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamIT.java new file mode 100644 index 0000000..1728b40 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamIT.java
@@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.container.annotation.ArquillianTest; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +/** + * Test that parameter extraction annotation annotated fields, constructor parameters and method parameters are injected. + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@ArquillianTest +class CdiParamIT extends AbstractParamIT { + + CdiParamIT() { + super("params"); + } + + @Deployment(testable = false) + public static WebArchive createDeployment() { + return defaultDeployment(CdiParamIT.class).addClass(CdiParamResource.class); + } + +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamMixedIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamMixedIT.java new file mode 100644 index 0000000..bc9ca14 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamMixedIT.java
@@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.net.URI; + +import ee.jakarta.tck.ws.rs.common.util.CdiSupport; +import jakarta.json.JsonObject; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriBuilder; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.container.annotation.ArquillianTest; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests that CDI injection works on a constructor with both parameter extraction annotations and standard CDI beans. + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@ArquillianTest +@Tag("cdi") +class CdiParamMixedIT { + + @ArquillianResource + private URI baseUri; + + @Deployment(testable = false) + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, CdiParamMixedIT.class.getSimpleName() + ".war") + .addClasses(ParamApplication.class, CdiParamMixedResource.class) + .addAsWebInfResource(CdiSupport.BEANS_XML, "beans.xml"); + } + + @Test + void mixedConstructor() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("mixed") + .queryParam("q", "testValue"); + final JsonObject result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .header("Custom", "custom-value") + .get(JsonObject.class); + Assertions.assertNotNull(result); + Assertions.assertEquals("testValue", result.getString("query"), + () -> "@QueryParam should be injected in mixed constructor: %s".formatted(result)); + Assertions.assertFalse(result.getString("path").isEmpty(), + () -> "UriInfo should be injected in mixed constructor: %s".formatted(result)); + Assertions.assertEquals("custom-value", result.getString("customHeader"), + () -> "@HeaderParam should be injected in mixed constructor: %s".formatted(result)); + } + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamMixedResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamMixedResource.java new file mode 100644 index 0000000..5aa85bd --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamMixedResource.java
@@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriInfo; + +@Path("/mixed") +@RequestScoped +public class CdiParamMixedResource { + + private final String query; + private final UriInfo uriInfo; + private final String customHeader; + + CdiParamMixedResource() { + this.query = null; + this.uriInfo = null; + this.customHeader = null; + } + + @Inject + public CdiParamMixedResource(@QueryParam("q") final String query, + final UriInfo uriInfo, + @HeaderParam("Custom") final String customHeader) { + this.query = query; + this.uriInfo = uriInfo; + this.customHeader = customHeader; + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public JsonObject get() { + return Json.createObjectBuilder() + .add("query", query != null ? query : "") + .add("path", uriInfo != null ? uriInfo.getPath() : "") + .add("customHeader", customHeader != null ? customHeader : "") + .build(); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamNotEncodedResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamNotEncodedResource.java new file mode 100644 index 0000000..49262b0 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamNotEncodedResource.java
@@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Encoded; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; + +@Path("/not-encoded/{p}") +@RequestScoped +public class CdiParamNotEncodedResource { + + @Inject + @Encoded + @QueryParam("q") + private String encodedQuery; + + @Inject + @QueryParam("q2") + private String decodedQuery; + + @Inject + @PathParam("p") + private String pathValue; + + CdiParamNotEncodedResource() { + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public EncodedDescriptor get() { + return EncodedDescriptor.of() + .setQueryValue(encodedQuery) + .setDecodedQuery(decodedQuery) + .setPathValue(pathValue); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamResource.java new file mode 100644 index 0000000..f3c1bd9 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamResource.java
@@ -0,0 +1,211 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.io.InputStream; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.CookieParam; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.MatrixParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; + +/** + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@Path("/params/{pathParam}") +@RequestScoped +public class CdiParamResource implements ParamResource { + private final String cookieParam; + private final String formParam; + private final String headerParam; + private final long matrixParam; + private final String pathParam; + private final int queryParam; + + @Inject + @CookieParam("fieldCookieParam") + private String fieldCookieParam; + @Inject + @FormParam("fieldFormParam") + private String fieldFormParam; + @Inject + @HeaderParam("fieldHeaderParam") + private String fieldHeaderParam; + @Inject + @MatrixParam("fieldMatrixParam") + private int fieldMatrixParam; + @Inject + @PathParam("fieldPathParam") + private String fieldPathParam; + @Inject + @QueryParam("fieldQueryParam") + private int fieldQueryParam; + + private String methodCookieParam; + private String methodFormParam; + private String methodHeaderParam; + private int methodMatrixParam; + private String methodPathParam; + private int methodQueryParam; + + private String methodCookieParamPa; + private String methodFormParamPa; + private String methodHeaderParamPa; + private int methodMatrixParamPa; + private String methodPathParamPa; + private int methodQueryParamPa; + + CdiParamResource() { + cookieParam = null; + formParam = null; + headerParam = null; + matrixParam = -1; + pathParam = null; + queryParam = -1; + } + + @Inject + public CdiParamResource(@CookieParam("cookieParam") final String cookieParam, + @FormParam("formParam") final String formParam, + @HeaderParam("headerParam") final String headerParam, + @MatrixParam("matrixParam") final long matrixParam, + @PathParam("pathParam") final String pathParam, + @QueryParam("queryParam") final int queryParam) { + this.cookieParam = cookieParam; + this.formParam = formParam; + this.headerParam = headerParam; + this.matrixParam = matrixParam; + this.pathParam = pathParam; + this.queryParam = queryParam; + } + + @Override + public ParamDescriptor constructor(final InputStream body) { + return ParamDescriptor.of() + .setCookieParam(cookieParam) + .setFormParam(formParam) + .setHeaderParam(headerParam) + .setMatrixParam(matrixParam) + .setPathParam(pathParam) + .setQueryParam(queryParam); + } + + @Override + public ParamDescriptor field(final InputStream body) { + return ParamDescriptor.of() + .setCookieParam(fieldCookieParam) + .setFormParam(fieldFormParam) + .setHeaderParam(fieldHeaderParam) + .setMatrixParam(fieldMatrixParam) + .setPathParam(fieldPathParam) + .setQueryParam(fieldQueryParam); + } + + @Override + public ParamDescriptor method(final InputStream body) { + return ParamDescriptor.of() + .setCookieParam(methodCookieParam) + .setFormParam(methodFormParam) + .setHeaderParam(methodHeaderParam) + .setMatrixParam(methodMatrixParam) + .setPathParam(methodPathParam) + .setQueryParam(methodQueryParam); + } + + @Override + public ParamDescriptor methodParamsAnnotated(final InputStream body) { + return ParamDescriptor.of() + .setCookieParam(methodCookieParamPa) + .setFormParam(methodFormParamPa) + .setHeaderParam(methodHeaderParamPa) + .setMatrixParam(methodMatrixParamPa) + .setPathParam(methodPathParamPa) + .setQueryParam(methodQueryParamPa); + } + + @Inject + @CookieParam("methodCookieParam") + public void setCookieParam(final String methodCookieParam) { + this.methodCookieParam = methodCookieParam; + } + + @Inject + @FormParam("methodFormParam") + public void setFormParam(final String methodFormParam) { + this.methodFormParam = methodFormParam; + } + + @Inject + @HeaderParam("methodHeaderParam") + public void setHeaderParam(final String methodHeaderParam) { + this.methodHeaderParam = methodHeaderParam; + } + + @Inject + @MatrixParam("methodMatrixParam") + public void setMatrixParam(final int methodMatrixParam) { + this.methodMatrixParam = methodMatrixParam; + } + + @Inject + @PathParam("methodPathParam") + public void setPathParam(final String methodPathParam) { + this.methodPathParam = methodPathParam; + } + + @Inject + @QueryParam("methodQueryParam") + public void setQueryParam(final int methodQueryParam) { + this.methodQueryParam = methodQueryParam; + } + + @Inject + public void setCookieParamPa(@CookieParam("methodCookieParamPa") final String cookieParam) { + this.methodCookieParamPa = cookieParam; + } + + @Inject + public void setFormParamPa(@FormParam("methodFormParamPa") final String formParam) { + this.methodFormParamPa = formParam; + } + + @Inject + public void setHeaderParamPa(@HeaderParam("methodHeaderParamPa") final String headerParam) { + this.methodHeaderParamPa = headerParam; + } + + @Inject + public void setMatrixParamPa(@MatrixParam("methodMatrixParamPa") final int matrixParam) { + this.methodMatrixParamPa = matrixParam; + } + + @Inject + public void setPathParamPa(@PathParam("methodPathParamPa") final String pathParam) { + this.methodPathParamPa = pathParam; + } + + @Inject + public void setQueryParamPa(@QueryParam("methodQueryParamPa") final int queryParam) { + this.methodQueryParamPa = queryParam; + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamTypesIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamTypesIT.java new file mode 100644 index 0000000..1fa5961 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamTypesIT.java
@@ -0,0 +1,157 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.net.URI; +import java.util.List; +import java.util.Set; + +import ee.jakarta.tck.ws.rs.common.util.CdiSupport; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriBuilder; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.container.annotation.ArquillianTest; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests various parameter extraction annotation injections work with various required types. + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@ArquillianTest +@Tag("cdi") +class CdiParamTypesIT { + + @ArquillianResource + private URI baseUri; + + @Deployment(testable = false) + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, CdiParamTypesIT.class.getSimpleName() + ".war") + .addClasses(ParamApplication.class, CdiParamTypesResource.class, + TypesDescriptor.class, ParamEnum.class) + .addAsWebInfResource(CdiSupport.BEANS_XML, "beans.xml"); + } + + @Test + void allTypes() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("types/path1/path2") + .queryParam("list", "a", "b", "c") + .queryParam("set", "x", "y", "z") + .queryParam("sortedSet", "c", "a", "b") + .queryParam("array", "one", "two") + .queryParam("wrapper", 99) + .queryParam("bool", true) + .queryParam("enum", "VALUE_ONE") + .queryParam("default", "explicit") + .queryParam("defaultInt", 7); + final TypesDescriptor result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(TypesDescriptor.class); + Assertions.assertNotNull(result); + + // Collection types + Assertions.assertNotNull(result.getListParam(), () -> "List should not be null: %s".formatted(result)); + Assertions.assertEquals(3, result.getListParam().size(), + () -> "Expected list size of 3: %s".formatted(result)); + Assertions.assertTrue(result.getListParam().containsAll(List.of("a", "b", "c")), + () -> "Expected list to contain [a, b, c]: %s".formatted(result)); + + Assertions.assertNotNull(result.getSetParam(), + () -> "Set should not be null: %s".formatted(result)); + Assertions.assertEquals(3, result.getSetParam().size(), + () -> "Expected set size of 3: %s".formatted(result)); + Assertions.assertTrue(result.getSetParam().containsAll(Set.of("x", "y", "z")), + () -> "Expected set to contain [x, y, z]: %s".formatted(result)); + + Assertions.assertNotNull(result.getSortedSetParam(), + () -> "SortedSet should not be null: %s".formatted(result)); + Assertions.assertEquals(3, result.getSortedSetParam().size(), + () -> "Expected sortedSet size of 3: %s".formatted(result)); + Assertions.assertEquals("a", result.getSortedSetParam().first(), + () -> "SortedSet should be sorted, expected 'a' first: %s".formatted(result)); + + // Array + Assertions.assertNotNull(result.getArrayParam(), + () -> "Array should not be null: %s".formatted(result)); + Assertions.assertArrayEquals(new String[] {"one", "two"}, result.getArrayParam(), + () -> "Expected array [one, two]: %s".formatted(result)); + + // Wrapper and primitive types + Assertions.assertEquals(99, result.getWrapperParam(), + () -> "Expected wrapper value of 99: %s".formatted(result)); + Assertions.assertEquals(true, result.getBooleanParam(), + () -> "Expected boolean value of true: %s".formatted(result)); + + // Enum + Assertions.assertEquals(ParamEnum.VALUE_ONE, result.getEnumParam(), + () -> "Expected enum VALUE_ONE: %s".formatted(result)); + + // Explicit values override @DefaultValue + Assertions.assertEquals("explicit", result.getDefaultParam(), + () -> "Expected default string to be 'explicit': %s".formatted(result)); + Assertions.assertEquals(7, result.getDefaultIntParam(), + () -> "Expected default int to be 7: %s".formatted(result)); + + // PathSegment + final List<String> listPaths = result.getListPaths(); + Assertions.assertEquals(2, listPaths.size(), () -> "Expected two PathSegments in %s".formatted(result)); + Assertions.assertEquals("path1", listPaths.get(0), + () -> "Expected the first PathSegment to be path1 in %s".formatted(result)); + Assertions.assertEquals("path2", listPaths.get(1), + () -> "Expected the first PathSegment to be path2 in %s".formatted(result)); + Assertions.assertEquals("path2", result.getSinglePath(), + () -> "Expected the single PathSegment to be path2 in %s".formatted(result)); + } + } + + @Test + void defaultValues() { + try (Client client = ClientBuilder.newClient()) { + final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri) + .path("types/ignored"); + final TypesDescriptor result = client.target(uriBuilder) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(TypesDescriptor.class); + Assertions.assertNotNull(result); + + // @DefaultValue should apply when params are absent + Assertions.assertEquals("fallback", result.getDefaultParam(), + () -> "Expected default string 'fallback' when param is absent: %s".formatted(result)); + Assertions.assertEquals(42, result.getDefaultIntParam(), + () -> "Expected default int 42 when param is absent: %s".formatted(result)); + + // Absent optional params without @DefaultValue + Assertions.assertNull(result.getWrapperParam(), + () -> "Absent wrapper should be null: %s".formatted(result)); + Assertions.assertNull(result.getBooleanParam(), + () -> "Absent boolean should be null: %s".formatted(result)); + Assertions.assertNull(result.getEnumParam(), + () -> "Absent enum should be null: %s".formatted(result)); + } + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamTypesResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamTypesResource.java new file mode 100644 index 0000000..7f022f2 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/CdiParamTypesResource.java
@@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.PathSegment; + +@Path("/types/{paths:.+}") +@RequestScoped +public class CdiParamTypesResource { + + @Inject + @QueryParam("list") + private List<String> listParam; + + @Inject + @QueryParam("set") + private Set<String> setParam; + + @Inject + @QueryParam("sortedSet") + private SortedSet<String> sortedSetParam; + + @Inject + @QueryParam("array") + private String[] arrayParam; + + @Inject + @QueryParam("wrapper") + private Integer wrapperParam; + + @Inject + @QueryParam("bool") + private Boolean booleanParam; + + @Inject + @QueryParam("enum") + private ParamEnum enumParam; + + @Inject + @QueryParam("default") + @DefaultValue("fallback") + private String defaultParam; + + @Inject + @QueryParam("defaultInt") + @DefaultValue("42") + private int defaultIntParam; + + @Inject + @PathParam("paths") + private List<PathSegment> listPaths; + + @Inject + @PathParam("paths") + private PathSegment singlePath; + + CdiParamTypesResource() { + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public TypesDescriptor get() { + return TypesDescriptor.of() + .setListParam(listParam) + .setSetParam(setParam) + .setSortedSetParam(sortedSetParam) + .setArrayParam(arrayParam) + .setWrapperParam(wrapperParam) + .setBooleanParam(booleanParam) + .setEnumParam(enumParam) + .setDefaultParam(defaultParam) + .setDefaultIntParam(defaultIntParam) + .setListPaths(listPaths.stream().map(PathSegment::getPath).toList()) + .setSinglePath(singlePath.getPath()); + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/EncodedDescriptor.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/EncodedDescriptor.java new file mode 100644 index 0000000..b24e122 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/EncodedDescriptor.java
@@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +public class EncodedDescriptor { + private String queryValue; + private String pathValue; + private String decodedQuery; + + public static EncodedDescriptor of() { + return new EncodedDescriptor(); + } + + public String getQueryValue() { + return queryValue; + } + + public EncodedDescriptor setQueryValue(final String queryValue) { + this.queryValue = queryValue; + return this; + } + + public String getPathValue() { + return pathValue; + } + + public EncodedDescriptor setPathValue(final String pathValue) { + this.pathValue = pathValue; + return this; + } + + public String getDecodedQuery() { + return decodedQuery; + } + + public EncodedDescriptor setDecodedQuery(final String decodedQuery) { + this.decodedQuery = decodedQuery; + return this; + } + + @Override + public String toString() { + return "EncodedDescriptor{" + "queryValue='" + queryValue + '\'' + + ", pathValue='" + pathValue + '\'' + + ", decodedQuery='" + decodedQuery + '\'' + + '}'; + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamApplication.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamApplication.java new file mode 100644 index 0000000..e6891d0 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamApplication.java
@@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + +/** + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@ApplicationPath("/") +public class ParamApplication extends Application { + +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamDescriptor.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamDescriptor.java new file mode 100644 index 0000000..2b88f12 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamDescriptor.java
@@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +/** + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +public class ParamDescriptor { + private String cookieParam; + private String formParam; + private String headerParam; + private long matrixParam; + private String pathParam; + private int queryParam; + + public static ParamDescriptor of() { + return new ParamDescriptor(); + } + + public String getCookieParam() { + return cookieParam; + } + + public ParamDescriptor setCookieParam(final String cookieParam) { + this.cookieParam = cookieParam; + return this; + } + + public String getFormParam() { + return formParam; + } + + public ParamDescriptor setFormParam(final String formParam) { + this.formParam = formParam; + return this; + } + + public String getHeaderParam() { + return headerParam; + } + + public ParamDescriptor setHeaderParam(final String headerParam) { + this.headerParam = headerParam; + return this; + } + + public long getMatrixParam() { + return matrixParam; + } + + public ParamDescriptor setMatrixParam(final long matrixParam) { + this.matrixParam = matrixParam; + return this; + } + + public String getPathParam() { + return pathParam; + } + + public ParamDescriptor setPathParam(final String pathParam) { + this.pathParam = pathParam; + return this; + } + + public int getQueryParam() { + return queryParam; + } + + public ParamDescriptor setQueryParam(final int queryParam) { + this.queryParam = queryParam; + return this; + } + + @Override + public String toString() { + return "ParamDescriptor{" + "cookieParam='" + cookieParam + '\'' + + ", formParam='" + formParam + '\'' + + ", headerParam='" + headerParam + '\'' + + ", matrixParam=" + matrixParam + + ", pathParam='" + pathParam + '\'' + + ", queryParam=" + queryParam + + '}'; + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamEnum.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamEnum.java new file mode 100644 index 0000000..dba4258 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamEnum.java
@@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +public enum ParamEnum { + VALUE_ONE, + VALUE_TWO +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamResource.java new file mode 100644 index 0000000..f029413 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/ParamResource.java
@@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.io.InputStream; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +/** + * + * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a> + */ +@Consumes(MediaType.APPLICATION_FORM_URLENCODED) +@Produces(MediaType.APPLICATION_JSON) +public interface ParamResource { + + @Path("constructor") + @POST + ParamDescriptor constructor(InputStream body); + + @Path("field/{fieldPathParam}") + @POST + ParamDescriptor field(InputStream body); + + @Path("method/{methodPathParam}") + @POST + ParamDescriptor method(InputStream body); + + @Path("method-param/{methodPathParamPa}") + @POST + ParamDescriptor methodParamsAnnotated(InputStream body); +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/SearchParams.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/SearchParams.java new file mode 100644 index 0000000..cbcddc1 --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/SearchParams.java
@@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.QueryParam; + +@RequestScoped +public class SearchParams { + + @Inject + @QueryParam("q") + private String query; + + @Inject + @QueryParam("limit") + @DefaultValue("10") + private int limit; + + @Inject + @HeaderParam("Accept-Language") + private String language; + + public String getQuery() { + return query; + } + + public void setQuery(final String query) { + this.query = query; + } + + public int getLimit() { + return limit; + } + + public void setLimit(final int limit) { + this.limit = limit; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(final String language) { + this.language = language; + } + + @Override + public String toString() { + return "SearchParams{" + "query='" + query + '\'' + + ", limit=" + limit + + ", language='" + language + '\'' + + '}'; + } +}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/TypesDescriptor.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/TypesDescriptor.java new file mode 100644 index 0000000..4d2306b --- /dev/null +++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/spec/context/cdi/param/TypesDescriptor.java
@@ -0,0 +1,155 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package ee.jakarta.tck.ws.rs.spec.context.cdi.param; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +public class TypesDescriptor { + private List<String> listParam; + private Set<String> setParam; + private SortedSet<String> sortedSetParam; + private String[] arrayParam; + private Integer wrapperParam; + private Boolean booleanParam; + private ParamEnum enumParam; + private String defaultParam; + private int defaultIntParam; + private List<String> listPaths; + private String singlePath; + + public static TypesDescriptor of() { + return new TypesDescriptor(); + } + + public List<String> getListParam() { + return listParam; + } + + public TypesDescriptor setListParam(final List<String> listParam) { + this.listParam = listParam; + return this; + } + + public Set<String> getSetParam() { + return setParam; + } + + public TypesDescriptor setSetParam(final Set<String> setParam) { + this.setParam = setParam; + return this; + } + + public SortedSet<String> getSortedSetParam() { + return sortedSetParam; + } + + public TypesDescriptor setSortedSetParam(final SortedSet<String> sortedSetParam) { + this.sortedSetParam = sortedSetParam; + return this; + } + + public String[] getArrayParam() { + return arrayParam; + } + + public TypesDescriptor setArrayParam(final String[] arrayParam) { + this.arrayParam = arrayParam; + return this; + } + + public Integer getWrapperParam() { + return wrapperParam; + } + + public TypesDescriptor setWrapperParam(final Integer wrapperParam) { + this.wrapperParam = wrapperParam; + return this; + } + + public Boolean getBooleanParam() { + return booleanParam; + } + + public TypesDescriptor setBooleanParam(final Boolean booleanParam) { + this.booleanParam = booleanParam; + return this; + } + + public ParamEnum getEnumParam() { + return enumParam; + } + + public TypesDescriptor setEnumParam(final ParamEnum enumParam) { + this.enumParam = enumParam; + return this; + } + + public String getDefaultParam() { + return defaultParam; + } + + public TypesDescriptor setDefaultParam(final String defaultParam) { + this.defaultParam = defaultParam; + return this; + } + + public int getDefaultIntParam() { + return defaultIntParam; + } + + public TypesDescriptor setDefaultIntParam(final int defaultIntParam) { + this.defaultIntParam = defaultIntParam; + return this; + } + + public List<String> getListPaths() { + return listPaths; + } + + public TypesDescriptor setListPaths(final List<String> listPaths) { + this.listPaths = listPaths; + return this; + } + + public String getSinglePath() { + return singlePath; + } + + public TypesDescriptor setSinglePath(final String singlePath) { + this.singlePath = singlePath; + return this; + } + + @Override + public String toString() { + return "TypesDescriptor{" + "listParam=" + listParam + + ", setParam=" + setParam + + ", sortedSetParam=" + sortedSetParam + + ", arrayParam=" + Arrays.toString(arrayParam) + + ", wrapperParam=" + wrapperParam + + ", booleanParam=" + booleanParam + + ", enumParam=" + enumParam + + ", defaultParam='" + defaultParam + '\'' + + ", defaultIntParam=" + defaultIntParam + + ", listPaths=" + listPaths + + ", singlePath=" + singlePath + + '}'; + } +}