From 191ed919d667bd16ef5e0089852b6d922f06e53a Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Tue, 12 Sep 2023 18:01:09 +0200 Subject: [PATCH 01/38] Document bug in Reactive Extensions of the Oracle JDBC driver See https://github.com/quarkusio/quarkus/issues/35477 (cherry picked from commit df454b68ec73eb102cea9836f5eafb941af6957f) --- docs/src/main/asciidoc/datasource.adoc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index d5afd66cfb894..a87a033b4056d 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -294,6 +294,22 @@ Quarkus offers several reactive clients for a use with reactive datasource. + The installed extension must be consistent with the `quarkus.datasource.db-kind` you define in your datasource configuration. +[WARNING] +==== +The Reactive Oracle datasource depends on the https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/jdbc-reactive-extensions.html[Reactive Extensions] provided by Oracle in its JDBC driver. + +There is a bug in versions 23.2 and 21.11 of the Oracle JDBC driver that prevents from getting any response if: + +* Reactive Extensions are used to execute an update/insert query that ends with an error (e.g. constraint violation), and +* https://vertx.io/docs/vertx-oracle-client/java/#_retrieving_generated_key_values[generated keys retrieval] is enabled. + +It is not known yet when the bug will be fixed. +In the meantime, you can either: + +* change the version of the driver in your project to `com.oracle.database.jdbc:ojdbc11:21.10.0.0`, or +* avoid executing queries with generated keys retrieval (e.g. load sequence value before inserting) +==== + . After adding the driver, configure the connection URL and define a proper size for your connection pool. + [source,properties] From 65387ec3a41c84dceee68f780c4505fc0fa3483e Mon Sep 17 00:00:00 2001 From: Holly Cummins Date: Fri, 25 Aug 2023 18:52:37 +0100 Subject: [PATCH 02/38] Cross-link from "first application" to "second application" guide, and try and sort out title of second application guide. (cherry picked from commit 966c4dfa54e061fa57bf3aa42e5bc12fb36bf907) --- docs/src/main/asciidoc/getting-started-dev-services.adoc | 7 +++++-- docs/src/main/asciidoc/getting-started.adoc | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/getting-started-dev-services.adoc b/docs/src/main/asciidoc/getting-started-dev-services.adoc index e9daadbc4993d..ace01fc77c6dc 100644 --- a/docs/src/main/asciidoc/getting-started-dev-services.adoc +++ b/docs/src/main/asciidoc/getting-started-dev-services.adoc @@ -3,18 +3,21 @@ This document is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// - [id="getting-started-dev-services-tutorial"] = Your second Quarkus application include::_attributes.adoc[] :diataxis-type: tutorial :categories: getting-started, data, core - +:summary: Discover some of the features that make developing with Quarkus a joyful experience. This tutorial shows you how to create an application which writes to and reads from a database. You will use Dev Services, so you will not actually download, configure, or even start the database yourself. You will also use Panache, a layer on top of Hibernate ORM, to make reading and writing data easier. +This guide helps you: + + * Read and write objects to a database + * Develop and test against services with zero configuration == Prerequisites diff --git a/docs/src/main/asciidoc/getting-started.adoc b/docs/src/main/asciidoc/getting-started.adoc index 39c1d9625ded0..f76500792ae0f 100644 --- a/docs/src/main/asciidoc/getting-started.adoc +++ b/docs/src/main/asciidoc/getting-started.adoc @@ -488,8 +488,9 @@ include::{generated-dir}/config/quarkus-info.adoc[opts=optional, leveloffset=+2] This guide covered the creation of an application using Quarkus. However, there is much more. -We recommend continuing the journey with the xref:building-native-image.adoc[building a native executable guide], where you learn about creating a native executable and packaging it in a container. -If you are interested in reactive, we recommend the xref:getting-started-reactive.adoc[Getting Started with Reactive guide], where you can see how to implement reactive applications with Quarkus. +We recommend continuing the journey by creating xref:getting-started-dev-services.adoc[your second Quarkus application], with dev services and persistence. +You can learn about creating a native executable and packaging it in a container with the xref:building-native-image.adoc[building a native executable guide]. +If you are interested in reactive, we recommend the xref:getting-started-reactive.adoc[getting started with reactive guide], where you can see how to implement reactive applications with Quarkus. In addition, the xref:tooling.adoc[tooling guide] document explains how to: From 03b069e57480efcbf4e1b60ccd8426ffe540f7a6 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 20 Sep 2023 10:26:52 +0300 Subject: [PATCH 03/38] Don't use RuntimeDelegate in ResponseHandler This is needed in order to avoid getting CCE when other implementations of Jakarta REST are on the classpath Closes: #36024 (cherry picked from commit c636df69d2456ad5106492e9d38d571294426c99) --- .../reactive/server/handlers/ResponseHandler.java | 6 +++--- .../reactive/server/jaxrs/ResponseBuilderImpl.java | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java index 62b7fc3ae5a49..25116ff3d8f69 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java @@ -146,13 +146,13 @@ public Response get() { if (result instanceof GenericEntity) { GenericEntity genericEntity = (GenericEntity) result; requestContext.setGenericReturnType(genericEntity.getType()); - responseBuilder = (ResponseBuilderImpl) ResponseImpl.ok(genericEntity.getEntity()); + responseBuilder = ResponseBuilderImpl.ok(genericEntity.getEntity()); } else if (result == null) { // FIXME: custom status codes depending on method? - responseBuilder = (ResponseBuilderImpl) ResponseImpl.noContent(); + responseBuilder = ResponseBuilderImpl.noContent(); } else { // FIXME: custom status codes depending on method? - responseBuilder = (ResponseBuilderImpl) ResponseImpl.ok(result); + responseBuilder = ResponseBuilderImpl.ok(result); } if (responseBuilder.getEntity() != null) { EncodedMediaType produces = requestContext.getResponseContentType(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java index 208c1093d4fd8..f1a546ff5c846 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/ResponseBuilderImpl.java @@ -96,19 +96,19 @@ protected AbstractResponseBuilder doClone() { //TODO: add the rest of static methods of Response if we need them - public static Response.ResponseBuilder withStatus(Response.Status status) { - return new ResponseBuilderImpl().status(status); + public static ResponseBuilderImpl withStatus(Response.Status status) { + return (ResponseBuilderImpl) new ResponseBuilderImpl().status(status); } - public static Response.ResponseBuilder ok() { + public static ResponseBuilderImpl ok() { return withStatus(Response.Status.OK); } - public static Response.ResponseBuilder ok(Object entity) { - return ok().entity(entity); + public static ResponseBuilderImpl ok(Object entity) { + return (ResponseBuilderImpl) ok().entity(entity); } - public static Response.ResponseBuilder noContent() { + public static ResponseBuilderImpl noContent() { return withStatus(Response.Status.NO_CONTENT); } } From c98d647b720d0009355e8feb52099bc5055a65e5 Mon Sep 17 00:00:00 2001 From: Alex Martel <13215031+manofthepeace@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:12:42 -0400 Subject: [PATCH 04/38] Fix duration converter with multiple units (cherry picked from commit 5173d04d97acc62d4857eeb6aa6820e746989bee) --- .../runtime/configuration/DurationConverter.java | 2 +- .../configuration/DurationConverterTestCase.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java index 925f26fa23051..f0250bc4f8439 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java @@ -20,7 +20,7 @@ public class DurationConverter implements Converter, Serializable { private static final String PERIOD = "P"; private static final String PERIOD_OF_TIME = "PT"; public static final Pattern DIGITS = Pattern.compile("^[-+]?\\d+$"); - private static final Pattern DIGITS_AND_UNIT = Pattern.compile("^[-+]?\\d+(?:\\.\\d+)?(?i)[hms]$"); + private static final Pattern DIGITS_AND_UNIT = Pattern.compile("^(?:[-+]?\\d+(?:\\.\\d+)?(?i)[hms])+$"); private static final Pattern DAYS = Pattern.compile("^[-+]?\\d+(?i)d$"); private static final Pattern MILLIS = Pattern.compile("^[-+]?\\d+(?i)ms$"); diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/DurationConverterTestCase.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/DurationConverterTestCase.java index a3fa126ff7af8..af048140322ae 100644 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/DurationConverterTestCase.java +++ b/core/runtime/src/test/java/io/quarkus/runtime/configuration/DurationConverterTestCase.java @@ -68,4 +68,18 @@ public void testValueIsInSec() { Duration actualDuration = durationConverter.convert("2s"); assertEquals(expectedDuration, actualDuration); } + + @Test + public void testValuesWithMultipleUnits() { + Duration expectedDuration = Duration.ofSeconds(150); + Duration actualDuration = durationConverter.convert("2m30s"); + assertEquals(expectedDuration, actualDuration); + } + + @Test + public void testValuesWithMultipleUnitsSigned() { + Duration expectedDuration = Duration.ofSeconds(90); + Duration actualDuration = durationConverter.convert("+2m-30s"); + assertEquals(expectedDuration, actualDuration); + } } From 72e5ce77f5218f1a7e1d260a442f736bc0fdef44 Mon Sep 17 00:00:00 2001 From: Ales Justin Date: Wed, 20 Sep 2023 16:21:23 +0200 Subject: [PATCH 05/38] Fix gRPC interceptors lookup in Micrometer binders (cherry picked from commit e8c1ec1864589fcd127854ea8857322505777d7e) --- .../deployment/binder/GrpcBinderProcessor.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/GrpcBinderProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/GrpcBinderProcessor.java index 196701b7b243f..fe5199205e449 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/GrpcBinderProcessor.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/GrpcBinderProcessor.java @@ -3,10 +3,10 @@ import java.util.function.BooleanSupplier; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; -import io.quarkus.micrometer.runtime.MicrometerRecorder; import io.quarkus.micrometer.runtime.config.MicrometerConfig; /** @@ -22,14 +22,12 @@ public class GrpcBinderProcessor { static final String CLIENT_INTERCEPTOR = "io.grpc.ClientInterceptor"; static final String SERVER_INTERCEPTOR = "io.grpc.ServerInterceptor"; - static final Class CLIENT_INTERCEPTOR_CLASS = MicrometerRecorder.getClassForName(CLIENT_INTERCEPTOR); - static final Class SERVER_INTERCEPTOR_CLASS = MicrometerRecorder.getClassForName(SERVER_INTERCEPTOR); - static class GrpcClientSupportEnabled implements BooleanSupplier { MicrometerConfig mConfig; public boolean getAsBoolean() { - return CLIENT_INTERCEPTOR_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.grpcClient); + return QuarkusClassLoader.isClassPresentAtRuntime(CLIENT_INTERCEPTOR) + && mConfig.checkBinderEnabledWithDefault(mConfig.binder.grpcClient); } } @@ -37,12 +35,14 @@ static class GrpcServerSupportEnabled implements BooleanSupplier { MicrometerConfig mConfig; public boolean getAsBoolean() { - return SERVER_INTERCEPTOR_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.grpcServer); + return QuarkusClassLoader.isClassPresentAtRuntime(SERVER_INTERCEPTOR) + && mConfig.checkBinderEnabledWithDefault(mConfig.binder.grpcServer); } } @BuildStep(onlyIf = GrpcClientSupportEnabled.class) - AdditionalBeanBuildItem addGrpcClientMetricInterceptor() { + AdditionalBeanBuildItem addGrpcClientMetricInterceptor(BuildProducer producer) { + producer.produce(new AdditionalIndexedClassesBuildItem(CLIENT_GRPC_METRICS_INTERCEPTOR)); return AdditionalBeanBuildItem.unremovableOf(CLIENT_GRPC_METRICS_INTERCEPTOR); } From 4de8ff27f41d7695e4ebd9f6792b1bee01d4cb6f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 20 Sep 2023 15:59:32 +0300 Subject: [PATCH 06/38] Properly add @CheckReturnValue * We don't add it if it's already there * We don't run the transformation twice Fixes: #35715 (cherry picked from commit 88f0f1c60738cfd0504c6f40a41bc9e10fc76af0) --- ...bernateReactivePanacheKotlinProcessor.java | 22 ------------------- .../PanacheHibernateResourceProcessor.java | 7 ++++-- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/kotlin/deployment/HibernateReactivePanacheKotlinProcessor.java b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/kotlin/deployment/HibernateReactivePanacheKotlinProcessor.java index 6d4adddff02a3..4e5624b3a3b8c 100644 --- a/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/kotlin/deployment/HibernateReactivePanacheKotlinProcessor.java +++ b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/kotlin/deployment/HibernateReactivePanacheKotlinProcessor.java @@ -14,9 +14,6 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import org.jboss.jandex.MethodInfo; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Type; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; @@ -41,18 +38,12 @@ import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; import io.quarkus.panache.common.deployment.TypeBundle; -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; public class HibernateReactivePanacheKotlinProcessor { private static final String META_INF_PANACHE_ARCHIVE_MARKER = "META-INF/panache-archive.marker"; private static final DotName DOTNAME_REACTIVE_SESSION = DotName.createSimple(Mutiny.Session.class.getName()); private static final DotName DOTNAME_ID = DotName.createSimple(Id.class.getName()); - private static final DotName DOTNAME_UNI = DotName.createSimple(Uni.class.getName()); - private static final DotName DOTNAME_MULTI = DotName.createSimple(Multi.class.getName()); - private static final String CHECK_RETURN_VALUE_BINARY_NAME = "io/smallrye/common/annotation/CheckReturnValue"; - private static final String CHECK_RETURN_VALUE_SIGNATURE = "L" + CHECK_RETURN_VALUE_BINARY_NAME + ";"; private static final TypeBundle TYPE_BUNDLE = ReactiveKotlinJpaTypeBundle.BUNDLE; @BuildStep @@ -171,17 +162,4 @@ public ValidationPhaseBuildItem.ValidationErrorBuildItem validate(ValidationPhas } return null; } - - @BuildStep - PanacheMethodCustomizerBuildItem mutinyReturnTypes() { - return new PanacheMethodCustomizerBuildItem(new PanacheMethodCustomizer() { - @Override - public void customize(Type entityClassSignature, MethodInfo method, MethodVisitor mv) { - DotName returnType = method.returnType().name(); - if (returnType.equals(DOTNAME_UNI) || returnType.equals(DOTNAME_MULTI)) { - mv.visitAnnotation(CHECK_RETURN_VALUE_SIGNATURE, true); - } - } - }); - } } diff --git a/extensions/panache/hibernate-reactive-panache/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheHibernateResourceProcessor.java b/extensions/panache/hibernate-reactive-panache/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheHibernateResourceProcessor.java index d320b3eb85e8a..75aa2098338bd 100644 --- a/extensions/panache/hibernate-reactive-panache/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheHibernateResourceProcessor.java +++ b/extensions/panache/hibernate-reactive-panache/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheHibernateResourceProcessor.java @@ -38,6 +38,7 @@ import io.quarkus.panache.common.deployment.PanacheJpaEntityOperationsEnhancer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; +import io.smallrye.common.annotation.CheckReturnValue; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -145,7 +146,8 @@ ValidationPhaseBuildItem.ValidationErrorBuildItem validate(ValidationPhaseBuildI return null; } - private static final String CHECK_RETURN_VALUE_BINARY_NAME = "io/smallrye/common/annotation/CheckReturnValue"; + private static final DotName DOTNAME_CHECK_RETURN_VALUE_CLASS = DotName.createSimple(CheckReturnValue.class); + private static final String CHECK_RETURN_VALUE_BINARY_NAME = CheckReturnValue.class.getName().replace('.', '/'); private static final String CHECK_RETURN_VALUE_SIGNATURE = "L" + CHECK_RETURN_VALUE_BINARY_NAME + ";"; @BuildStep @@ -154,7 +156,8 @@ PanacheMethodCustomizerBuildItem mutinyReturnTypes() { @Override public void customize(Type entityClassSignature, MethodInfo method, MethodVisitor mv) { DotName returnType = method.returnType().name(); - if (returnType.equals(DOTNAME_UNI) || returnType.equals(DOTNAME_MULTI)) { + if ((returnType.equals(DOTNAME_UNI) || returnType.equals(DOTNAME_MULTI)) + && !method.hasDeclaredAnnotation(DOTNAME_CHECK_RETURN_VALUE_CLASS)) { mv.visitAnnotation(CHECK_RETURN_VALUE_SIGNATURE, true); } } From 104cb79e77144ea2b4d0cd665e68634ff07a9ece Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 20 Sep 2023 21:47:01 +1000 Subject: [PATCH 07/38] Fix Dev UI Info where Full Git details Signed-off-by: Phillip Kruger (cherry picked from commit f8affcd268f8abc26067fb07937e38b43f4f3d24) --- .../src/main/resources/dev-ui/qwc-info.js | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js index 3cd1f9e32c2d2..5960477bd1aa8 100644 --- a/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js +++ b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js @@ -1,4 +1,5 @@ import { LitElement, html, css} from 'lit'; +import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import { columnBodyRenderer } from '@vaadin/grid/lit.js'; import { infoUrl } from 'build-time-data'; import '@vaadin/progress-bar'; @@ -22,13 +23,20 @@ export class QwcInfo extends LitElement { } .cardContent { display: flex; - align-items: center; padding: 10px; gap: 10px; + height: 100%; } vaadin-icon { font-size: xx-large; } + .table { + height: fit-content; + } + .row-header { + color: var(--lumo-contrast-50pct); + vertical-align: top; + } `; static properties = { @@ -58,8 +66,8 @@ export class QwcInfo extends LitElement { return html` ${this._renderOsInfo(this._info)} ${this._renderJavaInfo(this._info)} - ${this._renderGitInfo(this._info)} ${this._renderBuildInfo(this._info)} + ${this._renderGitInfo(this._info)} `; }else{ return html` @@ -78,9 +86,9 @@ export class QwcInfo extends LitElement {
${this._renderOsIcon(os.name)} - - - + + +
Name${os.name}
Version${os.version}
Arch${os.arch}
Name${os.name}
Version${os.version}
Arch${os.arch}
`; @@ -94,7 +102,7 @@ export class QwcInfo extends LitElement {
- +
Version${java.version}
Version${java.version}
`; @@ -121,25 +129,45 @@ export class QwcInfo extends LitElement {
- - - + + + + ${this._renderOptionalData(git)}
Branch${git.branch}
Commit${git.commit.id}
Time${git.commit.time}
Branch${git.branch}
Commit Id ${this._renderCommitId(git)}
Commit Time${git.commit.time}
`; } } + _renderCommitId(git){ + if(typeof git.commit.id === "string"){ + return html`${git.commit.id}`; + }else { + return html`${git.commit.id.full}`; + } + } + + _renderOptionalData(git){ + if(typeof git.commit.id !== "string"){ + return html`Commit User${git.commit.user.name} <${git.commit.user.email}> + Commit Message${unsafeHTML(this._replaceNewLine(git.commit.id.message.full))}` + } + } + + _replaceNewLine(line){ + return line.replace(new RegExp('\r?\n','g'), '
'); + } + _renderBuildInfo(info){ if(info.build){ let build = info.build; return html`
- - - - + + + +
Group${build.group}
Artifact${build.artifact}
Version${build.version}
Time${build.time}
Group${build.group}
Artifact${build.artifact}
Version${build.version}
Time${build.time}
`; From 6d0cf45bfa651de2b94ed4b0226b29f1972d40e0 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 21 Sep 2023 11:50:03 +0100 Subject: [PATCH 08/38] Make OIDC Google example more complete (cherry picked from commit e5718d355ecfc2b5eba05e762f0b73fde4c1a067) --- .../oidc-google-authorized-redirects.png | Bin 0 -> 74456 bytes .../images/oidc-google-test-users.png | Bin 0 -> 62860 bytes .../security-openid-connect-providers.adoc | 58 ++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 docs/src/main/asciidoc/images/oidc-google-authorized-redirects.png create mode 100644 docs/src/main/asciidoc/images/oidc-google-test-users.png diff --git a/docs/src/main/asciidoc/images/oidc-google-authorized-redirects.png b/docs/src/main/asciidoc/images/oidc-google-authorized-redirects.png new file mode 100644 index 0000000000000000000000000000000000000000..844b400d5641cc9589e5a301a12fdcd2b800f7ff GIT binary patch literal 74456 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 literal 0 HcmV?d00001 diff --git a/docs/src/main/asciidoc/security-openid-connect-providers.adoc b/docs/src/main/asciidoc/security-openid-connect-providers.adoc index 2f9e5dac0e7d6..8b7b24c82ea5a 100644 --- a/docs/src/main/asciidoc/security-openid-connect-providers.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-providers.adoc @@ -542,10 +542,68 @@ Finally, you need to configure the Google Calendar address and request the Goo [source,properties] ---- +quarkus.oidc.provider=google +quarkus.oidc.client-id= +quarkus.oidc.credentials.secret= + +# Add a required calendar scope quarkus.oidc.authentication.extra-params.scope=https://www.googleapis.com/auth/calendar + +# Point REST client to Google Calendar endpoint quarkus.rest-client.google-calendar-api.url=https://www.googleapis.com/calendar/v3 ---- +Now you are ready to have users authenticated with Google and support updating their `Google` calendars on their behalf, for example: + +[source,java] +---- +package org.acme.calendar; + +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +import io.quarkus.oidc.IdToken; +import io.quarkus.security.Authenticated; +import io.smallrye.mutiny.Uni; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +@Path("/calendar") +@Authenticated +public class CalendarService { + + @Inject + @IdToken + JsonWebToken jwt; + + @Inject + @RestClient + GoogleCalendarClient calendarClient; + + @GET + @Path("/event") + @Produces("text/plain") + public Uni get() { + return calendarClient.addEvent(new Event()).onItem() + .transform(c -> ("Hello " + jwt.getName() + ", new event: " + c)); + } +} +---- + +You must update the application registered with the <> provider to list `http://localhost:8080/calendar/event` as one of the authorized redirect URIs if you would like to test this endpoint on the local host, for example: + +image::oidc-google-authorized-redirects.png[role="thumb"] + +You might also have to register one or more test users: + +image::oidc-google-test-users.png[role="thumb"] + +Follow the same approach if the endpoint must access other Google services. + +The pattern of authenticating with a given provider, where the endpoint uses either an ID token or UserInfo (especially if an OAuth2-only provider such as `GitHub` is used) to get some information about the currently authenticated user and using an access token to access some downstream services (provider or application specific ones) on behalf of this user can be universally applied, irrespectively of which provider is used to secure the application. + == HTTPS Redirect URL Some providers will only accept HTTPS-based redirect URLs. Tools such as https://ngrok.com/[ngrok] https://linuxhint.com/set-up-use-ngrok/[can be set up] to help testing such providers with Quarkus endpoints running on localhost in devmode. From 82f30fd7c943348c9e355af937de7cc1370ee32f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 21 Sep 2023 14:58:03 +0300 Subject: [PATCH 09/38] Don't register subresource for reflection based on their use as a return type Fixes: #36050 (cherry picked from commit 5c37b57c31cf878a6d91a64c30a7cc95c6f5400b) --- .../deployment/ResteasyReactiveProcessor.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) 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 63a1959712a48..c52f28315e392 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 @@ -523,17 +523,19 @@ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { .constructors(false).methods().build()); } - reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem.Builder() - .type(method.returnType()) - .index(index) - .ignoreTypePredicate( - QuarkusResteasyReactiveDotNames.IGNORE_TYPE_FOR_REFLECTION_PREDICATE) - .ignoreFieldPredicate( - QuarkusResteasyReactiveDotNames.IGNORE_FIELD_FOR_REFLECTION_PREDICATE) - .ignoreMethodPredicate( - QuarkusResteasyReactiveDotNames.IGNORE_METHOD_FOR_REFLECTION_PREDICATE) - .source(source) - .build()); + if (!result.getPossibleSubResources().containsKey(method.returnType().name())) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem.Builder() + .type(method.returnType()) + .index(index) + .ignoreTypePredicate( + QuarkusResteasyReactiveDotNames.IGNORE_TYPE_FOR_REFLECTION_PREDICATE) + .ignoreFieldPredicate( + QuarkusResteasyReactiveDotNames.IGNORE_FIELD_FOR_REFLECTION_PREDICATE) + .ignoreMethodPredicate( + QuarkusResteasyReactiveDotNames.IGNORE_METHOD_FOR_REFLECTION_PREDICATE) + .source(source) + .build()); + } boolean paramsRequireReflection = false; for (short i = 0; i < method.parametersCount(); i++) { From 207c82f5070d959bffc77a19354318041f6b645f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 22 Sep 2023 09:35:17 +0200 Subject: [PATCH 10/38] Fix build failure when using generic embeddable types (cherry picked from commit c5b004d89349cf02fae0386ba85974e626cc4fb7) --- .../orm/deployment/JpaJandexScavenger.java | 72 +++-- ...teEntityEnhancerPresentEmbeddableTest.java | 293 ++++++++++++++++++ 2 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/enhancer/HibernateEntityEnhancerPresentEmbeddableTest.java diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java index 225b2a406491e..198fd9a5ac411 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java @@ -59,8 +59,7 @@ */ public final class JpaJandexScavenger { - public static final List EMBEDDED_ANNOTATIONS = Arrays.asList(ClassNames.EMBEDDED_ID, ClassNames.EMBEDDED, - ClassNames.ELEMENT_COLLECTION); + public static final List EMBEDDED_ANNOTATIONS = Arrays.asList(ClassNames.EMBEDDED_ID, ClassNames.EMBEDDED); private static final String XML_MAPPING_DEFAULT_ORM_XML = "META-INF/orm.xml"; private static final String XML_MAPPING_NO_FILE = "no-file"; @@ -313,17 +312,15 @@ private void enlistEmbeddedsAndElementCollections(Collector collector) throws Bu Set embeddedTypes = new HashSet<>(); for (DotName embeddedAnnotation : EMBEDDED_ANNOTATIONS) { - Collection annotations = index.getAnnotations(embeddedAnnotation); - - for (AnnotationInstance annotation : annotations) { + for (AnnotationInstance annotation : index.getAnnotations(embeddedAnnotation)) { AnnotationTarget target = annotation.target(); switch (target.kind()) { case FIELD: - collectEmbeddedTypes(embeddedAnnotation, embeddedTypes, target.asField().type()); + collectEmbeddedType(embeddedTypes, target.asField().type(), true); break; case METHOD: - collectEmbeddedTypes(embeddedAnnotation, embeddedTypes, target.asMethod().returnType()); + collectEmbeddedType(embeddedTypes, target.asMethod().returnType(), true); break; default: throw new IllegalStateException( @@ -333,6 +330,23 @@ private void enlistEmbeddedsAndElementCollections(Collector collector) throws Bu } } + for (AnnotationInstance annotation : index.getAnnotations(ClassNames.ELEMENT_COLLECTION)) { + AnnotationTarget target = annotation.target(); + + switch (target.kind()) { + case FIELD: + collectElementCollectionTypes(embeddedTypes, target.asField().type()); + break; + case METHOD: + collectElementCollectionTypes(embeddedTypes, target.asMethod().returnType()); + break; + default: + throw new IllegalStateException( + "[internal error] " + ClassNames.ELEMENT_COLLECTION + " placed on a unknown element: " + target); + } + + } + for (DotName embeddedType : embeddedTypes) { addClassHierarchyToReflectiveList(collector, embeddedType); } @@ -481,22 +495,44 @@ private static void collectModelType(Collector collector, ClassInfo modelClass) } } - private void collectEmbeddedTypes(DotName embeddedAnnotation, Set embeddedTypes, Type indexType) + private void collectEmbeddedType(Set embeddedTypes, Type embeddedType, boolean validate) + throws BuildException { + DotName className; + switch (embeddedType.kind()) { + case CLASS: + className = embeddedType.asClassType().name(); + break; + case PARAMETERIZED_TYPE: + className = embeddedType.name(); + break; + default: + // do nothing + return; + } + if (validate && !index.getClassByName(className).hasAnnotation(ClassNames.EMBEDDABLE)) { + throw new BuildException( + className + " is used as an embeddable but does not have an @Embeddable annotation."); + } + embeddedTypes.add(embeddedType.name()); + } + + private void collectElementCollectionTypes(Set embeddedTypes, Type indexType) throws BuildException { switch (indexType.kind()) { case CLASS: - DotName className = indexType.asClassType().name(); - validateEmbeddable(embeddedAnnotation, className); - embeddedTypes.add(className); + // Raw collection type, nothing we can do break; case PARAMETERIZED_TYPE: embeddedTypes.add(indexType.name()); - for (Type typeArgument : indexType.asParameterizedType().arguments()) { - collectEmbeddedTypes(embeddedAnnotation, embeddedTypes, typeArgument); + var typeArguments = indexType.asParameterizedType().arguments(); + for (Type typeArgument : typeArguments) { + // We don't validate @Embeddable annotations on element collections at the moment + // See https://github.com/quarkusio/quarkus/pull/35822 + collectEmbeddedType(embeddedTypes, typeArgument, false); } break; case ARRAY: - collectEmbeddedTypes(embeddedAnnotation, embeddedTypes, indexType.asArrayType().constituent()); + collectEmbeddedType(embeddedTypes, indexType.asArrayType().constituent(), true); break; default: // do nothing @@ -504,14 +540,6 @@ private void collectEmbeddedTypes(DotName embeddedAnnotation, Set embed } } - private void validateEmbeddable(DotName embeddedAnnotation, DotName className) throws BuildException { - if ((ClassNames.EMBEDDED.equals(embeddedAnnotation) || ClassNames.EMBEDDED_ID.equals(embeddedAnnotation)) - && !index.getClassByName(className).hasAnnotation(ClassNames.EMBEDDABLE)) { - throw new BuildException( - className + " is used as an embeddable but does not have an @Embeddable annotation."); - } - } - private static boolean isIgnored(DotName classDotName) { String className = classDotName.toString(); if (className.startsWith("java.util.") || className.startsWith("java.lang.") diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/enhancer/HibernateEntityEnhancerPresentEmbeddableTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/enhancer/HibernateEntityEnhancerPresentEmbeddableTest.java new file mode 100644 index 0000000000000..75093df783891 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/enhancer/HibernateEntityEnhancerPresentEmbeddableTest.java @@ -0,0 +1,293 @@ +package io.quarkus.hibernate.orm.enhancer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import jakarta.inject.Inject; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OrderColumn; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.hibernate.annotations.SortNatural; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.TransactionTestUtils; +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Checks that the missing @Embeddable check doesn't mistakely report + * types that are annotated with @Embeddable (https://github.com/quarkusio/quarkus/issues/35598) + * or generic type parameters on @Embedded field types (https://github.com/quarkusio/quarkus/issues/36065) + */ +public class HibernateEntityEnhancerPresentEmbeddableTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(TransactionTestUtils.class) + .addClasses(EntityWithEmbedded.class, EmbeddableWithAnnotation.class, + ExtendedEmbeddableWithAnnotation.class, + NestingEmbeddableWithAnnotation.class, + GenericEmbeddableWithAnnotation.class)) + .withConfigurationResource("application.properties") + .overrideConfigKey("quarkus.hibernate-orm.implicit-naming-strategy", "component-path"); + + @Inject + EntityManager em; + + // Just test that the generic embeddeds work correctly over a persist/retrieve cycle + @Test + public void smokeTest() { + Long id = QuarkusTransaction.requiringNew().call(() -> { + EntityWithEmbedded entity = new EntityWithEmbedded(); + entity.setName("name"); + entity.setEmbeddedWithAnnotation(new EmbeddableWithAnnotation("simple")); + entity.setExtendedEmbeddedWithAnnotation(new ExtendedEmbeddableWithAnnotation("extended", 42)); + var nesting = new NestingEmbeddableWithAnnotation("nesting"); + entity.setNestingEmbeddedWithAnnotation(nesting); + nesting.setEmbedded(new EmbeddableWithAnnotation("nested")); + entity.setGenericEmbeddedWithAnnotation(new GenericEmbeddableWithAnnotation<>("generic")); + entity.setEmbeddableListWithAnnotation(List.of( + new EmbeddableWithAnnotation("list1"), + new EmbeddableWithAnnotation("list2"))); + entity.setEmbeddableMapValueWithAnnotation(new TreeMap<>(Map.of( + "first", new EmbeddableWithAnnotation("map1"), + "second", new EmbeddableWithAnnotation("map2")))); + em.persist(entity); + return entity.getId(); + }); + + QuarkusTransaction.requiringNew().run(() -> { + EntityWithEmbedded entity = em.find(EntityWithEmbedded.class, id); + assertThat(entity).extracting(e -> e.getName()) + .isEqualTo("name"); + assertThat(entity).extracting(e -> e.getEmbeddedWithAnnotation().getText()) + .isEqualTo("simple"); + assertThat(entity).extracting(e -> e.getExtendedEmbeddedWithAnnotation().getText()) + .isEqualTo("extended"); + assertThat(entity).extracting(e -> e.getExtendedEmbeddedWithAnnotation().getInteger()) + .isEqualTo(42); + assertThat(entity).extracting(e -> e.getNestingEmbeddedWithAnnotation().getText()) + .isEqualTo("nesting"); + assertThat(entity).extracting(e -> e.getNestingEmbeddedWithAnnotation().getEmbedded().getText()) + .isEqualTo("nested"); + assertThat(entity).extracting(e -> e.getGenericEmbeddedWithAnnotation().getValue()) + .isEqualTo("generic"); + assertThat(entity).extracting(e -> e.getEmbeddableListWithAnnotation()) + .asInstanceOf(InstanceOfAssertFactories.list(EmbeddableWithAnnotation.class)) + .extracting(EmbeddableWithAnnotation::getText) + .containsExactly("list1", "list2"); + assertThat(entity).extracting(e -> e.getEmbeddableMapValueWithAnnotation()) + .asInstanceOf(InstanceOfAssertFactories.map(String.class, EmbeddableWithAnnotation.class)) + .extractingFromEntries(e -> e.getValue().getText()) + .containsExactly("map1", "map2"); + }); + } + + @Entity + public static class EntityWithEmbedded { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @Embedded + private EmbeddableWithAnnotation embeddedWithAnnotation; + + @Embedded + private ExtendedEmbeddableWithAnnotation extendedEmbeddedWithAnnotation; + + @Embedded + private NestingEmbeddableWithAnnotation nestingEmbeddedWithAnnotation; + + @Embedded + private GenericEmbeddableWithAnnotation genericEmbeddedWithAnnotation; + + @ElementCollection + @OrderColumn + private List embeddableListWithAnnotation; + + @ElementCollection + @SortNatural + private SortedMap embeddableMapValueWithAnnotation; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public EmbeddableWithAnnotation getEmbeddedWithAnnotation() { + return embeddedWithAnnotation; + } + + public void setEmbeddedWithAnnotation(EmbeddableWithAnnotation embeddedWithAnnotation) { + this.embeddedWithAnnotation = embeddedWithAnnotation; + } + + public ExtendedEmbeddableWithAnnotation getExtendedEmbeddedWithAnnotation() { + return extendedEmbeddedWithAnnotation; + } + + public void setExtendedEmbeddedWithAnnotation(ExtendedEmbeddableWithAnnotation extendedEmbeddedWithAnnotation) { + this.extendedEmbeddedWithAnnotation = extendedEmbeddedWithAnnotation; + } + + public NestingEmbeddableWithAnnotation getNestingEmbeddedWithAnnotation() { + return nestingEmbeddedWithAnnotation; + } + + public void setNestingEmbeddedWithAnnotation(NestingEmbeddableWithAnnotation nestingEmbeddedWithAnnotation) { + this.nestingEmbeddedWithAnnotation = nestingEmbeddedWithAnnotation; + } + + public GenericEmbeddableWithAnnotation getGenericEmbeddedWithAnnotation() { + return genericEmbeddedWithAnnotation; + } + + public void setGenericEmbeddedWithAnnotation(GenericEmbeddableWithAnnotation genericEmbeddedWithAnnotation) { + this.genericEmbeddedWithAnnotation = genericEmbeddedWithAnnotation; + } + + public List getEmbeddableListWithAnnotation() { + return embeddableListWithAnnotation; + } + + public void setEmbeddableListWithAnnotation(List embeddableListWithAnnotation) { + this.embeddableListWithAnnotation = embeddableListWithAnnotation; + } + + public Map getEmbeddableMapValueWithAnnotation() { + return embeddableMapValueWithAnnotation; + } + + public void setEmbeddableMapValueWithAnnotation( + SortedMap embeddableMapValueWithAnnotation) { + this.embeddableMapValueWithAnnotation = embeddableMapValueWithAnnotation; + } + } + + @Embeddable + @MappedSuperclass + public static class EmbeddableWithAnnotation { + private String text; + + protected EmbeddableWithAnnotation() { + // For Hibernate ORM only - it will change the property value through reflection + } + + public EmbeddableWithAnnotation(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + @Embeddable + public static class NestingEmbeddableWithAnnotation { + private String text; + + @Embedded + private EmbeddableWithAnnotation embedded; + + protected NestingEmbeddableWithAnnotation() { + // For Hibernate ORM only - it will change the property value through reflection + } + + public NestingEmbeddableWithAnnotation(String text) { + this.text = text; + this.embedded = new EmbeddableWithAnnotation(text); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public EmbeddableWithAnnotation getEmbedded() { + return embedded; + } + + public void setEmbedded(EmbeddableWithAnnotation embedded) { + this.embedded = embedded; + } + } + + @Embeddable + public static class ExtendedEmbeddableWithAnnotation extends EmbeddableWithAnnotation { + private Integer integer; + + protected ExtendedEmbeddableWithAnnotation() { + // For Hibernate ORM only - it will change the property value through reflection + } + + public ExtendedEmbeddableWithAnnotation(String text, Integer integer) { + super(text); + this.integer = integer; + } + + public Integer getInteger() { + return integer; + } + + public void setInteger(Integer integer) { + this.integer = integer; + } + } + + @Embeddable + public static class GenericEmbeddableWithAnnotation { + private T value; + + protected GenericEmbeddableWithAnnotation() { + // For Hibernate ORM only - it will change the property value through reflection + } + + public GenericEmbeddableWithAnnotation(T value) { + this.value = value; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + } + +} From cfcc5895bd58cfc01ab806ddd8b81df365bed2f3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 21 Sep 2023 10:11:00 +0200 Subject: [PATCH 11/38] Declare Java 21 as a LTS version (cherry picked from commit 1876869c1046b119a53002e85608844da87090ca) --- .../src/main/java/io/quarkus/devtools/project/JavaVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java index edff9ab037a05..7652b34063ba2 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java @@ -54,7 +54,7 @@ public String toString() { } // ordering is important here, so let's keep them ordered - public static final SortedSet JAVA_VERSIONS_LTS = new TreeSet<>(List.of(11, 17)); + public static final SortedSet JAVA_VERSIONS_LTS = new TreeSet<>(List.of(11, 17, 21)); public static final int DEFAULT_JAVA_VERSION = 11; public static final int MAX_LTS_SUPPORTED_BY_KOTLIN = 17; public static final String DETECT_JAVA_RUNTIME_VERSION = "<>"; From 9387ddd55c34d128bb4cc33ad1d4b553f3337ddf Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Thu, 21 Sep 2023 11:35:09 +0300 Subject: [PATCH 12/38] Fix version after which org.graalvm.nativeimage needs to be exported (cherry picked from commit 2fdf6d59981fbebb5d50170d0c1458e9831e09bd) --- .../nativeimage/JPMSExportBuildItem.java | 24 ++++++++++++------- .../steps/NativeImageFeatureStep.java | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JPMSExportBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JPMSExportBuildItem.java index 126304bc05ac7..0c41ac1223e65 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JPMSExportBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JPMSExportBuildItem.java @@ -12,22 +12,30 @@ public final class JPMSExportBuildItem extends MultiBuildItem { private final String moduleName; private final String packageName; - private final GraalVM.Version exportAfter; + private final GraalVM.Version exportSince; private final GraalVM.Version exportBefore; public JPMSExportBuildItem(String moduleName, String packageName) { this(moduleName, packageName, null, null); } - public JPMSExportBuildItem(String moduleName, String packageName, GraalVM.Version exportAfter) { - this(moduleName, packageName, exportAfter, null); + public JPMSExportBuildItem(String moduleName, String packageName, GraalVM.Version exportSince) { + this(moduleName, packageName, exportSince, null); } - public JPMSExportBuildItem(String moduleName, String packageName, GraalVM.Version exportAfter, + /** + * Creates a build item that indicates that a Java package should be exported for a specific GraalVM version range. + * + * @param moduleName the module name + * @param packageName the package name + * @param exportSince the version of GraalVM since which the package should be exported (inclusive) + * @param exportBefore the version of GraalVM before which the package should be exported (exclusive) + */ + public JPMSExportBuildItem(String moduleName, String packageName, GraalVM.Version exportSince, GraalVM.Version exportBefore) { this.moduleName = moduleName; this.packageName = packageName; - this.exportAfter = exportAfter; + this.exportSince = exportSince; this.exportBefore = exportBefore; } @@ -56,8 +64,8 @@ public int hashCode() { return Objects.hash(moduleName, packageName); } - public GraalVM.Version getExportAfter() { - return exportAfter; + public GraalVM.Version getExportSince() { + return exportSince; } public GraalVM.Version getExportBefore() { @@ -65,7 +73,7 @@ public GraalVM.Version getExportBefore() { } public boolean isRequired(GraalVM.Version current) { - return (exportAfter == null || current.compareTo(exportAfter) > 0) && + return (exportSince == null || current.compareTo(exportSince) >= 0) && (exportBefore == null || current.compareTo(exportBefore) < 0); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java index bf8db1fa84953..9eb7e45c3a085 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java @@ -53,7 +53,7 @@ void addExportsToNativeImage(BuildProducer features) { features.produce(new JPMSExportBuildItem("org.graalvm.sdk", "org.graalvm.nativeimage.impl", null, GraalVM.Version.VERSION_23_1_0)); features.produce(new JPMSExportBuildItem("org.graalvm.nativeimage", "org.graalvm.nativeimage.impl", - GraalVM.Version.VERSION_23_0_0)); + GraalVM.Version.VERSION_23_1_0)); } @BuildStep From d107d967e88976e7e47416ec8cf8cdba998d3749 Mon Sep 17 00:00:00 2001 From: Marco Bungart Date: Mon, 26 Jun 2023 02:18:34 +0200 Subject: [PATCH 13/38] In AWT processor, set up build runner before using it (cherry picked from commit d7eda21c04c0a496835cc94d8a498ba99d324720) --- .../io/quarkus/awt/deployment/AwtProcessor.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java b/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java index 46f4b9b7807f5..3a796840a990c 100644 --- a/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java +++ b/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import io.quarkus.awt.runtime.graal.DarwinAwtFeature; @@ -21,6 +22,8 @@ import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem; import io.quarkus.deployment.builditem.nativeimage.UnsupportedOSBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageRunnerBuildItem; +import io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled; +import io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabledBuildItem; import io.quarkus.deployment.pkg.steps.GraalVM; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; @@ -95,7 +98,11 @@ ReflectiveClassBuildItem setupReflectionClassesWithMethods() { void setupAWTInit(BuildProducer jc, BuildProducer jm, BuildProducer jf, - NativeImageRunnerBuildItem nativeImageRunnerBuildItem) { + NativeImageRunnerBuildItem nativeImageRunnerBuildItem, + Optional processInheritIODisabled, + Optional processInheritIODisabledBuildItem) { + nativeImageRunnerBuildItem.getBuildRunner() + .setup(processInheritIODisabled.isPresent() || processInheritIODisabledBuildItem.isPresent()); final GraalVM.Version v = nativeImageRunnerBuildItem.getBuildRunner().getGraalVMVersion(); // Dynamically loading shared objects instead // of baking in static libs: https://github.com/oracle/graal/issues/4921 @@ -118,7 +125,11 @@ void setupAWTInit(BuildProducer jc, } @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) - JniRuntimeAccessBuildItem setupJava2DClasses(NativeImageRunnerBuildItem nativeImageRunnerBuildItem) { + JniRuntimeAccessBuildItem setupJava2DClasses(NativeImageRunnerBuildItem nativeImageRunnerBuildItem, + Optional processInheritIODisabled, + Optional processInheritIODisabledBuildItem) { + nativeImageRunnerBuildItem.getBuildRunner() + .setup(processInheritIODisabled.isPresent() || processInheritIODisabledBuildItem.isPresent()); final GraalVM.Version v = nativeImageRunnerBuildItem.getBuildRunner().getGraalVMVersion(); final List classes = new ArrayList<>(); classes.add("com.sun.imageio.plugins.jpeg.JPEGImageReader"); From 23ef54d117d64a9598c80d368c9dfdb369b1f9cd Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Sat, 23 Sep 2023 13:14:41 +0300 Subject: [PATCH 14/38] fix: init Job inherits ServiceAccount (cherry picked from commit e9b8a5356aacaa1b779892c40e00d25dd0d0b098) --- .../deployment/KubernetesCommonHelper.java | 16 ++++ .../KubernetesWithFlywayInitBase.java | 90 +++++++++++++++++++ .../KubernetesWithFlywayInitTest.java | 81 +---------------- 3 files changed, 108 insertions(+), 79 deletions(-) create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitBase.java diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 511a1d5f8f1e8..9e3aacf668b85 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -699,6 +699,13 @@ public static List createInitJobDecorators(String target, St .map(Optional::get) .collect(Collectors.toList()); + List serviceAccountDecorators = decorators.stream() + .filter(d -> d.getGroup() == null || d.getGroup().equals(target)) + .map(d -> d.getDecorator(ApplyServiceAccountNameDecorator.class)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + items.stream().filter(item -> item.getTarget() == null || item.getTarget().equals(target)).forEach(item -> { for (final AddImagePullSecretDecorator delegate : imagePullSecretDecorators) { @@ -710,6 +717,15 @@ public void andThenVisit(PodSpecBuilder builder, ObjectMeta meta) { })); } + for (final ApplyServiceAccountNameDecorator delegate : serviceAccountDecorators) { + result.add(new DecoratorBuildItem(target, new NamedResourceDecorator("Job", item.getName()) { + @Override + public void andThenVisit(PodSpecBuilder builder, ObjectMeta meta) { + delegate.andThenVisit(builder, meta); + } + })); + } + result.add(new DecoratorBuildItem(target, new NamedResourceDecorator("Job", item.getName()) { @Override public void andThenVisit(ContainerBuilder builder, ObjectMeta meta) { diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitBase.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitBase.java new file mode 100644 index 0000000000000..cdf9968b0341b --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitBase.java @@ -0,0 +1,90 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.batch.v1.Job; +import io.fabric8.kubernetes.api.model.rbac.RoleBinding; + +public class KubernetesWithFlywayInitBase { + + public void assertGeneratedResources(Path kubernetesDir, String name, String imagePullSecret, String serviceAccount) + throws IOException { + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml")); + List kubernetesList = DeserializationUtil.deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + + Optional deployment = kubernetesList.stream() + .filter(d -> "Deployment".equals(d.getKind()) + && name.equals(d.getMetadata().getName())) + .map(d -> (Deployment) d).findAny(); + + assertTrue(deployment.isPresent()); + assertThat(deployment).satisfies(j -> j.isPresent()); + assertThat(deployment.get()).satisfies(d -> { + assertThat(d.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo(name); + }); + + assertThat(d.getSpec()).satisfies(deploymentSpec -> { + assertThat(deploymentSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getImagePullSecrets()).singleElement() + .satisfies(s -> assertThat(s.getName()).isEqualTo(imagePullSecret)); + assertThat(podSpec.getServiceAccountName()).isEqualTo(serviceAccount); + assertThat(podSpec.getInitContainers()).singleElement().satisfies(container -> { + assertThat(container.getName()).isEqualTo("init"); + assertThat(container.getImage()).isEqualTo("groundnuty/k8s-wait-for:no-root-v1.7"); + }); + + }); + }); + }); + }); + + Optional job = kubernetesList.stream() + .filter(j -> "Job".equals(j.getKind()) && (name + "-flyway-init").equals(j.getMetadata().getName())) + .map(j -> (Job) j) + .findAny(); + assertTrue(job.isPresent()); + + assertThat(job.get()).satisfies(j -> { + assertThat(j.getSpec()).satisfies(jobSpec -> { + assertThat(jobSpec.getCompletionMode()).isEqualTo("NonIndexed"); + assertThat(jobSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getImagePullSecrets()).singleElement() + .satisfies(s -> assertThat(s.getName()).isEqualTo(imagePullSecret)); + assertThat(podSpec.getServiceAccountName()).isEqualTo(serviceAccount); + assertThat(podSpec.getRestartPolicy()).isEqualTo("OnFailure"); + assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { + assertThat(container.getName()).isEqualTo(name + "-flyway-init"); + assertThat(container.getEnv()).filteredOn(env -> "QUARKUS_FLYWAY_ENABLED".equals(env.getName())) + .singleElement().satisfies(env -> { + assertThat(env.getValue()).isEqualTo("true"); + }); + assertThat(container.getEnv()) + .filteredOn(env -> "QUARKUS_INIT_AND_EXIT".equals(env.getName())).singleElement() + .satisfies(env -> { + assertThat(env.getValue()).isEqualTo("true"); + }); + }); + }); + }); + }); + }); + + Optional roleBinding = kubernetesList.stream().filter( + r -> r instanceof RoleBinding && (name + "-view-jobs").equals(r.getMetadata().getName())) + .map(r -> (RoleBinding) r).findFirst(); + assertTrue(roleBinding.isPresent()); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java index fb7fe0aba6d61..06607fd7469be 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java @@ -1,30 +1,21 @@ package io.quarkus.it.kubernetes; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; -import java.util.List; -import java.util.Optional; 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.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.batch.v1.Job; -import io.fabric8.kubernetes.api.model.rbac.RoleBinding; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.builder.Version; import io.quarkus.test.ProdBuildResults; import io.quarkus.test.ProdModeTestResults; import io.quarkus.test.QuarkusProdModeTest; -public class KubernetesWithFlywayInitTest { +public class KubernetesWithFlywayInitTest extends KubernetesWithFlywayInitBase { private static final String NAME = "kubernetes-with-flyway"; private static final String IMAGE_PULL_SECRET = "my-pull-secret"; @@ -46,74 +37,6 @@ public class KubernetesWithFlywayInitTest { @Test public void assertGeneratedResources() throws IOException { final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); - assertThat(kubernetesDir) - .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json")) - .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml")); - List kubernetesList = DeserializationUtil - .deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); - - Optional deployment = kubernetesList.stream() - .filter(d -> "Deployment".equals(d.getKind()) - && NAME.equals(d.getMetadata().getName())) - .map(d -> (Deployment) d).findAny(); - - assertTrue(deployment.isPresent()); - assertThat(deployment).satisfies(j -> j.isPresent()); - assertThat(deployment.get()).satisfies(d -> { - assertThat(d.getMetadata()).satisfies(m -> { - assertThat(m.getName()).isEqualTo(NAME); - }); - - assertThat(d.getSpec()).satisfies(deploymentSpec -> { - assertThat(deploymentSpec.getTemplate()).satisfies(t -> { - assertThat(t.getSpec()).satisfies(podSpec -> { - assertThat(podSpec.getImagePullSecrets()).singleElement() - .satisfies(s -> assertThat(s.getName()).isEqualTo(IMAGE_PULL_SECRET)); - assertThat(podSpec.getServiceAccountName()).isEqualTo(NAME); - assertThat(podSpec.getInitContainers()).singleElement().satisfies(container -> { - assertThat(container.getName()).isEqualTo("init"); - assertThat(container.getImage()).isEqualTo("groundnuty/k8s-wait-for:no-root-v1.7"); - }); - - }); - }); - }); - }); - - Optional job = kubernetesList.stream() - .filter(j -> "Job".equals(j.getKind()) && (NAME + "-flyway-init").equals(j.getMetadata().getName())) - .map(j -> (Job) j) - .findAny(); - assertTrue(job.isPresent()); - - assertThat(job.get()).satisfies(j -> { - assertThat(j.getSpec()).satisfies(jobSpec -> { - assertThat(jobSpec.getCompletionMode()).isEqualTo("NonIndexed"); - assertThat(jobSpec.getTemplate()).satisfies(t -> { - assertThat(t.getSpec()).satisfies(podSpec -> { - assertThat(podSpec.getImagePullSecrets()).singleElement() - .satisfies(s -> assertThat(s.getName()).isEqualTo(IMAGE_PULL_SECRET)); - assertThat(podSpec.getRestartPolicy()).isEqualTo("OnFailure"); - assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { - assertThat(container.getName()).isEqualTo(NAME + "-flyway-init"); - assertThat(container.getEnv()).filteredOn(env -> "QUARKUS_FLYWAY_ENABLED".equals(env.getName())) - .singleElement().satisfies(env -> { - assertThat(env.getValue()).isEqualTo("true"); - }); - assertThat(container.getEnv()) - .filteredOn(env -> "QUARKUS_INIT_AND_EXIT".equals(env.getName())).singleElement() - .satisfies(env -> { - assertThat(env.getValue()).isEqualTo("true"); - }); - }); - }); - }); - }); - }); - - Optional roleBinding = kubernetesList.stream().filter( - r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName())) - .map(r -> (RoleBinding) r).findFirst(); - assertTrue(roleBinding.isPresent()); + assertGeneratedResources(kubernetesDir, NAME, IMAGE_PULL_SECRET, NAME); } } From e2d6900ec0854b3b1f8563b7c69a14d48de63873 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Sat, 23 Sep 2023 13:59:35 +0300 Subject: [PATCH 15/38] fix: user provided service account always takes precedence (cherry picked from commit 5c92840287e7929f4fa741bc3567e9e15f326e07) --- .../deployment/KubernetesCommonHelper.java | 9 +++- ...hFlywayIntAndCustomServiceAccountTest.java | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayIntAndCustomServiceAccountTest.java diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 9e3aacf668b85..c294cbbf16f5d 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -354,8 +354,7 @@ private static Collection createRbacDecorators(String name, } } - // Add service account from extensions: use the one provided by the user always - Optional effectiveServiceAccount = config.getServiceAccount(); + Optional effectiveServiceAccount = Optional.empty(); String effectiveServiceAccountNamespace = null; for (KubernetesServiceAccountBuildItem sa : serviceAccountsFromExtensions) { String saName = Optional.ofNullable(sa.getName()).orElse(name); @@ -382,6 +381,12 @@ private static Collection createRbacDecorators(String name, } } + // The user provided service account should always take precedence + if (config.getServiceAccount().isPresent()) { + effectiveServiceAccount = config.getServiceAccount(); + effectiveServiceAccountNamespace = null; + } + // Prepare default configuration String defaultRoleName = null; boolean defaultClusterWide = false; diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayIntAndCustomServiceAccountTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayIntAndCustomServiceAccountTest.java new file mode 100644 index 0000000000000..34197f6b040c1 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayIntAndCustomServiceAccountTest.java @@ -0,0 +1,44 @@ +package io.quarkus.it.kubernetes; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +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.bootstrap.model.AppArtifact; +import io.quarkus.builder.Version; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KubernetesWithFlywayIntAndCustomServiceAccountTest extends KubernetesWithFlywayInitBase { + + private static final String NAME = "kubernetes-with-flyway"; + private static final String IMAGE_PULL_SECRET = "my-pull-secret"; + private static final String SERVICE_ACCOUNT = "my-service-account"; + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) + .setApplicationName(NAME) + .setApplicationVersion("0.1-SNAPSHOT") + .setLogFileName("k8s.log") + .overrideConfigKey("quarkus.kubernetes.image-pull-secrets", IMAGE_PULL_SECRET) + .overrideConfigKey("quarkus.kubernetes.service-account", SERVICE_ACCOUNT) + .setForcedDependencies(Arrays.asList( + new AppArtifact("io.quarkus", "quarkus-kubernetes", Version.getVersion()), + new AppArtifact("io.quarkus", "quarkus-flyway", Version.getVersion()))); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertGeneratedResources(kubernetesDir, NAME, IMAGE_PULL_SECRET, SERVICE_ACCOUNT); + } +} From 8eb0b33f9dc68a571d4bdb735ab221ee3f748386 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 22 Sep 2023 18:19:41 +0200 Subject: [PATCH 16/38] Fault Tolerance: fix duplicate circuit breaker name detection Previously, duplicate circuit breaker names were detected in a crude way: find all occurences of `@CircuitBreakerName` in the Jandex index, collect the values, and fail of some value is present more than once. With this commit, circuit breaker names are collected in a more precise way: only from beans, and only from methods that are actually detected as methods with fault tolerance annotations. This even correctly includes transformed annotations. (cherry picked from commit a043ac4112723f361dfc694f5c74d0a3f148ac4b) --- .../deployment/FaultToleranceScanner.java | 2 +- .../SmallRyeFaultToleranceProcessor.java | 18 +++++------- .../noduplicate/CircuitBreakerService1.java | 16 ++++++++++ .../noduplicate/CircuitBreakerService2.java | 14 +++++++++ .../NoDuplicateCircuitBreakerNameTest.java | 29 +++++++++++++++++++ 5 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService1.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService2.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/NoDuplicateCircuitBreakerNameTest.java diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java index 943d1725f9028..bca9ef3b1e576 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java @@ -80,7 +80,7 @@ boolean hasFTAnnotations(ClassInfo clazz) { void forEachMethod(ClassInfo clazz, Consumer action) { for (MethodInfo method : clazz.methods()) { if (method.name().startsWith("<")) { - // constructors (or static init blocks) can't be intercepted + // constructors and static inititalizers can't be intercepted continue; } if (method.isSynthetic()) { diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index 805878809ebce..345c8808dc85f 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -24,7 +24,6 @@ import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; @@ -279,6 +278,7 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder, List ftMethods = new ArrayList<>(); List exceptions = new ArrayList<>(); + Map> existingCircuitBreakerNames = new HashMap<>(); for (BeanInfo info : validationPhase.getContext().beans()) { ClassInfo beanClass = info.getImplClazz(); @@ -319,6 +319,12 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder, reflectiveClass.produce(ReflectiveClassBuildItem.builder(exceptionNames.get()).build()); } } + + if (annotationStore.hasAnnotation(method, DotNames.CIRCUIT_BREAKER_NAME)) { + AnnotationInstance ann = annotationStore.getAnnotation(method, DotNames.CIRCUIT_BREAKER_NAME); + existingCircuitBreakerNames.computeIfAbsent(ann.value().asString(), ignored -> new HashSet<>()) + .add(method + " @ " + method.declaringClass()); + } } }); @@ -337,16 +343,6 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder, recorder.createFaultToleranceOperation(ftMethods); - // since annotation transformations are applied lazily, we can't know - // all transformed `@CircuitBreakerName`s and have to rely on Jandex here - Map> existingCircuitBreakerNames = new HashMap<>(); - for (AnnotationInstance it : index.getAnnotations(DotNames.CIRCUIT_BREAKER_NAME)) { - if (it.target().kind() == Kind.METHOD) { - MethodInfo method = it.target().asMethod(); - existingCircuitBreakerNames.computeIfAbsent(it.value().asString(), ignored -> new HashSet<>()) - .add(method + " @ " + method.declaringClass()); - } - } for (Map.Entry> entry : existingCircuitBreakerNames.entrySet()) { if (entry.getValue().size() > 1) { exceptions.add(new DefinitionException("Multiple circuit breakers have the same name '" diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService1.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService1.java new file mode 100644 index 0000000000000..78431399ba5ca --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService1.java @@ -0,0 +1,16 @@ +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.noduplicate; + +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; + +import io.smallrye.faulttolerance.api.CircuitBreakerName; + +@Singleton +public class CircuitBreakerService1 { + @CircuitBreaker + @CircuitBreakerName("hello") + public String hello() { + return "1"; + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService2.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService2.java new file mode 100644 index 0000000000000..5a0bdd28ffdc6 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/CircuitBreakerService2.java @@ -0,0 +1,14 @@ +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.noduplicate; + +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; + +import io.smallrye.faulttolerance.api.CircuitBreakerName; + +public class CircuitBreakerService2 { + // this class is not a bean, so there's no circuit breaker and hence no duplicate circuit breaker name + @CircuitBreaker + @CircuitBreakerName("hello") + public String hello() { + return "2"; + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/NoDuplicateCircuitBreakerNameTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/NoDuplicateCircuitBreakerNameTest.java new file mode 100644 index 0000000000000..f513b7cbdbd0c --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/noduplicate/NoDuplicateCircuitBreakerNameTest.java @@ -0,0 +1,29 @@ +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.noduplicate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.CircuitBreakerState; + +public class NoDuplicateCircuitBreakerNameTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(CircuitBreakerService1.class, CircuitBreakerService2.class)); + + @Inject + CircuitBreakerMaintenance cb; + + @Test + public void deploysWithoutError() { + assertNotNull(cb); + assertEquals(CircuitBreakerState.CLOSED, cb.currentState("hello")); + } +} From a3e75d9f30a1443b1836b7577bc2638ca0306e7b Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 22 Sep 2023 18:31:59 +0200 Subject: [PATCH 17/38] Fault Tolerance: fix misleading package names in the extension tests (cherry picked from commit 5db67ac9f1eb491d4f63f66eeaeb44f0863a25a8) --- .../{inheritance => duplicate}/CircuitBreakerService1.java | 2 +- .../{inheritance => duplicate}/CircuitBreakerService2.java | 2 +- .../DuplicateCircuitBreakerNameTest.java | 2 +- .../CircuitBreakerNameInheritanceTest.java | 2 +- .../{duplicate => inheritance}/SubCircuitBreakerService.java | 2 +- .../{duplicate => inheritance}/SuperCircuitBreakerService.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/{inheritance => duplicate}/CircuitBreakerService1.java (94%) rename extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/{inheritance => duplicate}/CircuitBreakerService2.java (94%) rename extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/{inheritance => duplicate}/DuplicateCircuitBreakerNameTest.java (97%) rename extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/{duplicate => inheritance}/CircuitBreakerNameInheritanceTest.java (96%) rename extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/{duplicate => inheritance}/SubCircuitBreakerService.java (93%) rename extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/{duplicate => inheritance}/SuperCircuitBreakerService.java (94%) diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerService1.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerService1.java similarity index 94% rename from extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerService1.java rename to extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerService1.java index 687f50da09b0b..884c337c7ef26 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerService1.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerService1.java @@ -1,4 +1,4 @@ -package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.inheritance; +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.duplicate; import jakarta.inject.Singleton; diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerService2.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerService2.java similarity index 94% rename from extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerService2.java rename to extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerService2.java index 94d7be6f34af0..6aa742b9b324d 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerService2.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerService2.java @@ -1,4 +1,4 @@ -package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.inheritance; +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.duplicate; import jakarta.inject.Singleton; diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/DuplicateCircuitBreakerNameTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/DuplicateCircuitBreakerNameTest.java similarity index 97% rename from extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/DuplicateCircuitBreakerNameTest.java rename to extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/DuplicateCircuitBreakerNameTest.java index c5bb50252ed96..bbc57be5a8726 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/DuplicateCircuitBreakerNameTest.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/DuplicateCircuitBreakerNameTest.java @@ -1,4 +1,4 @@ -package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.inheritance; +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.duplicate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerNameInheritanceTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerNameInheritanceTest.java similarity index 96% rename from extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerNameInheritanceTest.java rename to extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerNameInheritanceTest.java index 160e0d396ba18..d5fdf1a4a724e 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/CircuitBreakerNameInheritanceTest.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/CircuitBreakerNameInheritanceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.duplicate; +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.inheritance; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/SubCircuitBreakerService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/SubCircuitBreakerService.java similarity index 93% rename from extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/SubCircuitBreakerService.java rename to extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/SubCircuitBreakerService.java index b2f26bbc454a1..49e1c9c9bc742 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/SubCircuitBreakerService.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/SubCircuitBreakerService.java @@ -1,4 +1,4 @@ -package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.duplicate; +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.inheritance; import jakarta.inject.Singleton; diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/SuperCircuitBreakerService.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/SuperCircuitBreakerService.java similarity index 94% rename from extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/SuperCircuitBreakerService.java rename to extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/SuperCircuitBreakerService.java index 0a87d76f87b20..685b09931e0aa 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/duplicate/SuperCircuitBreakerService.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/maintenance/inheritance/SuperCircuitBreakerService.java @@ -1,4 +1,4 @@ -package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.duplicate; +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker.maintenance.inheritance; import jakarta.inject.Singleton; From 7c54d76fac0ea6e2c76a933c93749f908067c787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferraz=20Campos=20Florentino?= Date: Mon, 25 Sep 2023 12:38:30 -0300 Subject: [PATCH 18/38] Fix Authorization of Web Endpoints link Fix Authorization of Web Endpoints link (cherry picked from commit e4c9dc8f28ceccc387b5cb5a47e30e09d71cd2b3) --- docs/src/main/asciidoc/smallrye-graphql.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/smallrye-graphql.adoc b/docs/src/main/asciidoc/smallrye-graphql.adoc index 296dd9e9760ad..15b65f2d3f095 100644 --- a/docs/src/main/asciidoc/smallrye-graphql.adoc +++ b/docs/src/main/asciidoc/smallrye-graphql.adoc @@ -289,7 +289,7 @@ The GraphQL UI can be accessed from http://localhost:8080/q/graphql-ui/ . image:graphql-ui-screenshot01.png[alt=GraphQL UI] -Have a look at the link:security-authorization[Authorization of Web Endpoints] Guide on how to add/remove security for the GraphQL UI. +Have a look at the link:security-authorize-web-endpoints-reference[Authorization of Web Endpoints] Guide on how to add/remove security for the GraphQL UI. == Query the GraphQL API From c56a24c7b230a6dbedb1151c57bef030b63a823d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 25 Sep 2023 10:39:49 +0300 Subject: [PATCH 19/38] Properly cache ContextResolver usage for ObjectMapper in server code (cherry picked from commit 7b1221ea59089c4fdf3ebbd3a876f0ca487148b5) --- .../test/CustomObjectMapperTest.java | 100 ++++++++++++++++-- ...eaturedServerJacksonMessageBodyReader.java | 16 ++- ...eaturedServerJacksonMessageBodyWriter.java | 13 ++- 3 files changed, 115 insertions(+), 14 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomObjectMapperTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomObjectMapperTest.java index 99e66a254a9fa..a3d5c8a84382e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomObjectMapperTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomObjectMapperTest.java @@ -1,11 +1,14 @@ package io.quarkus.resteasy.reactive.jackson.deployment.test; import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; import static org.hamcrest.CoreMatchers.equalTo; import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.MediaType; @@ -18,6 +21,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import io.quarkus.arc.Unremovable; import io.quarkus.test.QuarkusUnitTest; @@ -32,26 +36,99 @@ public class CustomObjectMapperTest { * `objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);` */ @Test - void serverShouldUnwrapRootElement() { - given().body("{\"Request\":{\"value\":\"good\"}}") + void test() { + given().body("{\"Request\":{\"value\":\"FIRST\"}}") .contentType(ContentType.JSON) - .post("/server") + .post("/server/dummy") .then() .statusCode(HttpStatus.SC_OK) - .body(equalTo("good")); + .body(equalTo("0")); + + // ContextResolver was invoked for both reader and writer + when().get("/server/count") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("2")); + + given().body("{\"Request2\":{\"value\":\"FIRST\"}}") + .contentType(ContentType.JSON) + .post("/server/dummy2") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("0")); + + // ContextResolver was invoked for both reader and writer because different types where used + when().get("/server/count") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("4")); + + given().body("{\"Request\":{\"value\":\"FIRST\"}}") + .contentType(ContentType.JSON) + .post("/server/dummy") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("0")); + + // ContextResolver was not invoked because the types have already been cached + when().get("/server/count") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("4")); + + given().body("{\"Request2\":{\"value\":\"FIRST\"}}") + .contentType(ContentType.JSON) + .post("/server/dummy2") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("0")); + + // ContextResolver was not invoked because the types have already been cached + when().get("/server/count") + .then() + .statusCode(HttpStatus.SC_OK) + .body(equalTo("4")); + } + + private static void doTest() { + } @Path("/server") public static class MyResource { @POST @Consumes(MediaType.APPLICATION_JSON) - public String post(Request request) { - return request.value; + @Path("dummy") + public Dummy dummy(Request request) { + return Dummy.valueOf(request.value); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("dummy2") + public Dummy2 dummy2(Request2 request) { + return Dummy2.valueOf(request.value); + } + + @GET + @Path("count") + public long count() { + return CustomObjectMapperContextResolver.COUNT.get(); } } + public enum Dummy { + FIRST, + SECOND + } + + public enum Dummy2 { + FIRST, + SECOND + } + public static class Request { - private String value; + protected String value; public Request() { @@ -85,14 +162,21 @@ public int hashCode() { } } + public static class Request2 extends Request { + } + @Provider @Unremovable public static class CustomObjectMapperContextResolver implements ContextResolver { + static final AtomicLong COUNT = new AtomicLong(); + @Override public ObjectMapper getContext(final Class type) { + COUNT.incrementAndGet(); final ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE); + objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE) + .enable(SerializationFeature.WRITE_ENUMS_USING_INDEX); return objectMapper; } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java index 89dbe47c6d36a..863d2b396f877 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java @@ -43,7 +43,8 @@ public class FullyFeaturedServerJacksonMessageBodyReader extends JacksonBasicMes private final Providers providers; private final ConcurrentMap perMethodReader = new ConcurrentHashMap<>(); private final ConcurrentMap perTypeReader = new ConcurrentHashMap<>(); - private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap, ObjectMapper> contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap objectReaderMap = new ConcurrentHashMap<>(); @Inject public FullyFeaturedServerJacksonMessageBodyReader(ObjectMapper mapper, Providers providers) { @@ -154,7 +155,7 @@ private ObjectReader getEffectiveReader(Class type, Type genericType, Me ObjectReader effectiveReader = defaultReader; if (effectiveMapper != originalMapper) { // Effective reader based on the context - effectiveReader = contextResolverMap.computeIfAbsent(effectiveMapper, new Function<>() { + effectiveReader = objectReaderMap.computeIfAbsent(effectiveMapper, new Function<>() { @Override public ObjectReader apply(ObjectMapper objectMapper) { return objectMapper.reader(); @@ -201,7 +202,16 @@ private ObjectMapper getEffectiveMapper(Class type, MediaType responseMe contextResolver = providers.getContextResolver(ObjectMapper.class, null); } if (contextResolver != null) { - return contextResolver.getContext(type); + var cr = contextResolver; + ObjectMapper result = contextResolverMap.computeIfAbsent(type, new Function<>() { + @Override + public ObjectMapper apply(Class aClass) { + return cr.getContext(type); + } + }); + if (result != null) { + return result; + } } return originalMapper; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java index 171b6843fa62d..df6601849601f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java @@ -38,7 +38,8 @@ public class FullyFeaturedServerJacksonMessageBodyWriter extends ServerMessageBo private final ObjectWriter defaultWriter; private final ConcurrentMap perMethodWriter = new ConcurrentHashMap<>(); private final ConcurrentMap perTypeWriter = new ConcurrentHashMap<>(); - private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap, ObjectMapper> contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap objectWriterMap = new ConcurrentHashMap<>(); @Inject public FullyFeaturedServerJacksonMessageBodyWriter(ObjectMapper mapper, Providers providers) { @@ -112,7 +113,7 @@ private ObjectWriter getEffectiveWriter(ObjectMapper effectiveMapper) { if (effectiveMapper == originalMapper) { return defaultWriter; } - return contextResolverMap.computeIfAbsent(effectiveMapper, new Function<>() { + return objectWriterMap.computeIfAbsent(effectiveMapper, new Function<>() { @Override public ObjectWriter apply(ObjectMapper objectMapper) { return createDefaultWriter(effectiveMapper); @@ -133,7 +134,13 @@ private ObjectMapper getEffectiveMapper(Object o, ServerRequestContext context) contextResolver = providers.getContextResolver(ObjectMapper.class, null); } if (contextResolver != null) { - ObjectMapper mapperFromContextResolver = contextResolver.getContext(o.getClass()); + var cr = contextResolver; + ObjectMapper mapperFromContextResolver = contextResolverMap.computeIfAbsent(o.getClass(), new Function<>() { + @Override + public ObjectMapper apply(Class aClass) { + return cr.getContext(o.getClass()); + } + }); if (mapperFromContextResolver != null) { effectiveMapper = mapperFromContextResolver; } From 024184e4b1ae452177a5f257b6e2e16192030f49 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 25 Sep 2023 12:28:21 +0300 Subject: [PATCH 20/38] Properly cache ContextResolver usage for ObjectMapper in client code Fixes: #36067 (cherry picked from commit 1c65e20b549925e49b41daeabc155325d6265eb6) --- ...entObjectMapperForClientAndServerTest.java | 34 ++++++++---- .../ClientJacksonMessageBodyReader.java | 38 +++---------- .../ClientJacksonMessageBodyWriter.java | 18 +++---- .../runtime/serialisers/JacksonUtil.java | 53 +++++++++++++++++++ .../runtime/serialisers/ResolverMapKey.java | 53 +++++++++++++++++++ 5 files changed, 143 insertions(+), 53 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/JacksonUtil.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ResolverMapKey.java diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java index dc444fdd5d9ff..90f3de332246c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java @@ -3,13 +3,11 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import jakarta.inject.Singleton; import jakarta.ws.rs.GET; @@ -70,10 +68,17 @@ void serverShouldWrapRootElement() { */ @Test void shouldClientUseCustomObjectMapperUnwrappingRootElement() { - assertFalse(ClientObjectMapperUnwrappingRootElement.USED.get()); + AtomicLong count = ClientObjectMapperUnwrappingRootElement.COUNT; + assertEquals(0, count.get()); Request request = clientUnwrappingRootElement.get(); assertEquals("good", request.value); - assertTrue(ClientObjectMapperUnwrappingRootElement.USED.get()); + assertEquals(1, count.get()); + + assertEquals("good", clientUnwrappingRootElement.get().value); + assertEquals("good", clientUnwrappingRootElement.get().value); + assertEquals("good", clientUnwrappingRootElement.get().value); + // count should not change as the resolution of the ObjectMapper should be cached + assertEquals(1, count.get()); } /** @@ -82,10 +87,17 @@ void shouldClientUseCustomObjectMapperUnwrappingRootElement() { */ @Test void shouldClientUseCustomObjectMapperNotUnwrappingRootElement() { - assertFalse(MyClientNotUnwrappingRootElement.CUSTOM_OBJECT_MAPPER_USED.get()); + AtomicLong count = MyClientNotUnwrappingRootElement.CUSTOM_OBJECT_MAPPER_COUNT; + assertEquals(0, count.get()); Request request = clientNotUnwrappingRootElement.get(); assertNull(request.value); - assertTrue(MyClientNotUnwrappingRootElement.CUSTOM_OBJECT_MAPPER_USED.get()); + assertEquals(1, count.get()); + + assertNull(clientNotUnwrappingRootElement.get().value); + assertNull(clientNotUnwrappingRootElement.get().value); + assertNull(clientNotUnwrappingRootElement.get().value); + // count should not change as the resolution of the ObjectMapper should be cached + assertEquals(1, count.get()); } @Path("/server") @@ -108,14 +120,14 @@ public interface MyClientUnwrappingRootElement { @Path("/server") @Produces(MediaType.APPLICATION_JSON) public interface MyClientNotUnwrappingRootElement { - AtomicBoolean CUSTOM_OBJECT_MAPPER_USED = new AtomicBoolean(false); + AtomicLong CUSTOM_OBJECT_MAPPER_COUNT = new AtomicLong(); @GET Request get(); @ClientObjectMapper static ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) { - CUSTOM_OBJECT_MAPPER_USED.set(true); + CUSTOM_OBJECT_MAPPER_COUNT.incrementAndGet(); return defaultObjectMapper.copy() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .disable(DeserializationFeature.UNWRAP_ROOT_VALUE); @@ -158,11 +170,11 @@ public int hashCode() { } public static class ClientObjectMapperUnwrappingRootElement implements ContextResolver { - static final AtomicBoolean USED = new AtomicBoolean(false); + static final AtomicLong COUNT = new AtomicLong(); @Override public ObjectMapper getContext(Class type) { - USED.set(true); + COUNT.incrementAndGet(); return new ObjectMapper().enable(DeserializationFeature.UNWRAP_ROOT_VALUE); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java index ad7e481b8405b..63c4fb8cec20c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java @@ -1,5 +1,7 @@ package io.quarkus.rest.client.reactive.jackson.runtime.serialisers; +import static io.quarkus.rest.client.reactive.jackson.runtime.serialisers.JacksonUtil.getObjectMapperFromContext; + import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; @@ -13,8 +15,6 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.ext.ContextResolver; -import jakarta.ws.rs.ext.Providers; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.ClientWebApplicationException; @@ -33,7 +33,8 @@ public class ClientJacksonMessageBodyReader extends JacksonBasicMessageBodyReade private static final Logger log = Logger.getLogger(ClientJacksonMessageBodyReader.class); - private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap objectReaderMap = new ConcurrentHashMap<>(); private RestClientRequestContext context; @Inject @@ -66,43 +67,16 @@ public void handle(RestClientRequestContext requestContext) { } private ObjectReader getEffectiveReader(Class type, MediaType responseMediaType) { - ObjectMapper effectiveMapper = getObjectMapperFromContext(type, responseMediaType); + ObjectMapper effectiveMapper = getObjectMapperFromContext(type, responseMediaType, context, contextResolverMap); if (effectiveMapper == null) { return getEffectiveReader(); } - return contextResolverMap.computeIfAbsent(effectiveMapper, new Function<>() { + return objectReaderMap.computeIfAbsent(effectiveMapper, new Function<>() { @Override public ObjectReader apply(ObjectMapper objectMapper) { return objectMapper.reader(); } }); } - - private ObjectMapper getObjectMapperFromContext(Class type, MediaType responseMediaType) { - Providers providers = getProviders(); - if (providers == null) { - return null; - } - - ContextResolver contextResolver = providers.getContextResolver(ObjectMapper.class, - responseMediaType); - if (contextResolver == null) { - // TODO: not sure if this is correct, but Jackson does this as well... - contextResolver = providers.getContextResolver(ObjectMapper.class, null); - } - if (contextResolver != null) { - return contextResolver.getContext(type); - } - - return null; - } - - private Providers getProviders() { - if (context != null && context.getClientRequestContext() != null) { - return context.getClientRequestContext().getProviders(); - } - - return null; - } } diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java index 98a52d39391fe..9c71047af4a28 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java @@ -1,5 +1,6 @@ package io.quarkus.rest.client.reactive.jackson.runtime.serialisers; +import static io.quarkus.rest.client.reactive.jackson.runtime.serialisers.JacksonUtil.getObjectMapperFromContext; import static org.jboss.resteasy.reactive.server.jackson.JacksonMessageBodyWriterUtil.createDefaultWriter; import static org.jboss.resteasy.reactive.server.jackson.JacksonMessageBodyWriterUtil.doLegacyWrite; @@ -27,7 +28,8 @@ public class ClientJacksonMessageBodyWriter implements MessageBodyWriter protected final ObjectMapper originalMapper; protected final ObjectWriter defaultWriter; - private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); + private final ConcurrentMap objectWriterMap = new ConcurrentHashMap<>(); private RestClientRequestContext context; @Inject @@ -44,7 +46,7 @@ public boolean isWriteable(Class type, Type genericType, Annotation[] annotation @Override public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - doLegacyWrite(o, annotations, httpHeaders, entityStream, getEffectiveWriter()); + doLegacyWrite(o, annotations, httpHeaders, entityStream, getEffectiveWriter(type, mediaType)); } @Override @@ -52,22 +54,18 @@ public void handle(RestClientRequestContext requestContext) throws Exception { this.context = requestContext; } - protected ObjectWriter getEffectiveWriter() { - if (context == null) { - // no context injected when writer is not running within a rest client context - return defaultWriter; - } - - ObjectMapper objectMapper = context.getConfiguration().getFromContext(ObjectMapper.class); + protected ObjectWriter getEffectiveWriter(Class type, MediaType responseMediaType) { + ObjectMapper objectMapper = getObjectMapperFromContext(type, responseMediaType, context, contextResolverMap); if (objectMapper == null) { return defaultWriter; } - return contextResolverMap.computeIfAbsent(objectMapper, new Function<>() { + return objectWriterMap.computeIfAbsent(objectMapper, new Function<>() { @Override public ObjectWriter apply(ObjectMapper objectMapper) { return createDefaultWriter(objectMapper); } }); } + } diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/JacksonUtil.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/JacksonUtil.java new file mode 100644 index 0000000000000..bb8c217f4ac35 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/JacksonUtil.java @@ -0,0 +1,53 @@ +package io.quarkus.rest.client.reactive.jackson.runtime.serialisers; + +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.ext.ContextResolver; +import jakarta.ws.rs.ext.Providers; + +import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; + +import com.fasterxml.jackson.databind.ObjectMapper; + +final class JacksonUtil { + + private JacksonUtil() { + } + + static ObjectMapper getObjectMapperFromContext(Class type, MediaType responseMediaType, RestClientRequestContext context, + ConcurrentMap contextResolverMap) { + Providers providers = getProviders(context); + if (providers == null) { + return null; + } + + ContextResolver contextResolver = providers.getContextResolver(ObjectMapper.class, + responseMediaType); + if (contextResolver == null) { + // TODO: not sure if this is correct, but Jackson does this as well... + contextResolver = providers.getContextResolver(ObjectMapper.class, null); + } + if (contextResolver != null) { + var cr = contextResolver; + var key = new ResolverMapKey(type, context.getConfiguration(), context.getInvokedMethod().getDeclaringClass()); + return contextResolverMap.computeIfAbsent(key, new Function<>() { + @Override + public ObjectMapper apply(ResolverMapKey resolverMapKey) { + return cr.getContext(resolverMapKey.getType()); + } + }); + } + + return null; + } + + private static Providers getProviders(RestClientRequestContext context) { + if (context != null && context.getClientRequestContext() != null) { + return context.getClientRequestContext().getProviders(); + } + + return null; + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ResolverMapKey.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ResolverMapKey.java new file mode 100644 index 0000000000000..02b8148f06b40 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ResolverMapKey.java @@ -0,0 +1,53 @@ +package io.quarkus.rest.client.reactive.jackson.runtime.serialisers; + +import java.util.Objects; + +import jakarta.ws.rs.core.Configuration; + +/** + * Each REST Client can potentially have different providers, so we need to make sure that + * caching for one client does not affect caching of another + */ +public final class ResolverMapKey { + + private final Class type; + private final Configuration configuration; + + private final Class restClientClass; + + public ResolverMapKey(Class type, Configuration configuration, Class restClientClass) { + this.type = type; + this.configuration = configuration; + this.restClientClass = restClientClass; + } + + public Class getType() { + return type; + } + + public Configuration getConfiguration() { + return configuration; + } + + public Class getRestClientClass() { + return restClientClass; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ResolverMapKey)) { + return false; + } + ResolverMapKey that = (ResolverMapKey) o; + return Objects.equals(type, that.type) && Objects.equals(configuration, that.configuration) + && Objects.equals(restClientClass, that.restClientClass); + } + + @Override + public int hashCode() { + return Objects.hash(type, configuration, restClientClass); + } +} From 9590cee03239b8fae5885f9a964accc7b4b674f1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 26 Sep 2023 09:02:34 +0300 Subject: [PATCH 21/38] Add note about JDK version in AppCDS doc Relates to: https://github.com/quarkusio/quarkus/issues/32877#issuecomment-1734230601 (cherry picked from commit b815ca5f5eb8a691fd8ac290a48bd3659fe67c5c) --- docs/src/main/asciidoc/appcds.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/main/asciidoc/appcds.adoc b/docs/src/main/asciidoc/appcds.adoc index 25486c848647a..5811801c47bb0 100644 --- a/docs/src/main/asciidoc/appcds.adoc +++ b/docs/src/main/asciidoc/appcds.adoc @@ -87,6 +87,11 @@ This results in an archive generation process that on one hand is completely saf As a result, users are expected to get a slightly more effective archive if they manually go through the hoops of generating the AppCDS archive. ==== +[IMPORTANT] +==== +AppCDS has improved significantly in the latest JDK releases. This means that to ensure the best possible improvements from it, make sure your projects targets the highest possible Java version (ideally 17 or 21). +==== + === Usage in containers When building container images using the `quarkus-container-image-jib` extension, Quarkus automatically takes care of all the steps needed to generate the archive From 269cb6f580de5a14d5d9b3bad737f09b359f2450 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Mon, 25 Sep 2023 09:11:20 +0200 Subject: [PATCH 22/38] Make rest-client invocation context implement ArcInvocationContext (cherry picked from commit 1070e3bc5916b94ec02ec6730aca4a2991500939) --- .../runtime/QuarkusInvocationContextImpl.java | 198 ++++++++++++++++++ .../QuarkusProxyInvocationHandler.java | 26 ++- 2 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusInvocationContextImpl.java diff --git a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusInvocationContextImpl.java b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusInvocationContextImpl.java new file mode 100644 index 0000000000000..6dc5856db69bf --- /dev/null +++ b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusInvocationContextImpl.java @@ -0,0 +1,198 @@ +package io.quarkus.restclient.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletionException; + +import jakarta.enterprise.inject.spi.InterceptionType; +import jakarta.enterprise.inject.spi.Interceptor; +import jakarta.interceptor.InvocationContext; +import jakarta.ws.rs.client.ResponseProcessingException; + +import org.jboss.resteasy.microprofile.client.ExceptionMapping; + +import io.quarkus.arc.ArcInvocationContext; + +/** + * A Quarkus copy of {@link org.jboss.resteasy.microprofile.client.InvocationContextImpl} which makes it implement + * {@link ArcInvocationContext} instead so that it's compatible with Quarkus interceptors. + */ +public class QuarkusInvocationContextImpl implements ArcInvocationContext { + + private final Object target; + + private final Method method; + + private Object[] args; + + private final int position; + + private final Map contextData; + + private final List chain; + + private final Set interceptorBindings; + + public QuarkusInvocationContextImpl(final Object target, final Method method, final Object[] args, + final List chain, Set interceptorBindings) { + this(target, method, args, chain, 0, interceptorBindings); + } + + private QuarkusInvocationContextImpl(final Object target, final Method method, final Object[] args, + final List chain, final int position, + Set interceptorBindings) { + this.target = target; + this.method = method; + this.args = args; + this.interceptorBindings = interceptorBindings == null ? Collections.emptySet() : interceptorBindings; + this.contextData = new HashMap<>(); + // put in bindings under Arc's specific key + this.contextData.put(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings); + this.position = position; + this.chain = chain; + } + + boolean hasNextInterceptor() { + return position < chain.size(); + } + + protected Object invokeNext() throws Exception { + return chain.get(position).invoke(nextContext()); + } + + private InvocationContext nextContext() { + return new QuarkusInvocationContextImpl(target, method, args, chain, position + 1, interceptorBindings); + } + + protected Object interceptorChainCompleted() throws Exception { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof CompletionException) { + cause = cause.getCause(); + } + if (cause instanceof ExceptionMapping.HandlerException) { + ((ExceptionMapping.HandlerException) cause).mapException(method); + } + if (cause instanceof ResponseProcessingException) { + ResponseProcessingException rpe = (ResponseProcessingException) cause; + // Note that the default client engine leverages a single connection + // MP FT: we need to close the response otherwise we would not be able to retry if the method returns jakarta.ws.rs.core.Response + rpe.getResponse().close(); + cause = rpe.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + } + throw e; + } + } + + @Override + public Object proceed() throws Exception { + try { + if (hasNextInterceptor()) { + return invokeNext(); + } else { + return interceptorChainCompleted(); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } + } + + @Override + public Object getTarget() { + return target; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Constructor getConstructor() { + return null; + } + + @Override + public Object[] getParameters() throws IllegalStateException { + return args; + } + + @Override + public void setParameters(Object[] params) throws IllegalStateException, IllegalArgumentException { + this.args = params; + } + + @Override + public Map getContextData() { + return contextData; + } + + @Override + public Object getTimer() { + return null; + } + + @Override + public Set getInterceptorBindings() { + return interceptorBindings; + } + + @Override + public T findIterceptorBinding(Class annotationType) { + for (Annotation annotation : getInterceptorBindings()) { + if (annotation.annotationType().equals(annotationType)) { + return (T) annotation; + } + } + return null; + } + + @Override + public List findIterceptorBindings(Class annotationType) { + List found = new ArrayList<>(); + for (Annotation annotation : getInterceptorBindings()) { + if (annotation.annotationType().equals(annotationType)) { + found.add((T) annotation); + } + } + return found; + } + + public static class InterceptorInvocation { + + @SuppressWarnings("rawtypes") + private final Interceptor interceptor; + + private final Object interceptorInstance; + + public InterceptorInvocation(final Interceptor interceptor, final Object interceptorInstance) { + this.interceptor = interceptor; + this.interceptorInstance = interceptorInstance; + } + + @SuppressWarnings("unchecked") + Object invoke(InvocationContext ctx) throws Exception { + return interceptor.intercept(InterceptionType.AROUND_INVOKE, interceptorInstance, ctx); + } + } +} diff --git a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusProxyInvocationHandler.java b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusProxyInvocationHandler.java index 2eba8ce96542c..022f4b53305b3 100644 --- a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusProxyInvocationHandler.java +++ b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusProxyInvocationHandler.java @@ -30,7 +30,6 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.microprofile.client.ExceptionMapping; -import org.jboss.resteasy.microprofile.client.InvocationContextImpl; import org.jboss.resteasy.microprofile.client.RestClientProxy; import org.jboss.resteasy.microprofile.client.header.ClientHeaderFillingException; @@ -52,7 +51,9 @@ public class QuarkusProxyInvocationHandler implements InvocationHandler { private final Set providerInstances; - private final Map> interceptorChains; + private final Map> interceptorChains; + + private final Map> interceptorBindingsMap; private final ResteasyClient client; @@ -70,10 +71,13 @@ public QuarkusProxyInvocationHandler(final Class restClientInterface, this.closed = new AtomicBoolean(); if (beanManager != null) { this.creationalContext = beanManager.createCreationalContext(null); - this.interceptorChains = initInterceptorChains(beanManager, creationalContext, restClientInterface); + this.interceptorBindingsMap = new HashMap<>(); + this.interceptorChains = initInterceptorChains(beanManager, creationalContext, restClientInterface, + interceptorBindingsMap); } else { this.creationalContext = null; this.interceptorChains = Collections.emptyMap(); + this.interceptorBindingsMap = Collections.emptyMap(); } } @@ -152,10 +156,10 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl args = argsReplacement; } - List chain = interceptorChains.get(method); + List chain = interceptorChains.get(method); if (chain != null) { // Invoke business method interceptors - return new InvocationContextImpl(target, method, args, chain).proceed(); + return new QuarkusInvocationContextImpl(target, method, args, chain, interceptorBindingsMap.get(method)).proceed(); } else { try { return method.invoke(target, args); @@ -245,10 +249,11 @@ private static BeanManager getBeanManager(Class restClientInterface) { } } - private static Map> initInterceptorChains( - BeanManager beanManager, CreationalContext creationalContext, Class restClientInterface) { + private static Map> initInterceptorChains( + BeanManager beanManager, CreationalContext creationalContext, Class restClientInterface, + Map> interceptorBindingsMap) { - Map> chains = new HashMap<>(); + Map> chains = new HashMap<>(); // Interceptor as a key in a map is not entirely correct (custom interceptors) but should work in most cases Map, Object> interceptorInstances = new HashMap<>(); @@ -267,12 +272,13 @@ private static Map> in List> interceptors = beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, interceptorBindings); if (!interceptors.isEmpty()) { - List chain = new ArrayList<>(); + List chain = new ArrayList<>(); for (Interceptor interceptor : interceptors) { - chain.add(new InvocationContextImpl.InterceptorInvocation(interceptor, + chain.add(new QuarkusInvocationContextImpl.InterceptorInvocation(interceptor, interceptorInstances.computeIfAbsent(interceptor, i -> beanManager.getReference(i, i.getBeanClass(), creationalContext)))); } + interceptorBindingsMap.put(method, Set.of(interceptorBindings)); chains.put(method, chain); } } From 5671cbe55af28a02974deb28da7a628e2b39d975 Mon Sep 17 00:00:00 2001 From: brunobat Date: Mon, 25 Sep 2023 11:57:32 +0100 Subject: [PATCH 23/38] test for WithSpan on a rest client (cherry picked from commit f35b85480008f27a617130ae16edb5b9c06ee501) --- .../it/opentelemetry/PingPongResource.java | 12 +++ .../it/opentelemetry/OpenTelemetryTest.java | 74 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java index 4d02a03af17e8..8593eb7981e17 100644 --- a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java +++ b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java @@ -10,6 +10,8 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.inject.RestClient; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; import io.vertx.core.MultiMap; @@ -34,6 +36,11 @@ public interface PingPongRestClient { @GET @Path("/client/pong/{message}") Uni asyncPingpong(@PathParam("message") String message); + + @GET + @Path("/client/pong/{message}") + @WithSpan + String pingpongIntercept(@SpanAttribute(value = "message") @PathParam("message") String message); } @Inject @@ -81,4 +88,9 @@ public Uni asyncPingNamed(@PathParam("message") String message) { .onItemOrFailure().call(httpClient::close); } + @GET + @Path("pong-intercept/{message}") + public String pongIntercept(@PathParam("message") String message) { + return pingRestClient.pingpongIntercept(message); + } } diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java index 1e970fc6a583f..b0a1f43fa33ca 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -557,6 +558,79 @@ void testAsyncClientTracing() { assertNotNull(clientServer.get("attr_user_agent.original")); } + @Test + void testClientTracingWithInterceptor() { + given() + .when().get("/client/pong-intercept/one") + .then() + .statusCode(200) + .body(containsString("one")); + + await().atMost(5, SECONDS).until(() -> getSpans().size() == 4); + List> spans = getSpans(); + assertEquals(4, spans.size()); + assertEquals(1, spans.stream().map(map -> map.get("traceId")).collect(toSet()).size()); + + Map server = getSpanByKindAndParentId(spans, SERVER, "0000000000000000"); + assertEquals(SERVER.toString(), server.get("kind")); + verifyResource(server); + assertEquals("GET /client/pong-intercept/{message}", server.get("name")); + assertEquals(SERVER.toString(), server.get("kind")); + assertTrue((Boolean) server.get("ended")); + assertEquals(SpanId.getInvalid(), server.get("parent_spanId")); + assertEquals(TraceId.getInvalid(), server.get("parent_traceId")); + assertFalse((Boolean) server.get("parent_valid")); + assertFalse((Boolean) server.get("parent_remote")); + assertEquals("GET", server.get("attr_http.method")); + assertEquals("/client/pong-intercept/one", server.get("attr_http.target")); + assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); + assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); + assertEquals("http", server.get("attr_http.scheme")); + assertEquals("/client/pong-intercept/{message}", server.get("attr_http.route")); + assertEquals("200", server.get("attr_http.status_code")); + assertNotNull(server.get("attr_http.client_ip")); + assertNotNull(server.get("attr_user_agent.original")); + + Map fromInterceptor = getSpanByKindAndParentId(spans, INTERNAL, server.get("spanId")); + assertEquals("PingPongRestClient.pingpongIntercept", fromInterceptor.get("name")); + assertEquals(INTERNAL.toString(), fromInterceptor.get("kind")); + assertTrue((Boolean) fromInterceptor.get("ended")); + assertTrue((Boolean) fromInterceptor.get("parent_valid")); + assertFalse((Boolean) fromInterceptor.get("parent_remote")); + assertNull(fromInterceptor.get("attr_http.method")); + assertNull(fromInterceptor.get("attr_http.status_code")); + assertEquals("one", fromInterceptor.get("attr_message")); + + Map client = getSpanByKindAndParentId(spans, CLIENT, fromInterceptor.get("spanId")); + assertEquals("GET", client.get("name")); + assertEquals(SpanKind.CLIENT.toString(), client.get("kind")); + assertTrue((Boolean) client.get("ended")); + assertTrue((Boolean) client.get("parent_valid")); + assertFalse((Boolean) client.get("parent_remote")); + assertEquals("GET", client.get("attr_http.method")); + assertEquals("http://localhost:8081/client/pong/one", client.get("attr_http.url")); + assertEquals("200", client.get("attr_http.status_code")); + + Map clientServer = getSpanByKindAndParentId(spans, SERVER, client.get("spanId")); + assertEquals(SERVER.toString(), clientServer.get("kind")); + verifyResource(clientServer); + assertEquals("GET /client/pong/{message}", clientServer.get("name")); + assertEquals(SERVER.toString(), clientServer.get("kind")); + assertTrue((Boolean) clientServer.get("ended")); + assertTrue((Boolean) clientServer.get("parent_valid")); + assertTrue((Boolean) clientServer.get("parent_remote")); + assertEquals("GET", clientServer.get("attr_http.method")); + assertEquals("/client/pong/one", clientServer.get("attr_http.target")); + assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); + assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); + assertEquals("http", clientServer.get("attr_http.scheme")); + assertEquals("/client/pong/{message}", clientServer.get("attr_http.route")); + assertEquals("200", clientServer.get("attr_http.status_code")); + assertNotNull(clientServer.get("attr_http.client_ip")); + assertNotNull(clientServer.get("attr_user_agent.original")); + assertEquals(clientServer.get("parentSpanId"), client.get("spanId")); + } + @Test void testTemplatedPathOnClass() { given() From 57e72ef362fa4de7f59fba860012a0d1f159f3de Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 26 Sep 2023 13:28:20 +0200 Subject: [PATCH 24/38] fix: Improve handling of broken accept headers in MediaTypeHeaderDelegate.parse(..) within resteasy-reactive Previously, a "broken" MIME-type in an access header could trigger an StringIndexOutOfBoundsException during MediaTypeHeaderDelegate.parse(..) instead of the more suitable IllegalArgumentException. Example: "Accept: x; /x" This PR now throws an IllegalArgumentException in case of a broken MIME-type like in the example. Fixes #36159 (cherry picked from commit 84d6c5db8e67ccb1a342e2517222464a1a89724f) --- .../headers/MediaTypeHeaderDelegate.java | 3 +++ .../headers/MediaTypeHeaderDelegateTest.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegateTest.java diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegate.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegate.java index 88977f12023af..ed26840ce0c67 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegate.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegate.java @@ -80,6 +80,9 @@ private static MediaType internalParse(String type) { } else { major = type.substring(0, typeIndex); if (paramIndex > -1) { + if (typeIndex + 1 > paramIndex) { + throw new IllegalArgumentException("Failed to parse media type " + type); + } subtype = type.substring(typeIndex + 1, paramIndex); } else { subtype = type.substring(typeIndex + 1); diff --git a/independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegateTest.java b/independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegateTest.java new file mode 100644 index 0000000000000..4141af5ff6c26 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/test/java/org/jboss/resteasy/reactive/common/headers/MediaTypeHeaderDelegateTest.java @@ -0,0 +1,21 @@ +package org.jboss.resteasy.reactive.common.headers; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class MediaTypeHeaderDelegateTest { + + public void parsingBrokenMediaTypeShouldThrowIllegalArgumentException_minimized() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + MediaTypeHeaderDelegate.parse("x; /x"); + }); + } + + @Test + public void parsingBrokenMediaTypeShouldThrowIllegalArgumentException_actual() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + MediaTypeHeaderDelegate.parse("() { ::}; echo \"NS:\" $(/bin/sh -c \"expr 123456 - 123456\")"); + }); + } + +} \ No newline at end of file From 95093c1f857cdfd785975721143f957f1f2eadcd Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Fri, 22 Sep 2023 16:34:11 +0300 Subject: [PATCH 25/38] fix: respect user provide description of cli plugin (cherry picked from commit ead521142f9495a4c1344c33332b60a75d387f2b) --- .../src/main/java/io/quarkus/cli/plugin/PluginManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java index e3c4d1dd54e93..41032580ce5e4 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java @@ -97,10 +97,11 @@ public Optional addPlugin(String nameOrLocation, boolean userCatalog, Op Map installablePlugins = state.installablePlugins(); Optional plugin = Optional.ofNullable(installablePlugins.get(name)).map(Plugin::inUserCatalog); return plugin.map(p -> { - PluginCatalog updatedCatalog = state.pluginCatalog(userCatalog).addPlugin(p); + Plugin withDescription = p.withDescription(description); + PluginCatalog updatedCatalog = state.pluginCatalog(userCatalog).addPlugin(withDescription); pluginCatalogService.writeCatalog(updatedCatalog); state.invalidateInstalledPlugins(); - return p; + return withDescription; }); } From 6fd2c46af3febed5db09ce73eeb0d5aae259b9ad Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Fri, 22 Sep 2023 22:13:15 +0300 Subject: [PATCH 26/38] chore: make JBang Catalog calls from CLI not fatal. (cherry picked from commit 1b60619ade595a6640de1f1e693693c116eab5f8) --- .../cli/plugin/JBangCatalogService.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java index c28ce62985983..08cc81e1ce4df 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/JBangCatalogService.java @@ -1,6 +1,7 @@ package io.quarkus.cli.plugin; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,6 +23,7 @@ public class JBangCatalogService extends CatalogService { private final String fallbackCatalog; private final String[] remoteCatalogs; private final JBangSupport jbang; + private final MessageWriter output; public JBangCatalogService(MessageWriter output) { this(output, "quarkus-", "quarkusio"); @@ -38,6 +40,7 @@ public JBangCatalogService(boolean interactiveMode, MessageWriter output, String this.fallbackCatalog = fallbackCatalog; this.remoteCatalogs = remoteCatalogs; this.jbang = new JBangSupport(interactiveMode, output); + this.output = output; } @Override @@ -88,7 +91,12 @@ public JBangCatalog readCombinedCatalog(Optional projectDir, Optional catalogFile = projectDir .map(d -> RELATIVE_PLUGIN_CATALOG.apply(d).toAbsolutePath().toString()); catalogFile.ifPresent(f -> { - List lines = jbang.execute("alias", "list", "-f", f, "--verbose"); + List lines = new ArrayList<>(); + try { + lines.addAll(jbang.execute("alias", "list", "-f", f, "--verbose")); + } catch (Exception e) { + output.debug("Failed to read catalog file: " + f + ". Ignoring."); + } aliases.putAll(readAliases(lines)); }); }); @@ -111,14 +119,25 @@ public JBangCatalog readCombinedCatalog(Optional projectDir, Optional listAliases(JBangSupport jbang, String remoteCatalog) { - List lines = jbang.execute("alias", "list", "--verbose", remoteCatalog); + List lines = new ArrayList<>(); + try { + lines.addAll(jbang.execute("alias", "list", "--verbose", remoteCatalog)); + } catch (Exception e) { + this.output.debug("Failed to list aliases from remote catalog: " + remoteCatalog + ". Ignorning."); + } + return readAliases(lines); } private Map listAliasesOrFallback(JBangSupport jbang, String fallbackCatalog) { - List localCatalogs = jbang.execute("catalog", "list").stream() - .map(l -> l.substring(0, l.indexOf(" "))) - .collect(Collectors.toList()); + List localCatalogs = new ArrayList<>(); + try { + for (String catalog : jbang.execute("catalog", "list")) { + localCatalogs.add(catalog.substring(0, catalog.indexOf(" "))); + } + } catch (Exception e) { + this.output.debug("Failed to list jbang catalogs. Ignoring."); + } //If there are locally installed catalogs, then go through every single one of them //and collect the aliases. From 595267c67bf3e3e075e2a14ae2cbfbdef4d3bd85 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 27 Sep 2023 10:29:23 +1000 Subject: [PATCH 27/38] Dev-UI: Fix Test screen Signed-off-by: Phillip Kruger (cherry picked from commit 1a1438e42f715abc708fb7f75ef48d234dcaa096) --- .../src/main/resources/dev-ui/qwc/qwc-continuous-testing.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js index 86dbf99328516..82c85eb85863b 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-continuous-testing.js @@ -105,7 +105,7 @@ export class QwcContinuousTesting extends QwcHotReloadElement { this._state = JSON.parse(jsonRpcResponse.result); }); this._streamResultsObserver = this.jsonRpc.streamTestResults().onNext(jsonRpcResponse => { - this._results = JSON.parse(jsonRpcResponse.result); + this._results = jsonRpcResponse.result; }); } @@ -121,7 +121,7 @@ export class QwcContinuousTesting extends QwcHotReloadElement { }); this.jsonRpc.lastKnownResults().then(jsonRpcResponse => { if(jsonRpcResponse.result){ - this._results = JSON.parse(jsonRpcResponse.result); + this._results = jsonRpcResponse.result; } }); } From a4f222d1620eea6174c1b8c8a10c5038c94d4b56 Mon Sep 17 00:00:00 2001 From: brunobat Date: Wed, 27 Sep 2023 18:31:49 +0100 Subject: [PATCH 28/38] OTel stable, Jaeger deprecated (cherry picked from commit 77560e1a127f9e067548a24e653b80c51357d620) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 89d19a886def1..4a0f1d0a38eb6 100644 --- a/extensions/jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,6 +9,6 @@ metadata: categories: - "observability" unlisted: true - status: "stable" + status: "deprecated" config: - "quarkus.jaeger." diff --git a/extensions/opentelemetry/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/opentelemetry/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 42767c3d7e5ed..10abb559da59b 100644 --- a/extensions/opentelemetry/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/opentelemetry/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -11,6 +11,6 @@ metadata: guide: "https://quarkus.io/guides/opentelemetry" categories: - "observability" - status: "experimental" + status: "stable" config: - "quarkus.opentelemetry." From 5171969339faff6af62dd34328f416d8fba1ba4b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Sep 2023 18:20:13 +0300 Subject: [PATCH 29/38] Mention RestMulti in error message for dynamic media type in Multi (cherry picked from commit a7a691915d3cf2ee1b2b5af851994f836f38a1a3) --- .../reactive/server/handlers/PublisherResponseHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java index a6aa28fd1e632..4d097feaad874 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java @@ -308,14 +308,14 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti produces = REST_MULTI_DEFAULT_SERVER_MEDIA_TYPE; } else { throw new IllegalStateException( - "Negotiation or dynamic media type not supported yet for Multi: please use the @Produces annotation when returning a Multi"); + "Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'"); } } MediaType[] mediaTypes = produces.getSortedOriginalMediaTypes(); if (mediaTypes.length != 1) { throw new IllegalStateException( - "Negotiation or dynamic media type not supported yet for Multi: please use a single @Produces annotation"); + "Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'"); } MediaType mediaType = mediaTypes[0]; From 168ab6c4a0074920b6df70ea85c7e577006d9e6e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 28 Sep 2023 15:18:54 +0300 Subject: [PATCH 30/38] Fix broken collection assignability check The check that was being done is broken in Java 21 as that version bring SequencedCollection which List now implements Closes: #36170 (cherry picked from commit ffa805a8f6f4716e09b20906fe060f9614364f6f) --- .../JaxrsClientReactiveProcessor.java | 24 +++-------- .../deployment/LinksContainerFactory.java | 13 ++---- .../reactive/common/processor/JandexUtil.java | 40 +++++++++++++++++++ 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 6ee601bf8e874..4cecf9feab2e9 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -7,10 +7,13 @@ import static org.jboss.jandex.Type.Kind.PRIMITIVE; import static org.jboss.resteasy.reactive.client.impl.RestClientRequestContext.DEFAULT_CONTENT_TYPE_PROP; import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.extractProducesConsumesValues; +import static org.jboss.resteasy.reactive.common.processor.JandexUtil.*; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COLLECTION; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COMPLETION_STAGE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.CONSUMES; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.ENCODED; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.FORM_PARAM; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MAP; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OBJECT; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PART_TYPE_NAME; @@ -2744,28 +2747,11 @@ private void addQueryParamToWebTarget(BytecodeCreator creator, ResultHandle para } private boolean isCollection(Type type, IndexView index) { - if (type.kind() == Type.Kind.PRIMITIVE) { - return false; - } - ClassInfo classInfo = index.getClassByName(type.name()); - if (classInfo == null) { - return false; - } - return classInfo.interfaceNames().stream().anyMatch(DotName.createSimple(Collection.class.getName())::equals); + return isAssignableFrom(COLLECTION, type.name(), index); } private boolean isMap(Type type, IndexView index) { - if (type.kind() == Type.Kind.PRIMITIVE) { - return false; - } - ClassInfo classInfo = index.getClassByName(type.name()); - if (classInfo == null) { - return false; - } - if (ResteasyReactiveDotNames.MAP.equals(classInfo.name())) { - return true; - } - return classInfo.interfaceNames().stream().anyMatch(DotName.createSimple(Map.class.getName())::equals); + return isAssignableFrom(MAP, type.name(), index); } private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java index 9c4ddafd41688..2a991448e49df 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java @@ -1,5 +1,7 @@ package io.quarkus.resteasy.reactive.links.deployment; +import static org.jboss.resteasy.reactive.common.processor.JandexUtil.isAssignableFrom; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COLLECTION; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COMPLETABLE_FUTURE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COMPLETION_STAGE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; @@ -17,8 +19,6 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; @@ -138,14 +138,7 @@ private String getAnnotationValue(AnnotationInstance annotationInstance, String } private boolean isCollection(Type type, IndexView index) { - if (type.kind() == Type.Kind.PRIMITIVE) { - return false; - } - ClassInfo classInfo = index.getClassByName(type.name()); - if (classInfo == null) { - return false; - } - return classInfo.interfaceNames().stream().anyMatch(DotName.createSimple(Collection.class.getName())::equals); + return isAssignableFrom(COLLECTION, type.name(), index); } private Type getNonAsyncReturnType(Type returnType) { diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java index 8eb2a5d725bf9..2c8f8c37833b3 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java @@ -5,8 +5,10 @@ import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -415,4 +417,42 @@ public static boolean isImplementorOf(IndexView index, ClassInfo info, DotName n return isImplementorOf(index, superClass, name, additionalIgnoredSuperClasses); } + public static boolean isAssignableFrom(DotName superType, DotName subType, IndexView index) { + // java.lang.Object is assignable from any type + if (superType.equals(DOTNAME_OBJECT)) { + return true; + } + // type1 is the same as type2 + if (superType.equals(subType)) { + return true; + } + // type1 is a superclass + return findSupertypes(subType, index).contains(superType); + } + + private static Set findSupertypes(DotName name, IndexView index) { + Set result = new HashSet<>(); + + Deque workQueue = new ArrayDeque<>(); + workQueue.add(name); + while (!workQueue.isEmpty()) { + DotName type = workQueue.poll(); + if (result.contains(type)) { + continue; + } + result.add(type); + + ClassInfo clazz = index.getClassByName(type); + if (clazz == null) { + continue; + } + if (clazz.superName() != null) { + workQueue.add(clazz.superName()); + } + workQueue.addAll(clazz.interfaceNames()); + } + + return result; + } + } From 55d1cbc551c1117544dfdf35bd03ec5f0a634fb0 Mon Sep 17 00:00:00 2001 From: melloware Date: Sat, 30 Sep 2023 07:21:14 -0400 Subject: [PATCH 31/38] Fix Liquibase on Windows 11 (cherry picked from commit ac93affa9cd468f437feea0365806f18cc5127a9) --- .../SubstituteEnvironmentValueProvider.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java new file mode 100644 index 0000000000000..2fae996137a87 --- /dev/null +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java @@ -0,0 +1,20 @@ +package io.quarkus.liquibase.runtime.graal; + +import java.util.Map; + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "liquibase.configuration.core.EnvironmentValueProvider") +final class SubstituteEnvironmentValueProvider { + + @Delete + private Map environment; + + @Substitute + protected Map getMap() { + return System.getenv(); + } + +} \ No newline at end of file From a8d6bec380469428d47b2cc99b1f41b421169caa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Sep 2023 22:11:00 +0000 Subject: [PATCH 32/38] Bump org.apache.avro:avro from 1.11.2 to 1.11.3 in /bom/application Bumps org.apache.avro:avro from 1.11.2 to 1.11.3. --- updated-dependencies: - dependency-name: org.apache.avro:avro dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit 1ea628a83e6257a6ed051f289679a6f1d9f02797) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 19bca0d7cb51f..409a271d13759 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -204,7 +204,7 @@ 1.1.1.Final 2.20.0 1.3.0.Final - 1.11.2 + 1.11.3 2.4.3.Final 0.1.18.Final 1.18.3 From b56e6d8b24cd46201f53488cd4477eec09b310a2 Mon Sep 17 00:00:00 2001 From: Willem Jan Glerum Date: Tue, 3 Oct 2023 09:07:54 +0200 Subject: [PATCH 33/38] Update transaction.adoc Fix typo in config key (cherry picked from commit 24d96031b8505d40c923b30e8081757670d73fbb) --- docs/src/main/asciidoc/transaction.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/transaction.adoc b/docs/src/main/asciidoc/transaction.adoc index 4bbf0f4281e14..14ab4857d8026 100644 --- a/docs/src/main/asciidoc/transaction.adoc +++ b/docs/src/main/asciidoc/transaction.adoc @@ -369,7 +369,7 @@ In cloud environments where persistent storage is not available, such as when ap IMPORTANT: While there are several benefits to using a database to store transaction logs, you might notice a reduction in performance compared with using the file system to store the logs. -Quarkus allows the following JDBC-specific configuration of the object store included in `quarkus.transacion-manager.object-store.` properties, where can be: +Quarkus allows the following JDBC-specific configuration of the object store included in `quarkus.transaction-manager.object-store.` properties, where can be: * `type` (_string_): Configure this property to `jdbc` to enable usage of a Quarkus JDBC datasource for transaction logging. The default value is `file-system`. From b175aab1525c79540069b1e54321c8307c28a15b Mon Sep 17 00:00:00 2001 From: Bruno Lellis Date: Sun, 1 Oct 2023 22:02:26 -0300 Subject: [PATCH 34/38] fix RunOnVirtualThread typo (cherry picked from commit af980119203ee0b8e73b94194b58b08cf6c3b862) --- docs/src/main/asciidoc/virtual-threads.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/virtual-threads.adoc b/docs/src/main/asciidoc/virtual-threads.adoc index 946c009dbc637..4a91227dc9e5a 100644 --- a/docs/src/main/asciidoc/virtual-threads.adoc +++ b/docs/src/main/asciidoc/virtual-threads.adoc @@ -147,7 +147,7 @@ For each of them, the object stored in the `ThreadLocal` is created (often large This problem leads to high memory usage. Unfortunately, it requires sophisticated code changes in the libraries themselves. -=== Use @RunVirtualThread with RESTEasy Reactive +=== Use @RunOnVirtualThread with RESTEasy Reactive This section shows a brief example of using the link:{runonvthread}[@RunOnVirtualThread] annotation. It also explains the various development and execution models offered by Quarkus. From ab15ea6dca88552972d57d2244ee2f7c948fc9ff Mon Sep 17 00:00:00 2001 From: Bruno Lellis Date: Sun, 1 Oct 2023 22:21:34 -0300 Subject: [PATCH 35/38] fixes @RunOnVirtualThreads typo (cherry picked from commit a5ce58171ff196b7b1f2acb795484aeaf48bd164) --- docs/src/main/asciidoc/virtual-threads.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/virtual-threads.adoc b/docs/src/main/asciidoc/virtual-threads.adoc index 4a91227dc9e5a..70c52f51ef15d 100644 --- a/docs/src/main/asciidoc/virtual-threads.adoc +++ b/docs/src/main/asciidoc/virtual-threads.adoc @@ -375,7 +375,7 @@ mvn package === Using a local GraalVM installation -To compile a Quarkus applications leveraging `@RunOnVirtualThreads` into native executable, you must be sure to use a GraalVM / Mandrel `native-image` supporting virtual threads, so providing at least Java 19+. +To compile a Quarkus applications leveraging `@RunOnVirtualThread` into native executable, you must be sure to use a GraalVM / Mandrel `native-image` supporting virtual threads, so providing at least Java 19+. Then, until Java 21, you need to add the following property to your `application.properties` file: @@ -465,4 +465,4 @@ quarkus.virtual-threads.name-prefix= == Additional references -- https://dl.acm.org/doi/10.1145/3583678.3596895[Considerations for integrating virtual threads in a Java framework: a Quarkus example in a resource-constrained environment] \ No newline at end of file +- https://dl.acm.org/doi/10.1145/3583678.3596895[Considerations for integrating virtual threads in a Java framework: a Quarkus example in a resource-constrained environment] From b789cfe826c8a204d8ea980a11043765d7e0a5c2 Mon Sep 17 00:00:00 2001 From: Bruno Lellis Date: Sun, 1 Oct 2023 22:41:08 -0300 Subject: [PATCH 36/38] fixes why-not inline reference (cherry picked from commit 5eee00cc06e6b85605147056c382f7342d5da0c5) --- docs/src/main/asciidoc/virtual-threads.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/virtual-threads.adoc b/docs/src/main/asciidoc/virtual-threads.adoc index 70c52f51ef15d..fea2c14279868 100644 --- a/docs/src/main/asciidoc/virtual-threads.adoc +++ b/docs/src/main/asciidoc/virtual-threads.adoc @@ -296,7 +296,7 @@ Note that all three models can be used in a single application. == Use virtual thread friendly clients -As mentioned in the href:why-not[Why not run everything on virtual threads?] section, the Java ecosystem is not entirely ready for virtual threads. +As mentioned in the xref:why-not[Why not run everything on virtual threads?] section, the Java ecosystem is not entirely ready for virtual threads. So, you need to be careful, especially when using a libraries doing I/O. Fortunately, Quarkus provides a massive ecosystem that is ready to be used in virtual threads. From cacf752f9793bd3b1af2c7c603fc92107cfa6eab Mon Sep 17 00:00:00 2001 From: Nathan Erwin Date: Sun, 1 Oct 2023 22:10:53 -0400 Subject: [PATCH 37/38] Update dev-ui-v2 documentation * updated some pluralization, verb tense, and spelling in the documentation to help make it easier to read Signed-off-by:Nathan Erwin (cherry picked from commit 5eb142aa4a9c21c6a6bc503c2b3d4cc4d950cc56) --- docs/src/main/asciidoc/dev-ui-v2.adoc | 60 +++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/src/main/asciidoc/dev-ui-v2.adoc b/docs/src/main/asciidoc/dev-ui-v2.adoc index a5ed2d582e335..c13f494b9d681 100644 --- a/docs/src/main/asciidoc/dev-ui-v2.adoc +++ b/docs/src/main/asciidoc/dev-ui-v2.adoc @@ -24,13 +24,13 @@ image::dev-ui-overview-v2.png[alt=Dev UI overview,role="center"] It allows you to: -- quickly visualise all the extensions currently loaded +- quickly visualize all the extensions currently loaded - view extension statuses and go directly to extension documentation - view and change `Configuration` -- manage and visualise `Continuous Testing` +- manage and visualize `Continuous Testing` - view `Dev Services` information - view the Build information -- view and stream various logs. +- view and stream various logs Each extension used in the application will be listed and you can navigate to the guide for each extension, see some more information on the extension, and view configuration applicable for that extension: @@ -42,7 +42,7 @@ In order to make your extension listed in the Dev UI you don't need to do anythi So you can always start with that :) -Extension can: +Extensions can: - <> - <> @@ -60,7 +60,7 @@ A good example of this is the SmallRye OpenAPI extension that contains links to image::dev-ui-extension-openapi-v2.png[alt=Dev UI extension card,role="center"] -The links to these external references is know at build time, so to get links like this on your card, all you need to do is add the following Build Step in your extension: +The links to these external references is known at build time, so to get links like this on your card, all you need to do is add the following Build Step in your extension: [source,java] ---- @@ -87,9 +87,9 @@ public CardPageBuildItem pages(NonApplicationRootPathBuildItem nonApplicationRoo return cardPageBuildItem; } ---- -<1> Always make sure that this build step only run when dev mode +<1> Always make sure that this build step is only run when in dev mode <2> To add anything on the card, you need to return/produce a `CardPageBuildItem`. -<3> To add a link, you can use the `addPage` method, as all links go to a "page". `Page` has got some builders to assist with building a page. For `external` links, use the `externalPageBuilder` +<3> To add a link, you can use the `addPage` method, as all links go to a "page". `Page` has some builders to assist with building a page. For `external` links, use the `externalPageBuilder` <4> Adding the url of the external link (in this case we use `NonApplicationRootPathBuildItem` to create this link, as this link is under the configurable non application path, default `/q`). Always use `NonApplicationRootPathBuildItem` if your link is available under `/q`. <5> You can (optionally) hint the content type of the content you are navigating to. If there is no hint, a header call will be made to determine the `MediaType`; <6> You can add an icon. All free font-awesome icons are available. @@ -101,15 +101,15 @@ If you find your icon at https://fontawesome.com/search?o=r&m=free[Font awesome] ==== Embedding external content -By default, even external links will render inside (embedded) in Dev UI. In the case of HTML the page will be rendered and any other content will be shown using https://codemirror.net/[code-mirror] to markup the media type, for example the open api schema document in `yaml` format: +By default, even external links will render inside (embedded) in Dev UI. In the case of HTML, the page will be rendered and any other content will be shown using https://codemirror.net/[code-mirror] to markup the media type. For example the open api schema document in `yaml` format: image::dev-ui-extension-openapi-embed-v2.png[alt=Dev UI embedded page,role="center"] -If you do not want to embed the content, you can use the `.doNotEmbed()` on the Page Builder, this will then open a new tab. +If you do not want to embed the content, you can use the `.doNotEmbed()` on the Page Builder, this will then open the link in a new tab. ==== Runtime external links -The example above assumes you know the link to use at build time. There might be cases where you only know this at runtime. In that case you can use a <> Method that returns the link to add and use that when creating the link. Rather than using the `.url` method on the page builder, use the `.dynamicUrlJsonRPCMethodName("yourJsonRPCMethodName")`. +The example above assumes you know the link to use at build time. There might be cases where you only know this at runtime. In that case you can use a <> Method that returns the link to add, and use that when creating the link. Rather than using the `.url` method on the page builder, use the `.dynamicUrlJsonRPCMethodName("yourJsonRPCMethodName")`. ==== Adding labels @@ -189,7 +189,7 @@ image::dev-ui-table-page-v2.png[alt=Dev UI table page,role="center"] ==== Qute data -You can also display your build time data using a qute template. All build time data keys is available to use int the template: +You can also display your build time data using a qute template. All build time data keys are available to use in the template: [source,java] ---- @@ -200,7 +200,7 @@ cardPageBuildItem.addPage(Page.quteDataPageBuilder("Qute data") // <1> <1> Use the `quteDataPageBuilder`. <2> Link to the Qute template in `/deployment/src/main/resources/dev-ui/`. -Using any qute template to display the data, example `qute-jokes-template.html`: +Using any Qute template to display the data, for example `qute-jokes-template.html`: [source,html] ---- @@ -227,7 +227,7 @@ Using any qute template to display the data, example `qute-jokes-template.html`: ==== Web Component page -To build an interactive page with actions and runtime (or built time) data, you need to use the web component page: +To build an interactive page with actions and runtime (or build time) data, you need to use the web component page: [source,java] ---- @@ -237,7 +237,7 @@ cardPageBuildItem.addPage(Page.webComponentPageBuilder() // <1> .staticLabel(String.valueOf(beans.size()))); ---- <1> Use the `webComponentPageBuilder`. -<2> Link to the Web Component in `/deployment/src/main/resources/dev-ui/`. The title can also be defined (using `.title("My title")` in the builder), but if not the title will be assumed from the componentLink, that should always have the format `qwc` (stands for Quarkus Web Component) dash `extensionName` (example, `arc` in this case ) dash `page title` ("Beans" in this case) +<2> Link to the Web Component in `/deployment/src/main/resources/dev-ui/`. The title can also be defined (using `.title("My title")` in the builder), but if not the title will be assumed from the componentLink, which should always have the format `qwc` (stands for Quarkus Web Component) dash `extensionName` (example, `arc` in this case ) dash `page title` ("Beans" in this case) Dev UI uses https://lit.dev/[Lit] to make building these web components easier. You can read more about Web Components and Lit: @@ -292,14 +292,14 @@ customElements.define('qwc-arc-beans', QwcArcBeans); // <10> ---- <1> You can import Classes and/or functions from other libraries. -In this case we use `LitElement` class and `html` & `css` functions from `Lit` -<2> Build time data as defined in the Build step can be imported using the key and always from `build-time-data`. All keys added in your Build step will be available. +In this case we use the `LitElement` class and `html` & `css` functions from `Lit` +<2> Build time data as defined in the Build step and can be imported using the key and always from `build-time-data`. All keys added in your Build step will be available. <3> The component should be named in the following format: Qwc (stands for Quarkus Web Component) then Extension Name then Page Title, all concatenated with Camel Case. This will also match the file name format as described earlier. The component should also extend `LitComponent`. <4> CSS styles can be added using the `css` function, and these styles only apply to your component. <5> Styles can reference globally defined CSS variables to make sure your page renders correctly, especially when switching between light and dark mode. You can find all CSS variables in the Vaadin documentation (https://vaadin.com/docs/latest/styling/lumo/lumo-style-properties/color[Color], https://vaadin.com/docs/latest/styling/lumo/lumo-style-properties/size-space[Sizing and Spacing], etc) -<6> Properties can be added. Use `_` in front of a property if that property is private. Properties are usually injected in the HTML template, and can be defined as having state, meaning that if that property changes, the component should re-render. In this case, the beans are Build time data and only change on hot-reload, that we will cover later. +<6> Properties can be added. Use `_` in front of a property if that property is private. Properties are usually injected in the HTML template, and can be defined as having state, meaning that if that property changes, the component should re-render. In this case, the beans are Build time data and only change on hot-reload, which will be covered later. <7> Constructors (optional) should always call `super` first, and then set the default values for the properties. -<8> The render method (comes from `LitElement` will be called to render the page). In this method you return the markup of the page you want. You can use the `html` function from `Lit`, that gives you a template language to output the HTML you want. Once the template is created, you only need to set/change the properties to re-render the page content. Read more about https://lit.dev/docs/components/rendering/[Lit html] +<8> The render method (from `LitElement`) will be called to render the page. In this method you return the markup of the page you want. You can use the `html` function from `Lit`, that gives you a template language to output the HTML you want. Once the template is created, you only need to set/change the properties to re-render the page content. Read more about https://lit.dev/docs/components/rendering/[Lit html] <9> You can use the built-in template functions to do conditional, list, etc. Read more about https://lit.dev/docs/templates/overview/[Lit Templates] <10> You always need to register your Web component as a custom element, with a unique tag. Here the tag will follow the same format as the filename (`qwc` dash `extension name` dash `page title` ); @@ -458,11 +458,11 @@ customElements.define('qwc-arc-beans', QwcArcBeans); ---- <1> Import the Vaadin component you want to use <2> You can also import other functions if needed -<3> There are some internal UI component that you can use, described below +<3> There are some internal UI components that you can use, described below ===== Using internal UI components -Some https://github.com/quarkusio/quarkus/tree/main/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui[internal UI components] (under the `qui` namespace) is available to make certain things easier: +Some https://github.com/quarkusio/quarkus/tree/main/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui[internal UI components] (under the `qui` namespace) are available to make certain things easier: - Card - Badge @@ -501,7 +501,7 @@ image::dev-ui-qui-badge-v2.png[alt=Dev UI Badge,role="center"] import 'qui-badge'; ---- -You can use any combination of small, primary, pill, with icon and clickable with any level of `default`, `success`, `warning`, `error`, `contrast` or set your own colours. +You can use any combination of small, primary, pill, with icon and clickable with any level of `default`, `success`, `warning`, `error`, `contrast` or set your own colors. [source,html] ---- @@ -603,7 +603,7 @@ https://github.com/phillip-kruger/quarkus-jokes/blob/f572ed6f949de0c0b8cbfa99d73 ====== Alert -Alerts is modelled around the Bootstrap alerts. Click https://getbootstrap.com/docs/4.0/components/alerts[here] for more info. +Alerts are modeled around the Bootstrap alerts. Click https://getbootstrap.com/docs/4.0/components/alerts[here] for more info. Also see Notification controller below as an alternative. @@ -733,7 +733,7 @@ https://github.com/quarkusio/quarkus/blob/582f1f78806d2268885faea7aa8f5a4d2b3f5b ===== Using internal controllers -Some https://github.com/quarkusio/quarkus/tree/main/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller[internal controllers] is available to make certain things easier: +Some https://github.com/quarkusio/quarkus/tree/main/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller[internal controllers] are available to make certain things easier: - Notifier - Storage @@ -776,7 +776,7 @@ https://github.com/phillip-kruger/quarkus-jokes/blob/f572ed6f949de0c0b8cbfa99d73 An easy way to access the local storage in a safe way. This will store values in the local storage, scoped to your extension. This way you do not have to worry that you might clash with another extension. -Local storage is useful to remember user preferences or state. Example, the footer remembers the state (open/close) and the size when open of the bottom drawer. +Local storage is useful to remember user preferences or state. For example, the footer remembers the state (open/close) and the size when open of the bottom drawer. [source,javascript] ---- @@ -836,7 +836,7 @@ https://github.com/quarkusio/quarkus/blob/main/extensions/vertx-http/dev-ui-reso ====== Router -The router is mostly used internally. This uses https://github.com/vaadin/router[Vaadin Router] under the covers to route urls to the correct page/section within the SPA. It will update the navigation and allow history (back button). This also creates the sub-menu available on extensions that has multiple pages. +The router is mostly used internally. This uses https://github.com/vaadin/router[Vaadin Router] under the covers to route URLs to the correct page/section within the SPA. It will update the navigation and allow history (back button). This also creates the sub-menu available on extensions that have multiple pages. See the https://github.com/quarkusio/quarkus/blob/main/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js[controller] for some methods that might be useful. @@ -860,7 +860,7 @@ JsonRPCProvidersBuildItem createJsonRPCServiceForCache() {// <2> ---- <1> Always only do this in Dev Mode <2> Produce / return a `JsonRPCProvidersBuildItem` -<3> Define the class in your runtime module that will contain methods that makes data available in the UI +<3> Define the class in your runtime module that will contain methods that make data available in the UI https://github.com/quarkusio/quarkus/blob/main/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/devui/CacheDevUiProcessor.java[Example code] @@ -930,13 +930,13 @@ connectedCallback() { ---- <1> Note the method `getAll` corresponds to the method in your Java Service. This method returns a https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise[Promise] with the JsonRPC result. -<2> in this case the result is an array, so we can loop over it. +<2> In this case the result is an array, so we can loop over it. JsonArray (or any Java collection) in either blocking or non-blocking will return an array, else a JsonObject will be returned. https://github.com/quarkusio/quarkus/blob/main/extensions/cache/deployment/src/main/resources/dev-ui/qwc-cache-caches.js[Example code] -You can also pass in parameters in the method being called, example: +You can also pass in parameters in the method being called, for example: (In the Runtime Java code) [source,java] @@ -1047,7 +1047,7 @@ export class QwcMyExtensionPage extends QwcHotReloadElement { == Custom cards -You can customise the card that is being displayed on the extension page if you do not want to use the default built-in card. +You can customize the card that is being displayed on the extension page if you do not want to use the default built-in card. To do this, you need to provide a Webcomponent that will be loaded in the place of the provided card and register this in the Java Processor: @@ -1063,7 +1063,7 @@ On the Javascript side, you have access to all the pages (in case you want to cr import { pages } from 'build-time-data'; ---- -And the following properties will be passes in: +And the following properties will be passed in: - extensionName - description From 53077bf23dfa2fbae1cb60a95805ad36a956f319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 3 Oct 2023 12:35:51 +0200 Subject: [PATCH 38/38] Upgrade to Hibernate Search 6.2.2.Final (cherry picked from commit d74718052aa804ecb7e3d50a661f1c83687774d6) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 409a271d13759..015aa3bc7dd62 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -105,7 +105,7 @@ 2.0.5.Final 8.0.1.Final - 6.2.1.Final + 6.2.2.Final 7.0.0.Final 2.1 8.0.0.Final