diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 72c6f12f5859c..d70e547a5b639 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -18,7 +18,7 @@ 1.0.2.3 1.0.12.3 3.0.2 - 3.0.3 + 3.0.4 4.7.7.Final 0.33.0 0.2.4 @@ -117,7 +117,7 @@ 9.2.0 2.3.2 2.1.214 - 42.5.0 + 42.5.1 3.0.9 8.0.30 11.2.0.jre11 @@ -4252,6 +4252,11 @@ graal-sdk ${graal-sdk.version} + + org.graalvm.js + js + ${graal-sdk.version} + io.smallrye jandex diff --git a/build-parent/pom.xml b/build-parent/pom.xml index b2ae4963435fc..1087209f91a06 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -34,7 +34,7 @@ ${version.surefire.plugin} - 3.0.3 + 3.0.4 1.0.0 2.5.7 diff --git a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java index 6745692b559dd..7bb6a30512282 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java @@ -29,6 +29,7 @@ import io.quarkus.bootstrap.app.AugmentResult; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.maven.dependency.ArtifactCoords; /** * Builds the Quarkus application. @@ -77,11 +78,11 @@ protected boolean beforeExecute() throws MojoExecutionException { getLog().info("Skipping Quarkus build"); return false; } - if (mavenProject().getPackaging().equals("pom")) { + if (mavenProject().getPackaging().equals(ArtifactCoords.TYPE_POM)) { getLog().info("Type of the artifact is POM, skipping build goal"); return false; } - if (!mavenProject().getArtifact().getArtifactHandler().getExtension().equals("jar")) { + if (!mavenProject().getArtifact().getArtifactHandler().getExtension().equals(ArtifactCoords.TYPE_JAR)) { throw new MojoExecutionException( "The project artifact's extension is '" + mavenProject().getArtifact().getArtifactHandler().getExtension() + "' while this goal expects it be 'jar'"); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index ac5bc0683a3e1..aa3ef463c7f52 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -391,6 +391,11 @@ public void setLog(Log log) { @Override public void execute() throws MojoFailureException, MojoExecutionException { + if (project.getPackaging().equals(ArtifactCoords.TYPE_POM)) { + getLog().info("Type of the artifact is POM, skipping dev goal"); + return; + } + mavenVersionEnforcer.ensureMavenVersion(getLog(), session); initToolchain(); diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index e2f3200065220..3a29e83ad1fb6 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -1050,9 +1050,11 @@ Then, in your test you can simply use `@InjectMock` to create and inject a mock: [source,java] ---- -import static org.assertj.core.api.Assertions.assertThat; +package io.quarkus.it.rest.client.main; + import static org.mockito.Mockito.when; +import org.eclipse.microprofile.rest.client.inject.RestClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1063,6 +1065,7 @@ import io.quarkus.test.junit.mockito.InjectMock; public class InjectMockTest { @InjectMock + @RestClient Client mock; @BeforeEach @@ -1082,6 +1085,8 @@ If Mockito doesn't meet your needs, you can create a mock programmatically using [source,java] ---- +package io.quarkus.it.rest.client.main; + import org.eclipse.microprofile.rest.client.inject.RestClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java index 16dff62bae3f2..23afa839228d5 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java @@ -128,7 +128,8 @@ public Builder setUnremovable() { } /** - * The default scope is only used if there is no scope declared on the bean class. + * The default scope is only used if there is no scope declared on the bean class or added by an annotation transformer + * with priority higher than {@code io.quarkus.arc.processor.BuildExtension.DEFAULT_PRIORITY} *

* The default scope should be used in cases where a bean class source is not controlled by the extension and the * scope annotation cannot be declared directly on the class. diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 9b5840817838c..00fe8ebcf5324 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -260,6 +260,10 @@ public void transform(TransformationContext transformationContext) { // If it declares a scope no action is needed return; } + if (customScopes.isScopeIn(transformationContext.getAnnotations())) { + // if one of annotations (even if added via transformer) is a scope, no action is needed + return; + } DotName defaultScope = additionalBeanTypes.get(beanClassName); if (defaultScope != null) { transformationContext.transform().add(defaultScope).done(); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java index 89d4bde9aecd2..d2f66dccebc8c 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -31,6 +32,9 @@ public class LookupConditionsProcessor { private static final DotName LOOK_UP_UNLESS_PROPERTY_CONTAINER = DotName .createSimple(LookupUnlessProperty.List.class.getName()); + public static final Set LOOKUP_BEAN_ANNOTATIONS = Set.of(LOOK_UP_IF_PROPERTY, LOOK_UP_IF_CONTAINER, + LOOK_UP_UNLESS_PROPERTY, LOOK_UP_UNLESS_PROPERTY_CONTAINER); + private static final String NAME = "name"; private static final String STRING_VALUE = "stringValue"; private static final String LOOKUP_IF_MISSING = "lookupIfMissing"; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index df40a1a0d9ca0..f56ed896c9ef1 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -731,7 +731,7 @@ private FilterClassIntrospector createFilterClassIntrospector() { return ab.get(); } - // We want to add @Typed to resources and providers so that they can be resolved as CDI bean using purely their + // We want to add @Typed to resources, beanparams and providers so that they can be resolved as CDI bean using purely their // class as a bean type. This removes any ambiguity that potential subclasses may have. @BuildStep public void transformEndpoints( @@ -745,6 +745,10 @@ public void transformEndpoints( allResources.addAll(resourceScanningResultBuildItem.getResult().getScannedResources().keySet()); allResources.addAll(resourceScanningResultBuildItem.getResult().getPossibleSubResources().keySet()); + // all found bean params + Set beanParams = resourceScanningResultBuildItem.getResult() + .getBeanParams(); + // discovered filters and interceptors Set filtersAndInterceptors = new HashSet<>(); InterceptorContainer readerInterceptors = resourceInterceptorsBuildItem.getResourceInterceptors() @@ -789,6 +793,14 @@ public void transform(TransformationContext context) { && clazz.declaredAnnotation(ResteasyReactiveDotNames.TYPED) == null) { // Add @Typed(MyResource.class) context.transform().add(createTypedAnnotationInstance(clazz, beanArchiveIndexBuildItem)).done(); + return; + } + // check if the class is a bean param + if (beanParams.contains(clazz.name().toString()) + && clazz.declaredAnnotation(ResteasyReactiveDotNames.TYPED) == null) { + // Add @Typed(MyBean.class) + context.transform().add(createTypedAnnotationInstance(clazz, beanArchiveIndexBuildItem)).done(); + return; } } })); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java index 32f47b768d25a..aacd2043ff891 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java @@ -50,6 +50,7 @@ import io.quarkus.arc.deployment.BuildTimeEnabledProcessor; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.deployment.LookupConditionsProcessor; import io.quarkus.arc.processor.DotNames; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; @@ -81,6 +82,13 @@ */ public class ResteasyReactiveScanningProcessor { + public static final Set CONDITIONAL_BEAN_ANNOTATIONS; + + static { + CONDITIONAL_BEAN_ANNOTATIONS = new HashSet<>(BuildTimeEnabledProcessor.BUILD_TIME_ENABLED_BEAN_ANNOTATIONS); + CONDITIONAL_BEAN_ANNOTATIONS.addAll(LookupConditionsProcessor.LOOKUP_BEAN_ANNOTATIONS); + } + @BuildStep public MethodScannerBuildItem asyncSupport() { return new MethodScannerBuildItem(new AsyncReturnTypeScanner()); @@ -313,9 +321,18 @@ public void handleCustomAnnotatedMethods( List generatedFilters = FilterGeneration.generate(index, Set.of(HTTP_SERVER_REQUEST, HTTP_SERVER_RESPONSE, ROUTING_CONTEXT), Set.of(Unremovable.class.getName()), (methodInfo -> { + List methodAnnotations = methodInfo.annotations(); + for (AnnotationInstance methodAnnotation : methodAnnotations) { + if (CONDITIONAL_BEAN_ANNOTATIONS.contains(methodAnnotation.name())) { + throw new RuntimeException("The combination of '@" + methodAnnotation.name().withoutPackagePrefix() + + "' and '@ServerRequestFilter' or '@ServerResponseFilter' is not allowed. Offending method is '" + + methodInfo.name() + "' of class '" + methodInfo.declaringClass().name() + "'"); + } + } + List classAnnotations = methodInfo.declaringClass().declaredAnnotations(); for (AnnotationInstance classAnnotation : classAnnotations) { - if (BuildTimeEnabledProcessor.BUILD_TIME_ENABLED_BEAN_ANNOTATIONS.contains(classAnnotation.name())) { + if (CONDITIONAL_BEAN_ANNOTATIONS.contains(classAnnotation.name())) { return true; } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/BeanParamTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/BeanParamTest.java new file mode 100644 index 0000000000000..85a739e30b75a --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/BeanParamTest.java @@ -0,0 +1,104 @@ +package io.quarkus.resteasy.reactive.server.test.beanparam; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.CookieParam; +import javax.ws.rs.FormParam; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class BeanParamTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(MyBeanParamWithFieldsAndProperties.class, Top.class); + }); + + @Test + void shouldDeployWithoutIssues() { + // we only need to check that it deploys + } + + public static class Top { + @PathParam("pathParam") + private String pathParam = "pathParam"; + + public String getPathParam() { + return pathParam; + } + + public void setPathParam(String pathParam) { + this.pathParam = pathParam; + } + } + + public static class MyBeanParamWithFieldsAndProperties extends Top { + @HeaderParam("headerParam") + private String headerParam = "headerParam"; + @CookieParam("cookieParam") + private String cookieParam = "cookieParam"; + @FormParam("formParam") + private String formParam = "formParam"; + @QueryParam("queryParam") + private String queryParam = "queryParam"; + + // FIXME: Matrix not supported + + public String getHeaderParam() { + return headerParam; + } + + public void setHeaderParam(String headerParam) { + this.headerParam = headerParam; + } + + public String getCookieParam() { + return cookieParam; + } + + public void setCookieParam(String cookieParam) { + this.cookieParam = cookieParam; + } + + public String getFormParam() { + return formParam; + } + + public void setFormParam(String formParam) { + this.formParam = formParam; + } + + public String getQueryParam() { + return queryParam; + } + + public void setQueryParam(String queryParam) { + this.queryParam = queryParam; + } + } + + @Path("/") + public static class Resource { + @Path("/a/{restPathDefault}/{restPath_Overridden}/{pathParam}") + @POST + public String beanParamWithFields(@BeanParam MyBeanParamWithFieldsAndProperties p) { + return null; + } + + @Path("/b/{pathParam}") + @POST + public String beanParamWithFields(@BeanParam Top p) { + return null; + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ConditionalBeanFiltersTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ConditionalBeanFiltersTest.java index 4168dabc5eadc..e1822c0da4553 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ConditionalBeanFiltersTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ConditionalBeanFiltersTest.java @@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.lookup.LookupIfProperty; +import io.quarkus.arc.lookup.LookupUnlessProperty; import io.quarkus.arc.profile.IfBuildProfile; import io.quarkus.arc.properties.IfBuildProperty; import io.quarkus.test.QuarkusUnitTest; @@ -49,14 +51,14 @@ public JavaArchive get() { public void testExpectedFilters() { List responseFiltersValues = get("/test/filters") .then().statusCode(200) - .body(Matchers.is("void-on,response-on,uni-on,always")) + .body(Matchers.is("void-on,response-on,uni-on,void-lookup-on,always")) .extract() .headers() .getList("response-filters") .stream() .map(Header::getValue) .collect(Collectors.toList()); - assertThat(responseFiltersValues).containsOnly("always", "void-on", "uni-on"); + assertThat(responseFiltersValues).containsOnly("always", "void-lookup-on", "void-on", "uni-on"); } @Path("test") @@ -135,6 +137,34 @@ public Uni uniResponseFilter(ContainerResponseContext ctx) { } } + @LookupIfProperty(name = "notexistingproperty", stringValue = "true") + public static class WontBeEnabledLookupPropertyFilter { + + @ServerRequestFilter(priority = Priorities.USER + 10) + public void voidRequestFilter(ContainerRequestContext requestContext) { + requestContext.getHeaders().add("request-filters", "void-lookup-off"); + } + + @ServerResponseFilter + public void voidResponseFilter(ContainerResponseContext ctx) { + assertFalse(true); + } + } + + @LookupUnlessProperty(name = "notexistingproperty", stringValue = "true", lookupIfMissing = true) + public static class WillBeEnabledLookupPropertyFilter { + + @ServerRequestFilter(priority = Priorities.USER + 20) + public void voidRequestFilter(ContainerRequestContext requestContext) { + requestContext.getHeaders().add("request-filters", "void-lookup-on"); + } + + @ServerResponseFilter(priority = Priorities.USER + 20) + public void voidResponseFilter(ContainerResponseContext ctx) { + ctx.getHeaders().add("response-filters", "void-lookup-on"); + } + } + @Singleton public static class AlwaysEnabledFilter { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/InvalidConditionalBeanFiltersTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/InvalidConditionalBeanFiltersTest.java new file mode 100644 index 0000000000000..b101b18929538 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/InvalidConditionalBeanFiltersTest.java @@ -0,0 +1,62 @@ +package io.quarkus.resteasy.reactive.server.test.customproviders; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.UriInfo; + +import org.jboss.resteasy.reactive.server.ServerRequestFilter; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.profile.IfBuildProfile; +import io.quarkus.test.QuarkusUnitTest; + +public class InvalidConditionalBeanFiltersTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TestResource.class, Filters.class); + } + }).assertException(t -> { + String message = t.getMessage(); + assertTrue(message.contains("@IfBuildProfile")); + assertTrue(message.contains("request")); + assertTrue(message.contains(InvalidConditionalBeanFiltersTest.Filters.class.getName())); + }); + + @Test + public void test() { + fail("Should never have been called"); + } + + @Path("test") + public static class TestResource { + + @GET + public String hello() { + return "hello"; + } + + } + + public static class Filters { + + @IfBuildProfile("test") + @ServerRequestFilter + public void request(UriInfo info) { + + } + + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index 6c34e448daabe..c95b37cbd1302 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -95,6 +95,7 @@ import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; +import io.quarkus.vertx.http.runtime.devmode.DevConsoleCORSFilter; import io.quarkus.vertx.http.runtime.devmode.DevConsoleFilter; import io.quarkus.vertx.http.runtime.devmode.DevConsoleRecorder; import io.quarkus.vertx.http.runtime.devmode.RedirectHandler; @@ -438,6 +439,7 @@ public DevConsoleTemplateInfoBuildItem config(List routes, @@ -472,6 +474,12 @@ public void setupDevConsoleRoutes( // if the handler is a proxy, then that means it's been produced by a recorder and therefore belongs in the regular runtime Vert.x instance // otherwise this is handled in the setupDeploymentSideHandling method if (!i.isDeploymentSide()) { + if (devUIConfig.cors.enabled) { + routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() + .route("dev/*") + .handler(new DevConsoleCORSFilter()) + .build()); + } NonApplicationRootPathBuildItem.Builder builder = nonApplicationRootPathBuildItem.routeBuilder() .routeFunction( "dev/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath(), diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevUIConfig.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevUIConfig.java index 634a1d9d7dd5a..828db66ef474c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevUIConfig.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevUIConfig.java @@ -1,5 +1,6 @@ package io.quarkus.vertx.http.deployment.devmode.console; +import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -12,4 +13,19 @@ public class DevUIConfig { @ConfigItem(defaultValue = "50") public int historySize; + /** + * CORS configuration. + */ + public Cors cors = new Cors(); + + @ConfigGroup + public static class Cors { + + /** + * Enable CORS filter. + */ + @ConfigItem(defaultValue = "true") + public boolean enabled = true; + } + } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java index a89ee9697275b..bea0ee1578301 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.cors; import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.nullValue; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,6 +29,7 @@ void corsMatchingOrigin() { .when() .options("/test").then() .statusCode(200) + .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Credentials", "true"); } @@ -42,7 +44,8 @@ void corsNotMatchingOrigin() { .header("Access-Control-Request-Headers", headers) .when() .options("/test").then() - .statusCode(200) + .statusCode(403) + .header("Access-Control-Allow-Origin", nullValue()) .header("Access-Control-Allow-Credentials", "false"); } @@ -58,6 +61,7 @@ void corsMatchingOriginWithWildcard() { .when() .options("/test").then() .statusCode(200) + .header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Credentials", "false"); } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleCorsTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleCorsTest.java new file mode 100644 index 0000000000000..81d6660f6c244 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleCorsTest.java @@ -0,0 +1,194 @@ +package io.quarkus.vertx.http.devconsole; + +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class DevConsoleCorsTest { + + @RegisterExtension + static final QuarkusDevModeTest config = new QuarkusDevModeTest() + .withEmptyApplication(); + + @Test + public void testPreflightHttpLocalhostOrigin() { + String origin = "http://localhost:8080"; + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightHttpLocalhostIpOrigin() { + String origin = "http://127.0.0.1:8080"; + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightHttpsLocalhostOrigin() { + String origin = "https://localhost:8443"; + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightHttpsLocalhostIpOrigin() { + String origin = "https://127.0.0.1:8443"; + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightNonLocalhostOrigin() { + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", "https://quarkus.io/http://localhost") + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(403) + .header("Access-Control-Allow-Origin", nullValue()) + .header("Access-Control-Allow-Methods", nullValue()) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightBadLocalhostOrigin() { + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", "http://localhost:8080/devui") + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(403) + .header("Access-Control-Allow-Origin", nullValue()) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightBadLocalhostIpOrigin() { + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", "http://127.0.0.1:8080/devui") + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(403) + .header("Access-Control-Allow-Origin", nullValue()) + .body(emptyOrNullString()); + } + + @Test + public void testPreflightLocalhostOriginWithoutPort() { + String methods = "GET,POST"; + RestAssured.given() + .header("Origin", "http://localhost") + .header("Access-Control-Request-Method", methods) + .when() + .options("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(403) + .header("Access-Control-Allow-Origin", nullValue()) + .body(emptyOrNullString()); + } + + @Test + public void testSimpleRequestHttpLocalhostOrigin() { + String origin = "http://localhost:8080"; + RestAssured.given() + .header("Origin", origin) + .when() + .get("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", nullValue()) + .body(not(emptyOrNullString())); + } + + @Test + public void testSimpleRequestHttpLocalhostIpOrigin() { + String origin = "http://127.0.0.1:8080"; + RestAssured.given() + .header("Origin", origin) + .when() + .get("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", nullValue()) + .body(not(emptyOrNullString())); + } + + @Test + public void testSimpleRequestHttpsLocalhostOrigin() { + String origin = "https://localhost:8443"; + RestAssured.given() + .header("Origin", origin) + .when() + .get("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", nullValue()) + .body(not(emptyOrNullString())); + } + + @Test + public void testSimpleRequestHttpsLocalhostIpOrigin() { + String origin = "https://127.0.0.1:8443"; + RestAssured.given() + .header("Origin", origin) + .when() + .get("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", nullValue()) + .body(not(emptyOrNullString())); + } + + @Test + public void testSimpleRequestNonLocalhostOrigin() { + RestAssured.given() + .header("Origin", "https://quarkus.io/http://localhost") + .when() + .get("q/dev/io.quarkus.quarkus-vertx-http/config").then() + .statusCode(403) + .header("Access-Control-Allow-Origin", nullValue()) + .body(emptyOrNullString()); + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java index 50f45d0b1f383..d98820d59c099 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java @@ -24,7 +24,7 @@ public class CORSConfig { */ @ConfigItem @ConvertWith(TrimmedStringConverter.class) - public Optional> origins; + public Optional> origins = Optional.empty(); /** * HTTP methods allowed for CORS @@ -36,7 +36,7 @@ public class CORSConfig { */ @ConfigItem @ConvertWith(TrimmedStringConverter.class) - public Optional> methods; + public Optional> methods = Optional.empty(); /** * HTTP headers allowed for CORS @@ -48,7 +48,7 @@ public class CORSConfig { */ @ConfigItem @ConvertWith(TrimmedStringConverter.class) - public Optional> headers; + public Optional> headers = Optional.empty(); /** * HTTP headers exposed in CORS @@ -59,14 +59,14 @@ public class CORSConfig { */ @ConfigItem @ConvertWith(TrimmedStringConverter.class) - public Optional> exposedHeaders; + public Optional> exposedHeaders = Optional.empty(); /** * The `Access-Control-Max-Age` response header value indicating * how long the results of a pre-flight request can be cached. */ @ConfigItem - public Optional accessControlMaxAge; + public Optional accessControlMaxAge = Optional.empty(); /** * The `Access-Control-Allow-Credentials` header is used to tell the @@ -77,7 +77,7 @@ public class CORSConfig { * there is a match with the precise `Origin` header and that header is not '*'. */ @ConfigItem - public Optional accessControlAllowCredentials; + public Optional accessControlAllowCredentials = Optional.empty(); @Override public String toString() { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java index 8bb838927594e..4a7fee1a14e4f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java @@ -195,7 +195,11 @@ public void handle(RoutingContext event) { String.join(",", exposedHeaders.orElse(Collections.emptyList()))); } - if (request.method().equals(HttpMethod.OPTIONS) && (requestedHeaders != null || requestedMethods != null)) { + if (!allowsOrigin) { + response.setStatusCode(403); + response.setStatusMessage("CORS Rejected - Invalid origin"); + response.end(); + } else if (request.method().equals(HttpMethod.OPTIONS) && (requestedHeaders != null || requestedMethods != null)) { if (corsConfig.accessControlMaxAge.isPresent()) { response.putHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, String.valueOf(corsConfig.accessControlMaxAge.get().getSeconds())); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleCORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleCORSFilter.java new file mode 100644 index 0000000000000..0ef7e7627bec5 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleCORSFilter.java @@ -0,0 +1,63 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import java.util.List; +import java.util.Optional; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.logging.Logger; + +import io.quarkus.vertx.http.runtime.cors.CORSConfig; +import io.quarkus.vertx.http.runtime.cors.CORSFilter; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + +public class DevConsoleCORSFilter implements Handler { + private static final Logger LOG = Logger.getLogger(DevConsoleCORSFilter.class); + + private static final String HTTP_PORT_CONFIG_PROP = "quarkus.http.port"; + private static final String HTTPS_PORT_CONFIG_PROP = "quarkus.http.ssl-port"; + private static final String LOCAL_HOST = "localhost"; + private static final String LOCAL_HOST_IP = "127.0.0.1"; + private static final String HTTP_LOCAL_HOST = "http://" + LOCAL_HOST; + private static final String HTTPS_LOCAL_HOST = "https://" + LOCAL_HOST; + private static final String HTTP_LOCAL_HOST_IP = "http://" + LOCAL_HOST_IP; + private static final String HTTPS_LOCAL_HOST_IP = "https://" + LOCAL_HOST_IP; + + public DevConsoleCORSFilter() { + } + + private static CORSFilter corsFilter() { + int httpPort = ConfigProvider.getConfig().getValue(HTTP_PORT_CONFIG_PROP, int.class); + int httpsPort = ConfigProvider.getConfig().getValue(HTTPS_PORT_CONFIG_PROP, int.class); + CORSConfig config = new CORSConfig(); + config.origins = Optional.of(List.of( + HTTP_LOCAL_HOST + ":" + httpPort, + HTTP_LOCAL_HOST_IP + ":" + httpPort, + HTTPS_LOCAL_HOST + ":" + httpsPort, + HTTPS_LOCAL_HOST_IP + ":" + httpsPort)); + return new CORSFilter(config); + } + + @Override + public void handle(RoutingContext event) { + HttpServerRequest request = event.request(); + HttpServerResponse response = event.response(); + String origin = request.getHeader(HttpHeaders.ORIGIN); + if (origin == null) { + corsFilter().handle(event); + } else { + if (origin.startsWith(HTTP_LOCAL_HOST) || origin.startsWith(HTTPS_LOCAL_HOST) + || origin.startsWith(HTTP_LOCAL_HOST_IP) || origin.startsWith(HTTPS_LOCAL_HOST_IP)) { + corsFilter().handle(event); + } else { + LOG.errorf("Only localhost origin is allowed, but Origin header value is: %s", origin); + response.setStatusCode(403); + response.setStatusMessage("CORS Rejected - Invalid origin"); + response.end(); + } + } + } +} diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusCodecProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusCodecProcessor.java index 1b6c049760a64..3d2aba17cccdf 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusCodecProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusCodecProcessor.java @@ -42,7 +42,7 @@ public void registerCodecs( final IndexView index = beanArchiveIndexBuildItem.getIndex(); Collection consumeEventAnnotationInstances = index.getAnnotations(CONSUME_EVENT); - Map codecByTypes = new HashMap<>(); + Map codecByTypes = new HashMap<>(); for (AnnotationInstance consumeEventAnnotationInstance : consumeEventAnnotationInstances) { AnnotationTarget typeTarget = consumeEventAnnotationInstance.target(); if (typeTarget.kind() != AnnotationTarget.Kind.METHOD) { @@ -59,7 +59,7 @@ public void registerCodecs( if (codecTargetFromParameter == null) { throw new IllegalStateException("Invalid `codec` argument in @ConsumeEvent - no parameter"); } - codecByTypes.put(codecTargetFromParameter, codec.asClass().asClassType().name()); + codecByTypes.put(codecTargetFromParameter.name(), codec.asClass().asClassType().name()); } else if (codecTargetFromParameter != null) { // Codec is not set, check if we have a built-in codec if (!hasBuiltInCodec(codecTargetFromParameter)) { @@ -70,24 +70,24 @@ public void registerCodecs( "The generic message codec can only be used for local delivery," + ", implement your own event bus codec for " + codecTargetFromParameter.name() .toString()); - } else if (!codecByTypes.containsKey(codecTargetFromParameter)) { + } else if (!codecByTypes.containsKey(codecTargetFromParameter.name())) { LOGGER.infof("Local Message Codec registered for type %s", codecTargetFromParameter.toString()); - codecByTypes.put(codecTargetFromParameter, LOCAL_EVENT_BUS_CODEC); + codecByTypes.put(codecTargetFromParameter.name(), LOCAL_EVENT_BUS_CODEC); } } } if (codecTargetFromReturnType != null && !hasBuiltInCodec(codecTargetFromReturnType) - && !codecByTypes.containsKey(codecTargetFromReturnType)) { + && !codecByTypes.containsKey(codecTargetFromReturnType.name())) { LOGGER.infof("Local Message Codec registered for type %s", codecTargetFromReturnType.toString()); - codecByTypes.put(codecTargetFromReturnType, LOCAL_EVENT_BUS_CODEC); + codecByTypes.put(codecTargetFromReturnType.name(), LOCAL_EVENT_BUS_CODEC); } } // Produce the build items - for (Map.Entry entry : codecByTypes.entrySet()) { + for (Map.Entry entry : codecByTypes.entrySet()) { messageCodecs.produce(new MessageCodecBuildItem(entry.getKey().toString(), entry.getValue().toString())); } diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/EventBusCodecTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/EventBusCodecTest.java index 3bb61dd42ea6f..26cbd3a1ea86a 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/EventBusCodecTest.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/EventBusCodecTest.java @@ -2,6 +2,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -73,6 +77,11 @@ String getMessage() { } } + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.TYPE_USE) + @interface NonNull { + } + static class MyBean { @ConsumeEvent("person") public CompletionStage hello(Person p) { @@ -83,6 +92,12 @@ public CompletionStage hello(Person p) { public CompletionStage hello(Pet p) { return CompletableFuture.completedFuture(new Greeting("Hello " + p.getName())); } + + // presence of this method is enough to verify that type annotation + // on the message type doesn't cause failure + @ConsumeEvent("message-type-with-type-annotation") + void messageTypeWithTypeAnnotation(@NonNull Person person) { + } } static class MyNonLocalBean { diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index a7d542363b9b4..c75046ffe976b 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -42,7 +42,7 @@ 2.0.2 1.3.3 - 3.0.3 + 3.0.4 5.9.1 3.8.6 3.23.1 diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java index 3446f86ad3ef2..88e8e83a89377 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java @@ -73,6 +73,13 @@ public ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, C * the annotation members have the same values as the given annotation instance. * An implementation of the annotation type will be generated automatically. *

+ * It is expected that given annotation instance is runtime-retained; an exception is thrown + * if not. Further, it is expected that the annotation type is available (that is, + * {@code annotationClass != null}); an exception is thrown if not. Callers that expect + * they always deal with runtime-retained annotations whose classes are available do not + * have to check (and will get decent errors for free), but callers that can possibly deal + * with class-retained annotations or missing annotation classes must check explicitly. + *

* We call the generated implementation of the annotation type an annotation literal class * and the instance produced by the generated bytecode an annotation literal instance, * even though the generated code doesn't use CDI's {@code AnnotationLiteral} anymore. @@ -84,6 +91,10 @@ public ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, C * @return an annotation literal instance result handle */ public ResultHandle create(BytecodeCreator bytecode, ClassInfo annotationClass, AnnotationInstance annotationInstance) { + if (!annotationInstance.runtimeVisible()) { + throw new IllegalArgumentException("Annotation does not have @Retention(RUNTIME): " + annotationInstance); + } + Objects.requireNonNull(annotationClass, "Annotation class not available: " + annotationInstance); AnnotationLiteralClassInfo literal = cache.getValue(new CacheKey(annotationClass)); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 698525b765051..952895860541d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -956,14 +956,15 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator bridgeCreate.getMethodParam(0))); } - private List newProviderHandles(BeanInfo bean, ClassCreator beanCreator, MethodCreator createMethod, + private void newProviderHandles(BeanInfo bean, ClassCreator beanCreator, MethodCreator createMethod, Map injectionPointToProviderField, Map interceptorToProviderField, Map decoratorToProviderSupplierField, Map interceptorToWrap, - List transientReferences) { + List transientReferences, + List injectableParamHandles, + List allOtherParamHandles) { - List providerHandles = new ArrayList<>(); Optional constructorInjection = bean.getConstructorInjection(); if (constructorInjection.isPresent()) { @@ -978,7 +979,7 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr providerHandle, createMethod.getMethodParam(0)); ResultHandle referenceHandle = createMethod.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtx); - providerHandles.add(referenceHandle); + injectableParamHandles.add(referenceHandle); if (injectionPoint.isDependentTransientReference()) { transientReferences.add(new TransientReference(providerHandle, referenceHandle, childCtx)); } @@ -988,7 +989,7 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { ResultHandle wrapped = interceptorToWrap.get(interceptor); if (wrapped != null) { - providerHandles.add(wrapped); + allOtherParamHandles.add(wrapped); } else { ResultHandle interceptorProviderSupplierHandle = createMethod.readInstanceField( FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), @@ -996,7 +997,7 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr createMethod.getThis()); ResultHandle interceptorProviderHandle = createMethod.invokeInterfaceMethod( MethodDescriptors.SUPPLIER_GET, interceptorProviderSupplierHandle); - providerHandles.add(interceptorProviderHandle); + allOtherParamHandles.add(interceptorProviderHandle); } } for (DecoratorInfo decorator : bean.getBoundDecorators()) { @@ -1006,10 +1007,9 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr createMethod.getThis()); ResultHandle decoratorProviderHandle = createMethod.invokeInterfaceMethod( MethodDescriptors.SUPPLIER_GET, decoratorProviderSupplierHandle); - providerHandles.add(decoratorProviderHandle); + allOtherParamHandles.add(decoratorProviderHandle); } } - return providerHandles; } private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, BytecodeCreator creator, @@ -1402,14 +1402,20 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat } List transientReferences = new ArrayList<>(); - List providerHandles = newProviderHandles(bean, beanCreator, create, + // List of handles representing injectable parameters + List injectableCtorParams = new ArrayList<>(); + // list of handles representing all other parameters, such as injectable interceptors + List allOtherCtorParams = new ArrayList<>(); + newProviderHandles(bean, beanCreator, create, injectionPointToProviderSupplierField, interceptorToProviderSupplierField, decoratorToProviderSupplierField, - interceptorToWrap, transientReferences); + interceptorToWrap, transientReferences, injectableCtorParams, allOtherCtorParams); // Forwarding function // Supplier forward = () -> new SimpleBean_Subclass(ctx,lifecycleInterceptorProvider1) FunctionCreator func = create.createFunction(Supplier.class); BytecodeCreator funcBytecode = func.getBytecode(); + List providerHandles = new ArrayList<>(injectableCtorParams); + providerHandles.addAll(allOtherCtorParams); ResultHandle retHandle = newInstanceHandle(bean, beanCreator, funcBytecode, create, providerType.className(), baseName, providerHandles, @@ -1427,11 +1433,10 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat create.writeArrayValue(bindingsArray, bindingsIndex++, annotationLiterals.create(create, bindingClass, binding)); } - // ResultHandle of Object[] holding all constructor args - ResultHandle ctorArgsArray = create.newArray(Object.class, create.load(providerHandles.size())); - for (int i = 0; i < providerHandles.size(); i++) { - create.writeArrayValue(ctorArgsArray, i, providerHandles.get(i)); + ResultHandle ctorArgsArray = create.newArray(Object.class, create.load(injectableCtorParams.size())); + for (int i = 0; i < injectableCtorParams.size(); i++) { + create.writeArrayValue(ctorArgsArray, i, injectableCtorParams.get(i)); } ResultHandle invocationContextHandle = create.invokeStaticMethod( MethodDescriptors.INVOCATION_CONTEXTS_AROUND_CONSTRUCT, constructorHandle, @@ -1452,11 +1457,13 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat } else { List transientReferences = new ArrayList<>(); + List providerHandles = new ArrayList<>(); + newProviderHandles(bean, beanCreator, create, injectionPointToProviderSupplierField, + interceptorToProviderSupplierField, decoratorToProviderSupplierField, + interceptorToWrap, transientReferences, providerHandles, providerHandles); create.assign(instanceHandle, newInstanceHandle(bean, beanCreator, create, create, providerType.className(), baseName, - newProviderHandles(bean, beanCreator, create, injectionPointToProviderSupplierField, - interceptorToProviderSupplierField, decoratorToProviderSupplierField, - interceptorToWrap, transientReferences), + providerHandles, reflectionRegistration, isApplicationClass)); // Destroy injected transient references destroyTransientReferences(create, transientReferences); @@ -1932,9 +1939,14 @@ public static ResultHandle collectInjectionPointAnnotations(ClassOutput classOut annotationHandle = constructor .readStaticField(FieldDescriptor.of(InjectLiteral.class, "INSTANCE", InjectLiteral.class)); } else { - // Create annotation literal if needed - ClassInfo literalClass = getClassByName(beanDeployment.getBeanArchiveIndex(), annotation.name()); - annotationHandle = annotationLiterals.create(constructor, literalClass, annotation); + if (!annotation.runtimeVisible()) { + continue; + } + ClassInfo annotationClass = getClassByName(beanDeployment.getBeanArchiveIndex(), annotation.name()); + if (annotationClass == null) { + continue; + } + annotationHandle = annotationLiterals.create(constructor, annotationClass, annotation); } constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotationsHandle, annotationHandle); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 9beb087827f3b..0c32a3a479b9a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -649,7 +649,7 @@ private void addDecoratedMethods(Map decoratedMethods } } - List findMatchingDecorators(MethodInfo method, List decorators) { + private List findMatchingDecorators(MethodInfo method, List decorators) { List methodParams = method.parameterTypes(); List matching = new ArrayList<>(decorators.size()); for (DecoratorInfo decorator : decorators) { @@ -661,6 +661,10 @@ List findMatchingDecorators(MethodInfo method, List resolvedTypeParameters = Types.resolveDecoratedTypeParams(decoratedTypeClass, + decorator); + for (MethodInfo decoratedMethod : decoratedTypeClass.methods()) { if (!method.name().equals(decoratedMethod.name())) { continue; @@ -671,10 +675,12 @@ List findMatchingDecorators(MethodInfo method, List should result in a T -> String mapping List typeParameters = decoratedTypeClass.typeParameters(); - Map resolvedTypeParameters = Collections.emptyMap(); - if (!typeParameters.isEmpty()) { - resolvedTypeParameters = new HashMap<>(); - // The delegate type can be used to infer the parameter types - org.jboss.jandex.Type type = decorator.getDelegateType(); - if (type.kind() == Kind.PARAMETERIZED_TYPE) { - List typeArguments = type.asParameterizedType().arguments(); - for (int i = 0; i < typeParameters.size(); i++) { - resolvedTypeParameters.put(typeParameters.get(i).identifier(), typeArguments.get(i)); - } - } - } + Map resolvedTypeParameters = Types.resolveDecoratedTypeParams(decoratedTypeClass, + decorator); for (MethodInfo method : decoratedTypeClass.methods()) { if (Methods.skipForDelegateSubclass(method)) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index 0dfb282aa6440..c485959c7b4b3 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -606,7 +606,11 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi paramHandles[paramIdx++] = subclassConstructor.getThis(); } for (DecoratorInfo decoratorParameter : decoratorParameters) { - paramHandles[paramIdx++] = decoratorToResultHandle.get(decoratorParameter.getIdentifier()); + ResultHandle decoratorHandle = decoratorToResultHandle.get(decoratorParameter.getIdentifier()); + if (decoratorHandle == null) { + throw new IllegalStateException("Decorator handle must not be null"); + } + paramHandles[paramIdx++] = decoratorHandle; } ResultHandle delegateSubclassInstance = subclassConstructor.newInstance(MethodDescriptor.ofConstructor( delegateSubclass.getClassName(), constructorParameterTypes.toArray(new String[0])), paramHandles); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index 663b3ee491792..0b914cad9ef5b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -384,8 +384,23 @@ static Set getClassBeanTypeClosure(ClassInfo classInfo, BeanDeployment bea return restrictBeanTypes(types, beanDeployment.getAnnotations(classInfo), beanDeployment.getBeanArchiveIndex()); } - static List getResolvedParameters(ClassInfo classInfo, MethodInfo method, IndexView index) { - return getResolvedParameters(classInfo, Collections.emptyMap(), method, index); + static Map resolveDecoratedTypeParams(ClassInfo decoratedTypeClass, DecoratorInfo decorator) { + // A decorated type can declare type parameters + // For example Converter should result in a T -> String mapping + List typeParameters = decoratedTypeClass.typeParameters(); + Map resolvedTypeParameters = Collections.emptyMap(); + if (!typeParameters.isEmpty()) { + resolvedTypeParameters = new HashMap<>(); + // The delegate type can be used to infer the parameter types + org.jboss.jandex.Type type = decorator.getDelegateType(); + if (type.kind() == Kind.PARAMETERIZED_TYPE) { + List typeArguments = type.asParameterizedType().arguments(); + for (int i = 0; i < typeParameters.size(); i++) { + resolvedTypeParameters.put(typeParameters.get(i).identifier(), typeArguments.get(i)); + } + } + } + return resolvedTypeParameters; } static List getResolvedParameters(ClassInfo classInfo, Map resolvedMap, @@ -588,7 +603,7 @@ static Type resolveTypeParam(Type typeParam, Map resolvedTypeParam resolvedTypeParameters, index); Type[] typeParams = new Type[typeParameters.size()]; for (int i = 0; i < typeParameters.size(); i++) { - typeParams[i] = resolveTypeParam(arguments.get(i), resolvedMap, index); + typeParams[i] = resolveTypeParam(typeParameters.get(i), resolvedMap, index); } return ParameterizedType.create(parameterizedType.name(), typeParams, null); } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/AnnotationLiteralProcessorTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/AnnotationLiteralProcessorTest.java index 608f43f14835e..9735bfaca6cf1 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/AnnotationLiteralProcessorTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/AnnotationLiteralProcessorTest.java @@ -1,6 +1,7 @@ package io.quarkus.arc.processor; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.lang.annotation.Retention; @@ -29,6 +30,10 @@ public enum SimpleEnum { BAZ, } + @Retention(RetentionPolicy.CLASS) + public @interface ClassRetainedAnnotation { + } + @Retention(RetentionPolicy.RUNTIME) public @interface SimpleAnnotation { String value(); @@ -321,6 +326,35 @@ public void test() throws ReflectiveOperationException { annotation.toString()); } + @Test + public void missingAnnotationClass() { + AnnotationLiteralProcessor literals = new AnnotationLiteralProcessor(index, ignored -> true); + + TestClassLoader cl = new TestClassLoader(getClass().getClassLoader()); + try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className(generatedClass).build()) { + MethodCreator method = creator.getMethodCreator("hello", void.class); + + assertThrows(NullPointerException.class, () -> { + literals.create(method, null, simpleAnnotationJandex("foobar")); + }); + } + } + + @Test + public void classRetainedAnnotation() { + AnnotationLiteralProcessor literals = new AnnotationLiteralProcessor(index, ignored -> true); + + TestClassLoader cl = new TestClassLoader(getClass().getClassLoader()); + try (ClassCreator creator = ClassCreator.builder().classOutput(cl).className(generatedClass).build()) { + MethodCreator method = creator.getMethodCreator("hello", void.class); + + assertThrows(IllegalArgumentException.class, () -> { + literals.create(method, Index.singleClass(ClassRetainedAnnotation.class), + AnnotationInstance.builder(ClassRetainedAnnotation.class).build()); + }); + } + } + private static void verify(ComplexAnnotation ann) { assertEquals(true, ann.bool()); assertEquals((byte) 1, ann.b()); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java index 204114e169531..569681b9fe0aa 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java @@ -471,8 +471,13 @@ public void run() { } catch (Exception e) { // swallow exception and log errors for every problematic OM LOG.errorf( - "Failure occurred while notifying a transational %s for event of type %s \n- please enable debug logging to see the full stack trace", - observerMethod, eventContext.getMetadata().getType().getTypeName()); + "Failure occurred while notifying a transational %s for event of type %s " + + "\n- please enable debug logging to see the full stack trace" + + "\n %s", + observerMethod, eventContext.getMetadata().getType().getTypeName(), + e.getCause() != null && e.getMessage() != null + ? "Cause: " + e.getCause() + " Message: " + e.getMessage() + : "Exception caught: " + e); LOG.debugf(e, "Failure occurred while notifying a transational %s for event of type %s", observerMethod, eventContext.getMetadata().getType().getTypeName()); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/SimpleDecoratorOverloadingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/SimpleDecoratorOverloadingTest.java new file mode 100644 index 0000000000000..cd66ec68ebebf --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/SimpleDecoratorOverloadingTest.java @@ -0,0 +1,75 @@ +package io.quarkus.arc.test.decorators; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.annotation.Priority; +import javax.decorator.Decorator; +import javax.decorator.Delegate; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class SimpleDecoratorOverloadingTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, SimpleConverter.class, + ConverterDecorator.class); + + @Test + public void testDecoration() { + SimpleConverter converter = Arc.container().instance(SimpleConverter.class).get(); + assertEquals("HOLA!", converter.convert(" holA!")); + assertEquals(42, converter.convert(42)); + } + + interface Converter { + + int convert(int value); + + String convert(String value); + + } + + @ApplicationScoped + static class SimpleConverter implements Converter { + + @Override + public String convert(String value) { + return value.toUpperCase(); + } + + @Override + public int convert(int value) { + return -1 * value; + } + + } + + @Dependent + @Priority(1) + @Decorator + static class ConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + + @Override + public int convert(int value) { + return -1 * delegate.convert(value); + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java index 1658d1474e071..2bdb111422d78 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java @@ -36,12 +36,15 @@ import javax.persistence.criteria.CriteriaUpdate; import javax.persistence.metamodel.Metamodel; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.ResourceReferenceProvider; +import io.quarkus.arc.processor.InjectionPointsTransformer; import io.quarkus.arc.test.ArcTestContainer; public class ResourceInjectionTest { @@ -50,7 +53,26 @@ public class ResourceInjectionTest { public ArcTestContainer container = ArcTestContainer.builder() .beanClasses(EEResourceField.class, JpaClient.class) .resourceReferenceProviders(EntityManagerProvider.class, DummyProvider.class) - .resourceAnnotations(PersistenceContext.class, Dummy.class).build(); + .resourceAnnotations(PersistenceContext.class, Dummy.class) + .injectionPointsTransformers(new InjectionPointsTransformer() { + @Override + public boolean appliesTo(org.jboss.jandex.Type requiredType) { + return requiredType.name().toString().equals(String.class.getName()); + } + + @Override + public void transform(TransformationContext transformationContext) { + if (transformationContext.getAllAnnotations() + .stream() + .anyMatch(it -> it.name().toString().equals(Dummy.class.getName()))) { + // pretend that the injection point has an annotation whose class is missing + transformationContext.transform() + .add(AnnotationInstance.builder(DotName.createSimple("missing.NonNull")).build()) + .done(); + } + } + }) + .build(); @Test public void testInjection() { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java index b6d8a3706d6a8..396fda6f582e3 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java @@ -9,6 +9,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import javax.interceptor.AroundConstruct; +import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; @@ -23,7 +24,7 @@ public class AroundConstructTest { @RegisterExtension public ArcTestContainer container = new ArcTestContainer(MyTransactional.class, SimpleBean.class, - SimpleInterceptor.class, MyDependency.class); + SimpleInterceptor.class, MyDependency.class, SomeAroundInvokeInterceptor.class); public static AtomicBoolean INTERCEPTOR_CALLED = new AtomicBoolean(false); @@ -43,6 +44,12 @@ static class SimpleBean { } + // the method has to remain here in order to trigger subclass creation due to at least one around invoke + // intercepted method + public void ping() { + + } + } @Singleton @@ -74,4 +81,15 @@ void mySuperCoolAroundConstruct(InvocationContext ctx) throws Exception { } } + + @MyTransactional + @Interceptor + public static class SomeAroundInvokeInterceptor { + + @AroundInvoke + public Object someAroundInvoke(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + + } } diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 974a80e21d1dc..9300c293d3741 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -36,7 +36,7 @@ 11 3.0.0-M7 1.6.8 - 3.0.3 + 3.0.4 3.23.1 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index e0ddac0cf4dde..efa73692aeff2 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -42,7 +42,7 @@ 11 5.9.1 3.23.1 - 3.0.3 + 3.0.4 1.4.0.Final 3.5.0.Final 3.0.0-M7 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 943f804deb722..305ffe9fedd9d 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -41,7 +41,7 @@ 11 2.0.2 - 3.0.3 + 3.0.4 1.12.12 5.9.1 3.8.6 diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java index 0a27fbc05fa8c..2a2377cc3e021 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java @@ -522,6 +522,9 @@ public static void encodeResponseHeaders(ResteasyReactiveRequestContext requestC vertxResponse.addResponseHeader(header, (CharSequence) HeaderUtil.headerToString(o)); } } + if (header.equals("Transfer-Encoding")) { // using both headers together is not allowed + vertxResponse.removeResponseHeader("Content-Length"); + } } else { List strValues = new ArrayList<>(entry.getValue().size()); for (Object o : entry.getValue()) { diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/headers/ChunkedHeaderTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/headers/ChunkedHeaderTest.java new file mode 100644 index 0000000000000..3e94842869249 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/headers/ChunkedHeaderTest.java @@ -0,0 +1,40 @@ +package org.jboss.resteasy.reactive.server.vertx.test.headers; + +import static io.restassured.RestAssured.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ChunkedHeaderTest { + + @RegisterExtension + static ResteasyReactiveUnitTest TEST = new ResteasyReactiveUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(TestResource.class)); + + @Test + public void testReturnUni() { + given() + .get("/test/hello") + .then() + .statusCode(200) + .headers("Transfer-Encoding", "chunked") + .headers("Content-Length", is(nullValue())); + } + + @Path("/test") + public static class TestResource { + + @GET + @Path("hello") + public Response hello() { + return Response.ok("hello").header("Transfer-Encoding", "chunked").build(); + } + } +} diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 82d5da087e3e1..2b0113160120c 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -62,7 +62,7 @@ 20 2.11.0 1.13.2 - 3.0.3 + 3.0.4 registry-client