diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java index e003b25985c..a6140a5063d 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -184,7 +184,14 @@ private static URI baseUri(ServerRequest req) { } private void handle(ServerRequest req, ServerResponse res) { - Contexts.runInContext(req.context(), () -> doHandle(req.context(), req, res)); + Context context = req.context(); + + // make these available in context for ServerCdiExtension + context.supply(ServerRequest.class, () -> req); + context.supply(ServerResponse.class, () -> res); + + // call doHandle in active context + Contexts.runInContext(context, () -> doHandle(context, req, res)); } private void doHandle(Context ctx, ServerRequest req, ServerResponse res) { diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java index 4aa7d73f1ef..fc3e148c10b 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java @@ -17,13 +17,17 @@ package io.helidon.microprofile.server; import java.lang.System.Logger.Level; +import java.lang.annotation.Annotation; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -41,24 +45,32 @@ import io.helidon.nima.webserver.KeyPerformanceIndicatorSupport; import io.helidon.nima.webserver.WebServer; import io.helidon.nima.webserver.context.ContextFeature; +import io.helidon.nima.webserver.http.HttpRequest; import io.helidon.nima.webserver.http.HttpRouting; import io.helidon.nima.webserver.http.HttpService; +import io.helidon.nima.webserver.http.ServerRequest; +import io.helidon.nima.webserver.http.ServerResponse; import io.helidon.nima.webserver.staticcontent.StaticContentService; import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.BeforeDestroyed; import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.context.spi.CreationalContext; import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.CDI; import jakarta.enterprise.inject.spi.DeploymentException; import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.InjectionPoint; import jakarta.enterprise.inject.spi.ProcessManagedBean; import jakarta.enterprise.inject.spi.ProcessProducerField; import jakarta.enterprise.inject.spi.ProcessProducerMethod; +import jakarta.enterprise.util.AnnotationLiteral; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.ext.ParamConverterProvider; import org.eclipse.microprofile.config.ConfigProvider; @@ -582,4 +594,94 @@ private HttpRouting.Builder findRouting(String className, return serverNamedRoutingBuilder(routingName); } + + // -- CDI bean producers for ServerRequest and ServerResponse ----------------------- + + private void afterBeanDiscovery(@Observes AfterBeanDiscovery event) { + event.addBean(new ServerRequestProducer()); + event.addBean(new ServerResponseProducer()); + } + + /** + * Base class for server producers sharing common methods. + */ + static class ServerProducer { + + @SuppressWarnings("all") + public static class DefaultAnnotationLiteral extends AnnotationLiteral implements Default { + private static final long serialVersionUID = 1L; + } + + public Set getQualifiers() { + return Collections.singleton(new DefaultAnnotationLiteral()); + } + + public Class getScope() { + return RequestScoped.class; + } + + public Set> getStereotypes() { + return Collections.emptySet(); + } + + public Set getInjectionPoints() { + return Collections.emptySet(); + } + + public boolean isAlternative() { + return false; + } + + public String getName() { + return null; + } + } + + /** + * Producer bean that returns {@code ServerRequest} from active context. + */ + static class ServerRequestProducer extends ServerProducer implements Bean { + + @Override + public Class getBeanClass() { + return ServerRequest.class; + } + + @Override + public Set getTypes() { + return new HashSet<>(Arrays.asList(ServerRequest.class, HttpRequest.class, Object.class)); + } + + @Override + public ServerRequest create(CreationalContext creationalContext) { + return Contexts.context().orElseThrow().get(ServerRequest.class).orElseThrow(); + } + + public void destroy(ServerRequest request, CreationalContext creationalContext) { + } + } + + /** + * Producer bean that returns {@code ServerResponse} from active context. + */ + static class ServerResponseProducer extends ServerProducer implements Bean { + + @Override + public Class getBeanClass() { + return ServerResponse.class; + } + + @Override + public Set getTypes() { + return new HashSet<>(Arrays.asList(ServerResponse.class, Object.class)); + } + + @Override + public ServerResponse create(CreationalContext creationalContext) { + return Contexts.context().orElseThrow().get(ServerResponse.class).orElseThrow(); + } + + public void destroy(ServerResponse request, CreationalContext creationalContext) { + } + } } diff --git a/tests/functional/pom.xml b/tests/functional/pom.xml index 2912264af02..e224578e232 100644 --- a/tests/functional/pom.xml +++ b/tests/functional/pom.xml @@ -42,6 +42,7 @@ mp-compression request-scope request-scope-cdi + request-scope-injection jax-rs-multiple-apps param-converter-provider config-profiles diff --git a/tests/functional/request-scope-injection/pom.xml b/tests/functional/request-scope-injection/pom.xml new file mode 100644 index 00000000000..154d98e8846 --- /dev/null +++ b/tests/functional/request-scope-injection/pom.xml @@ -0,0 +1,61 @@ + + + + + 4.0.0 + + helidon-tests-functional-project + io.helidon.tests.functional + 4.0.0-SNAPSHOT + + + helidon-tests-functional-request-scope-injection + Helidon Functional Test: Helidon Request Scope Injection + + + + io.helidon.microprofile.bundles + helidon-microprofile-core + + + io.helidon.config + helidon-config-yaml + + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.junit.jupiter + junit-jupiter-params + test + + + diff --git a/tests/functional/request-scope-injection/src/main/java/io/helidon/tests/functional/context/injection/CheckInjectionResource.java b/tests/functional/request-scope-injection/src/main/java/io/helidon/tests/functional/context/injection/CheckInjectionResource.java new file mode 100644 index 00000000000..d85f9d3abb5 --- /dev/null +++ b/tests/functional/request-scope-injection/src/main/java/io/helidon/tests/functional/context/injection/CheckInjectionResource.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.tests.functional.context.injection; + +import java.util.Objects; + +import io.helidon.nima.webserver.http.ServerRequest; +import io.helidon.nima.webserver.http.ServerResponse; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; + +/** + * Verifies that {@code ServerRequest} and {@code ServerResponse} are injectable + * both via {@code @Context} and {@code @Inject}. + */ +@Path("/check") +public class CheckInjectionResource { + + @Context + private ServerRequest serverRequest; + + @Context + private ServerResponse serverResponse; + + @Inject + private ServerRequest serverRequestCdi; + + @Inject + private ServerResponse serverResponseCdi; + + @GET + public Response checkInjection() { + Objects.requireNonNull(serverRequest); + Objects.requireNonNull(serverResponse); + Objects.requireNonNull(serverRequestCdi); + Objects.requireNonNull(serverResponseCdi); + if (!serverRequestCdi.path().equals(serverRequest.path()) + || !serverResponseCdi.status().equals(serverResponse.status())) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + return Response.ok().build(); + } +} diff --git a/tests/functional/request-scope-injection/src/main/java/io/helidon/tests/functional/context/injection/package-info.java b/tests/functional/request-scope-injection/src/main/java/io/helidon/tests/functional/context/injection/package-info.java new file mode 100644 index 00000000000..a597378ffd6 --- /dev/null +++ b/tests/functional/request-scope-injection/src/main/java/io/helidon/tests/functional/context/injection/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Simple app to verify injection of {@code ServerRequest} and {@code ServerResponse}. + */ +package io.helidon.tests.functional.context.injection; diff --git a/tests/functional/request-scope-injection/src/main/resources/META-INF/beans.xml b/tests/functional/request-scope-injection/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..cf179d28185 --- /dev/null +++ b/tests/functional/request-scope-injection/src/main/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/tests/functional/request-scope-injection/src/test/java/io/helidon/tests/functional/context/injection/CheckInjectionTest.java b/tests/functional/request-scope-injection/src/test/java/io/helidon/tests/functional/context/injection/CheckInjectionTest.java new file mode 100644 index 00000000000..d29e2383545 --- /dev/null +++ b/tests/functional/request-scope-injection/src/test/java/io/helidon/tests/functional/context/injection/CheckInjectionTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.tests.functional.context.injection; + +import io.helidon.microprofile.tests.junit5.HelidonTest; +import jakarta.inject.Inject; +import jakarta.ws.rs.client.WebTarget; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Unit test for {@link CheckInjectionResource}. + */ +@HelidonTest +class CheckInjectionTest { + + private final WebTarget baseTarget; + + @Inject + CheckInjectionTest(WebTarget baseTarget) { + this.baseTarget = baseTarget; + } + + @Test + void testCheckInjection() { + WebTarget target = baseTarget.path("/check"); + assertThat(target.request().get().getStatus(), is(200)); + } +} \ No newline at end of file diff --git a/tests/functional/request-scope-injection/src/test/resources/logging.properties b/tests/functional/request-scope-injection/src/test/resources/logging.properties new file mode 100644 index 00000000000..2f5fd9c9515 --- /dev/null +++ b/tests/functional/request-scope-injection/src/test/resources/logging.properties @@ -0,0 +1,30 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.logging.jul.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +AUDIT.level=FINEST