From 3ffa6ecee00d1c6d030139434d63a3b3d5e2d973 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 21 Aug 2024 18:39:20 +0200 Subject: [PATCH 01/21] Encode URL in OIDC cookie Fix #31802 --- .../quarkus/oidc/runtime/CodeAuthenticationMechanism.java | 6 ++++-- .../src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 7ce98b9559606..2e982380f61fc 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -4,6 +4,8 @@ import static io.quarkus.oidc.runtime.OidcIdentityProvider.REFRESH_TOKEN_GRANT_RESPONSE; import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.SecureRandom; @@ -940,7 +942,7 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta Authentication authentication = configContext.oidcConfig.authentication; boolean pkceRequired = authentication.pkceRequired.orElse(false); if (!pkceRequired && !authentication.nonceRequired) { - bean.setRestorePath(parsedStateCookieValue[1]); + bean.setRestorePath(URLDecoder.decode(parsedStateCookieValue[1], StandardCharsets.UTF_8)); return bean; } @@ -1177,7 +1179,7 @@ private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue throw new AuthenticationCompletionException(ex); } } else { - return extraStateValue.getRestorePath(); + return URLEncoder.encode(extraStateValue.getRestorePath(), StandardCharsets.UTF_8); } } diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index 3b1e45505da7c..efc8f9016876e 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.net.URI; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Base64; @@ -1561,12 +1562,12 @@ private String getStateCookieStateParam(Cookie stateCookie) { private String getStateCookieSavedPath(WebClient webClient, String tenantId) { String[] parts = getStateCookie(webClient, tenantId).getValue().split("\\|"); - return parts.length == 2 ? parts[1] : null; + return parts.length == 2 ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : null; } private String getStateCookieSavedPath(Cookie stateCookie) { String[] parts = stateCookie.getValue().split("\\|"); - return parts.length == 2 ? parts[1] : null; + return parts.length == 2 ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : null; } private Cookie getSessionCookie(WebClient webClient, String tenantId) { From 65c3611368c25650d4ab4fb8e1628085cf7d2386 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 29 Aug 2024 15:56:16 +0100 Subject: [PATCH 02/21] Use Base64URL encoded JSON to store unencrypted OIDC state cookie value --- .../runtime/CodeAuthenticationMechanism.java | 19 +++++---- .../io/quarkus/oidc/runtime/OidcUtils.java | 3 +- .../src/main/resources/application.properties | 2 +- .../io/quarkus/it/keycloak/CodeFlowTest.java | 40 ++++++++++++++++--- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 2e982380f61fc..a9c5dc6c4f86b 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -4,8 +4,6 @@ import static io.quarkus.oidc.runtime.OidcIdentityProvider.REFRESH_TOKEN_GRANT_RESPONSE; import java.net.URI; -import java.net.URLDecoder; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.SecureRandom; @@ -71,7 +69,6 @@ public class CodeAuthenticationMechanism extends AbstractOidcAuthenticationMecha static final String EQ = "="; static final String COOKIE_DELIM = "|"; static final Pattern COOKIE_PATTERN = Pattern.compile("\\" + COOKIE_DELIM); - static final String STATE_COOKIE_RESTORE_PATH = "restore-path"; static final Uni VOID_UNI = Uni.createFrom().voidItem(); static final String NO_OIDC_COOKIES_AVAILABLE = "no_oidc_cookies"; @@ -940,13 +937,16 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta if (parsedStateCookieValue.length == 2) { CodeAuthenticationStateBean bean = new CodeAuthenticationStateBean(); Authentication authentication = configContext.oidcConfig.authentication; + boolean pkceRequired = authentication.pkceRequired.orElse(false); if (!pkceRequired && !authentication.nonceRequired) { - bean.setRestorePath(URLDecoder.decode(parsedStateCookieValue[1], StandardCharsets.UTF_8)); + JsonObject json = new JsonObject(OidcUtils.base64UrlDecode(parsedStateCookieValue[1])); + bean.setRestorePath(json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH)); return bean; } JsonObject json = null; + try { json = OidcUtils.decryptJson(parsedStateCookieValue[1], configContext.getStateEncryptionKey()); } catch (Exception ex) { @@ -954,7 +954,7 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta configContext.oidcConfig.tenantId.get()); throw new AuthenticationCompletionException(ex); } - bean.setRestorePath(json.getString(STATE_COOKIE_RESTORE_PATH)); + bean.setRestorePath(json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH)); bean.setCodeVerifier(json.getString(OidcConstants.PKCE_CODE_VERIFIER)); bean.setNonce(json.getString(OidcConstants.NONCE)); return bean; @@ -1161,8 +1161,9 @@ private boolean isRestorePath(Authentication auth) { } private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue, TenantConfigContext configContext) { + JsonObject json = new JsonObject(); + if (extraStateValue.getCodeVerifier() != null || extraStateValue.getNonce() != null) { - JsonObject json = new JsonObject(); if (extraStateValue.getCodeVerifier() != null) { json.put(OidcConstants.PKCE_CODE_VERIFIER, extraStateValue.getCodeVerifier()); } @@ -1170,7 +1171,7 @@ private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue json.put(OidcConstants.NONCE, extraStateValue.getNonce()); } if (extraStateValue.getRestorePath() != null) { - json.put(STATE_COOKIE_RESTORE_PATH, extraStateValue.getRestorePath()); + json.put(OidcUtils.STATE_COOKIE_RESTORE_PATH, extraStateValue.getRestorePath()); } try { return OidcUtils.encryptJson(json, configContext.getStateEncryptionKey()); @@ -1179,7 +1180,9 @@ private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue throw new AuthenticationCompletionException(ex); } } else { - return URLEncoder.encode(extraStateValue.getRestorePath(), StandardCharsets.UTF_8); + json.put(OidcUtils.STATE_COOKIE_RESTORE_PATH, extraStateValue.getRestorePath()); + + return Base64.getUrlEncoder().withoutPadding().encodeToString(json.encode().getBytes(StandardCharsets.UTF_8)); } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index db3b03456ab6f..6773643e284e8 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -75,6 +75,7 @@ public final class OidcUtils { private static final Logger LOG = Logger.getLogger(OidcUtils.class); + public static final String STATE_COOKIE_RESTORE_PATH = "restore-path"; public static final String CONFIG_METADATA_ATTRIBUTE = "configuration-metadata"; public static final String USER_INFO_ATTRIBUTE = "userinfo"; public static final String INTROSPECTION_ATTRIBUTE = "introspection"; @@ -252,7 +253,7 @@ private static JsonObject decodeAsJsonObject(String encodedContent) { } } - private static String base64UrlDecode(String encodedContent) { + public static String base64UrlDecode(String encodedContent) { return new String(Base64.getUrlDecoder().decode(encodedContent), StandardCharsets.UTF_8); } diff --git a/integration-tests/oidc-code-flow/src/main/resources/application.properties b/integration-tests/oidc-code-flow/src/main/resources/application.properties index 41372ae76857a..273e767de6583 100644 --- a/integration-tests/oidc-code-flow/src/main/resources/application.properties +++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties @@ -173,7 +173,7 @@ quarkus.oidc.tenant-split-tokens.token-state-manager.encryption-secret=eUk1p7UB3 quarkus.oidc.tenant-split-tokens.application-type=web-app quarkus.oidc.tenant-split-tokens.authentication.cookie-same-site=strict -quarkus.http.auth.permission.roles1.paths=/index.html +quarkus.http.auth.permission.roles1.paths=/index.html,/index.html;/checktterer quarkus.http.auth.permission.roles1.policy=authenticated quarkus.http.auth.permission.logout.paths=/tenant-logout diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index efc8f9016876e..886fd30414843 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.net.URI; -import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Base64; @@ -1082,6 +1081,29 @@ public void testAccessTokenInjection() throws IOException { } } + @Test + public void testInvalidPath() throws IOException { + try (final WebClient webClient = createWebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8081/index.html;/checktterer"); + assertEquals("/index.html;/checktterer", getStateCookieSavedPath(webClient, null)); + + assertEquals("Sign in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); + + try { + page = loginForm.getInputByName("login").click(); + } catch (FailingHttpStatusCodeException ex) { + assertEquals(404, ex.getStatusCode()); + } + + webClient.getCookieManager().clearCookies(); + } + } + @Test public void testAccessAndRefreshTokenInjection() throws IOException { try (final WebClient webClient = createWebClient()) { @@ -1387,8 +1409,8 @@ public void testAccessAndRefreshTokenInjectionWithoutIndexHtmlAndListenerMultiTa @Test public void testAccessAndRefreshTokenInjectionWithQuery() throws Exception { try (final WebClient webClient = createWebClient()) { - HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh-query?a=aValue"); - assertEquals("/web-app/refresh-query?a=aValue", getStateCookieSavedPath(webClient, null)); + HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh-query?a=aValue%"); + assertEquals("/web-app/refresh-query?a=aValue%25", getStateCookieSavedPath(webClient, null)); assertEquals("Sign in to quarkus", page.getTitleText()); @@ -1399,7 +1421,8 @@ public void testAccessAndRefreshTokenInjectionWithQuery() throws Exception { page = loginForm.getInputByName("login").click(); - assertEquals("RT injected:aValue", page.getBody().asNormalizedText()); + // Query parameters are decoded by the time they reach the JAX-RS endpoint + assertEquals("RT injected:aValue%", page.getBody().asNormalizedText()); webClient.getCookieManager().clearCookies(); } } @@ -1562,12 +1585,17 @@ private String getStateCookieStateParam(Cookie stateCookie) { private String getStateCookieSavedPath(WebClient webClient, String tenantId) { String[] parts = getStateCookie(webClient, tenantId).getValue().split("\\|"); - return parts.length == 2 ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : null; + return parts.length == 2 ? getSavedPathFromJson(parts[1]) : null; } private String getStateCookieSavedPath(Cookie stateCookie) { String[] parts = stateCookie.getValue().split("\\|"); - return parts.length == 2 ? URLDecoder.decode(parts[1], StandardCharsets.UTF_8) : null; + return parts.length == 2 ? getSavedPathFromJson(parts[1]) : null; + } + + private String getSavedPathFromJson(String value) { + JsonObject json = new JsonObject(OidcUtils.base64UrlDecode(value)); + return json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH); } private Cookie getSessionCookie(WebClient webClient, String tenantId) { From c84d30e4a78e0d6c05e77e17d668b36bc7c8aabe Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 29 Aug 2024 20:04:52 +0100 Subject: [PATCH 03/21] Remove BOOTSTRAP config phase from documentation --- docs/src/main/asciidoc/writing-extensions.adoc | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index b2490d1c9c168..bac8aab054435 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -1090,13 +1090,6 @@ configuration, and when they are available to applications. The phases defined b | ✗ | Appropriate for things which affect build and must be visible for run time code. Not read from config at run time. -| BOOTSTRAP -| ✗ -| ✓ -| ✗ -| ✓ -| Used when runtime configuration needs to be obtained from an external system (like `Consul`), but details of that system need to be configurable (for example Consul's URL). The high level way this works is by using the standard Quarkus config sources (such as properties files, system properties, etc.) and producing `ConfigSourceProvider` objects which are subsequently taken into account by Quarkus when creating the final runtime `Config` object. - | RUN_TIME | ✗ | ✓ @@ -1108,8 +1101,6 @@ configuration, and when they are available to applications. The phases defined b For all cases other than the `BUILD_TIME` case, the configuration mapping interface and all the configuration groups and types contained therein must be located in, or reachable from, the extension's run time artifact. Configuration mappings of phase `BUILD_TIME` may be located in or reachable from either of the extension's run time or deployment artifacts. -IMPORTANT: _Bootstrap_ configuration steps are executed during runtime-init *before* any of other runtime steps. This means that code executed as part of this step cannot access anything that gets initialized in runtime init steps (runtime synthetic CDI beans being one such example). - ==== Configuration Example [source%nowrap,java] @@ -1203,8 +1194,7 @@ Since `format` is not defined in these properties, the default value from `@With A configuration mapping name can contain an extra suffix segment for the case where there are configuration mappings for multiple <>. Classes which correspond to the `BUILD_TIME` and `BUILD_AND_RUN_TIME_FIXED` may end with `BuildTimeConfig` or `BuildTimeConfiguration`, classes which correspond to the `RUN_TIME` phase -may end with `RuntimeConfig`, `RunTimeConfig`, `RuntimeConfiguration` or `RunTimeConfiguration` while classes which -correspond to the `BOOTSTRAP` configuration may end with `BootstrapConfig` or `BootstrapConfiguration`. +may end with `RuntimeConfig`, `RunTimeConfig`, `RuntimeConfiguration` or `RunTimeConfiguration`. ==== Configuration Reference Documentation From d37edb6e8f7493dfc3bde2fcb9de93899dcf5842 Mon Sep 17 00:00:00 2001 From: Alex Martel <13215031+manofthepeace@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:18:44 -0400 Subject: [PATCH 04/21] Update commons-lang3 to 3.17.0 --- bom/application/pom.xml | 2 +- .../deployment/pkg/steps/NativeImageBuildContainerRunner.java | 2 +- .../quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java | 2 +- independent-projects/bootstrap/pom.xml | 2 +- .../io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java | 2 +- .../io/quarkus/test/common/DefaultDockerContainerLauncher.java | 2 +- .../main/java/io/quarkus/test/junit/IntegrationTestUtil.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 7ad8ce8a6fbb0..634944580292c 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -95,7 +95,7 @@ 1.8.0 2.17.2 1.0.0.Final - 3.14.0 + 3.17.0 1.17.1 1.7.0 2.8 diff --git a/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java b/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java index eab9ad69c8765..c05c6304c1242 100644 --- a/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java +++ b/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java @@ -31,7 +31,7 @@ public static class RabbitMQResource implements QuarkusTestResourceLifecycleMana @Override public Map start() { String username = "tester"; - String password = RandomStringUtils.random(10); + String password = RandomStringUtils.insecure().next(10); rabbit = new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.12-management")) .withNetwork(Network.SHARED) diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java index 533048d795934..50295407fa505 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java @@ -50,7 +50,7 @@ public class DefaultDockerContainerLauncher implements DockerContainerArtifactLa private Map labels; private final Map systemProps = new HashMap<>(); private boolean isSsl; - private final String containerName = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false); + private final String containerName = "quarkus-integration-test-" + RandomStringUtils.insecure().next(5, true, false); private String containerRuntimeBinaryName; private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private Optional entryPoint; diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java index a29edfc4a0488..b44f78c962cf5 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java @@ -319,7 +319,7 @@ public void accept(String s, String s2) { if (networkIdOpt.isPresent()) { networkId = networkIdOpt.get(); } else { - networkId = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false); + networkId = "quarkus-integration-test-" + RandomStringUtils.insecure().next(5, true, false); manageNetwork = true; } } From 0117539c165b3d12cfc867bc1c17339f1aa4a4b9 Mon Sep 17 00:00:00 2001 From: Alex Martel <13215031+manofthepeace@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:49:58 -0400 Subject: [PATCH 05/21] Remove mvn.config hack for mvnd --- .mvn/maven.config | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .mvn/maven.config diff --git a/.mvn/maven.config b/.mvn/maven.config deleted file mode 100644 index 0f4fa3c8a6eae..0000000000000 --- a/.mvn/maven.config +++ /dev/null @@ -1 +0,0 @@ --Dmaven.multiModuleProjectDirectory=${session.rootDirectory} \ No newline at end of file From b442da9c000f7dbb1c63663beaea4b9bda6cd55f Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Fri, 30 Aug 2024 09:46:28 +0200 Subject: [PATCH 06/21] Upgrade spring-api dependency. Fixes #42871 --- 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 cff2fc3cf223a..e42038f592692 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -174,7 +174,7 @@ 3.26.1 0.3.0 4.17.0 - 6.1.SP2 + 6.1.SP3 3.2.SP2 6.2 3.2 From c4beb8c8f1770409c16d59016fc20095edbcc6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Fri, 30 Aug 2024 10:33:53 +0200 Subject: [PATCH 07/21] [Vert.X] Possible to handle routes for base URI without path from extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #42840 Signed-off-by: Martin Bartoš --- .../quarkus/vertx/http/deployment/HttpRootPathBuildItem.java | 5 ++++- .../vertx/http/deployment/HttpRootPathBuildItemTest.java | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java index b09a57db097b5..68078f8b88191 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java @@ -50,11 +50,13 @@ public String getRootPath() { *
    *
  • {@code resolvePath("foo")} will return {@literal /foo}
  • *
  • {@code resolvePath("/foo")} will return {@literal /foo}
  • + *
  • {@code resolvePath("/")} will return {@literal /}
  • *
* Given {@literal quarkus.http.root-path=/app} *
    *
  • {@code resolvePath("foo")} will return {@literal /app/foo}
  • *
  • {@code resolvePath("/foo")} will return {@literal /foo}
  • + *
  • {@code resolvePath("/")} will return {@literal /}
  • *
*

* The returned path will not end with a slash. @@ -64,7 +66,8 @@ public String getRootPath() { * @see UriNormalizationUtil#normalizeWithBase(URI, String, boolean) */ public String resolvePath(String path) { - return UriNormalizationUtil.normalizeWithBase(rootPath, path, false).getPath(); + var isSlashAbsolutePath = "/".equals(path); + return UriNormalizationUtil.normalizeWithBase(rootPath, path, isSlashAbsolutePath).getPath(); } /** diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java index 1e98718b35c87..9a7f4120556ef 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItemTest.java @@ -9,6 +9,7 @@ void testResolvePathWithSlash() { HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/"); Assertions.assertEquals("/", buildItem.resolvePath("")); + Assertions.assertEquals("/", buildItem.resolvePath("/")); Assertions.assertEquals("/foo", buildItem.resolvePath("foo")); Assertions.assertEquals("/foo", buildItem.resolvePath("/foo")); Assertions.assertThrows(IllegalArgumentException.class, () -> buildItem.resolvePath("../foo")); @@ -19,6 +20,7 @@ void testResolvePathWithSlashApp() { HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/app"); Assertions.assertEquals("/app", buildItem.resolvePath("")); + Assertions.assertEquals("/", buildItem.resolvePath("/")); Assertions.assertEquals("/app/foo", buildItem.resolvePath("foo")); Assertions.assertEquals("/foo", buildItem.resolvePath("/foo")); } From e8c5e457895f617b0dba4b5218ef84900a9213e0 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 30 Aug 2024 10:47:33 +0200 Subject: [PATCH 08/21] Process classes from the application artifact instead of the module output directory --- .../steps/CompiledJavaVersionBuildStep.java | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/CompiledJavaVersionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/CompiledJavaVersionBuildStep.java index 3131b4243e08d..33b00c930f6a8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/CompiledJavaVersionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/CompiledJavaVersionBuildStep.java @@ -1,21 +1,18 @@ package io.quarkus.deployment.steps; import java.io.DataInputStream; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; import java.util.concurrent.atomic.AtomicReference; import org.jboss.logging.Logger; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem; import io.quarkus.deployment.pkg.builditem.CompiledJavaVersionBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.maven.dependency.DependencyFlags; +import io.quarkus.maven.dependency.ResolvedDependency; public class CompiledJavaVersionBuildStep { @@ -26,44 +23,49 @@ public class CompiledJavaVersionBuildStep { * application .class file that is found */ @BuildStep - public CompiledJavaVersionBuildItem compiledJavaVersion(BuildSystemTargetBuildItem buildSystemTarget) { - if ((buildSystemTarget.getOutputDirectory() == null) || (!Files.exists(buildSystemTarget.getOutputDirectory()))) { - log.debug("Skipping because output directory does not exist"); - // needed for Arquillian TCK tests - return CompiledJavaVersionBuildItem.unknown(); - } - AtomicReference majorVersion = new AtomicReference<>(null); - try { - log.debugf("Walking directory '%s'", buildSystemTarget.getOutputDirectory().toAbsolutePath().toString()); - Files.walkFileTree(buildSystemTarget.getOutputDirectory(), new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - if (file.getFileName().toString().endsWith(".class")) { - log.debugf("Checking file '%s'", file.toAbsolutePath().toString()); - try (InputStream in = new FileInputStream(file.toFile())) { - DataInputStream data = new DataInputStream(in); - if (0xCAFEBABE == data.readInt()) { - data.readUnsignedShort(); // minor version -> we don't care about it - int v = data.readUnsignedShort(); - majorVersion.set(v); - log.debugf("Determined compile java version to be %d", v); - return FileVisitResult.TERMINATE; - } - } catch (IOException e) { - log.debugf(e, "Encountered exception while processing file '%s'", file.toAbsolutePath().toString()); - } - } - // if this was not .class file or there was an error parsing its contents, we continue on to the next file - return FileVisitResult.CONTINUE; + public CompiledJavaVersionBuildItem compiledJavaVersion(CurateOutcomeBuildItem curateOutcomeBuildItem) { + final ResolvedDependency appArtifact = curateOutcomeBuildItem.getApplicationModel().getAppArtifact(); + Integer majorVersion = getMajorJavaVersion(appArtifact); + if (majorVersion == null) { + // workspace info isn't available in prod builds though + for (ResolvedDependency module : curateOutcomeBuildItem.getApplicationModel() + .getDependencies(DependencyFlags.WORKSPACE_MODULE)) { + majorVersion = getMajorJavaVersion(module); + if (majorVersion != null) { + break; } - }); - } catch (IOException ignored) { - + } } - if (majorVersion.get() == null) { + if (majorVersion == null) { log.debug("No .class files located"); return CompiledJavaVersionBuildItem.unknown(); } - return CompiledJavaVersionBuildItem.fromMajorJavaVersion(majorVersion.get()); + return CompiledJavaVersionBuildItem.fromMajorJavaVersion(majorVersion); + } + + private static Integer getMajorJavaVersion(ResolvedDependency artifact) { + final AtomicReference majorVersion = new AtomicReference<>(null); + artifact.getContentTree().walk(visit -> { + final Path file = visit.getPath(); + if (file.getFileName() == null) { + // this can happen if it's the root of a JAR + return; + } + if (file.getFileName().toString().endsWith(".class") && !Files.isDirectory(file)) { + log.debugf("Checking file '%s'", file.toAbsolutePath().toString()); + try (DataInputStream data = new DataInputStream(Files.newInputStream(file))) { + if (0xCAFEBABE == data.readInt()) { + data.readUnsignedShort(); // minor version -> we don't care about it + int v = data.readUnsignedShort(); + majorVersion.set(v); + log.debugf("Determined compile java version to be %d", v); + visit.stopWalking(); + } + } catch (IOException e) { + log.debugf(e, "Encountered exception while processing file '%s'", file.toAbsolutePath().toString()); + } + } + }); + return majorVersion.get(); } } From 0b50a7673b2f8d6cc899e0802d0b59506c6c6198 Mon Sep 17 00:00:00 2001 From: xstefank Date: Fri, 30 Aug 2024 11:24:27 +0200 Subject: [PATCH 09/21] Use quarkus-rest instead of quarkus-resteasy as default extension in maven plugin --- .../maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 55baff707f8c6..c39bff63a8052 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -63,7 +63,7 @@ public class CreateProjectMojo extends AbstractMojo { private static final String DEFAULT_GROUP_ID = "org.acme"; private static final String DEFAULT_ARTIFACT_ID = "code-with-quarkus"; private static final String DEFAULT_VERSION = "1.0.0-SNAPSHOT"; - private static final String DEFAULT_EXTENSIONS = "resteasy"; + private static final String DEFAULT_EXTENSIONS = "rest"; @Parameter(defaultValue = "${project}") protected MavenProject project; From 07caa35db81061a872a58e94c305ebdc26aaf817 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 30 Aug 2024 15:44:02 +0200 Subject: [PATCH 10/21] Config Doc - Collect source type This is going to be necessary for the IDE work. --- .../discovery/DiscoveryConfigProperty.java | 22 ++++++++++++++----- .../config/model/AbstractConfigItem.java | 9 +++++++- .../config/model/ConfigProperty.java | 4 ++-- .../config/model/ConfigSection.java | 4 ++-- .../config/model/SourceType.java | 7 ++++++ .../config/resolver/ConfigResolver.java | 7 ++++-- .../config/scanner/ConfigMappingListener.java | 3 ++- .../scanner/LegacyConfigRootListener.java | 3 ++- 8 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java index 5f1c49fa11902..f98923e40e713 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java @@ -1,5 +1,6 @@ package io.quarkus.annotation.processor.documentation.config.discovery; +import io.quarkus.annotation.processor.documentation.config.model.SourceType; import io.quarkus.annotation.processor.documentation.config.util.TypeUtil; import io.quarkus.annotation.processor.util.Strings; @@ -8,6 +9,7 @@ public class DiscoveryConfigProperty { private final String path; private final String sourceClass; private final String sourceName; + private final SourceType sourceType; private final String defaultValue; private final String defaultValueForDoc; private final boolean deprecated; @@ -19,13 +21,15 @@ public class DiscoveryConfigProperty { private final boolean section; private final boolean sectionGenerated; - public DiscoveryConfigProperty(String path, String sourceClass, String sourceName, String defaultValue, + public DiscoveryConfigProperty(String path, String sourceClass, String sourceName, SourceType sourceType, + String defaultValue, String defaultValueForDoc, boolean deprecated, String mapKey, boolean unnamedMapKey, ResolvedType type, boolean converted, boolean enforceHyphenateEnumValue, boolean section, boolean sectionGenerated) { this.path = path; this.sourceClass = sourceClass; this.sourceName = sourceName; + this.sourceType = sourceType; this.defaultValue = defaultValue; this.defaultValueForDoc = defaultValueForDoc; this.deprecated = deprecated; @@ -50,6 +54,10 @@ public String getSourceName() { return sourceName; } + public SourceType getSourceType() { + return sourceType; + } + public String getDefaultValue() { return defaultValue; } @@ -122,8 +130,8 @@ public String toString(String prefix) { return sb.toString(); } - public static Builder builder(String sourceClass, String sourceName, ResolvedType type) { - return new Builder(sourceClass, sourceName, type); + public static Builder builder(String sourceClass, String sourceName, SourceType sourceType, ResolvedType type) { + return new Builder(sourceClass, sourceName, sourceType, type); } public static class Builder { @@ -131,6 +139,7 @@ public static class Builder { private String name; private final String sourceClass; private final String sourceName; + private final SourceType sourceType; private final ResolvedType type; private String defaultValue; private String defaultValueForDoc; @@ -142,9 +151,10 @@ public static class Builder { private boolean section = false; private boolean sectionGenerated = false; - public Builder(String sourceClass, String sourceName, ResolvedType type) { + public Builder(String sourceClass, String sourceName, SourceType sourceType, ResolvedType type) { this.sourceClass = sourceClass; this.sourceName = sourceName; + this.sourceType = sourceType; this.type = type; } @@ -202,8 +212,8 @@ public DiscoveryConfigProperty build() { defaultValue = TypeUtil.normalizeDurationValue(defaultValue); } - return new DiscoveryConfigProperty(name, sourceClass, sourceName, defaultValue, defaultValueForDoc, deprecated, - mapKey, unnamedMapKey, type, converted, enforceHyphenateEnumValue, section, sectionGenerated); + return new DiscoveryConfigProperty(name, sourceClass, sourceName, sourceType, defaultValue, defaultValueForDoc, + deprecated, mapKey, unnamedMapKey, type, converted, enforceHyphenateEnumValue, section, sectionGenerated); } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java index b6095aa69c73c..151dd87cd48f8 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java @@ -9,15 +9,18 @@ public sealed abstract class AbstractConfigItem implements Comparable additionalPaths, String type, String typeDescription, boolean map, boolean list, boolean optional, String mapKey, boolean unnamedMapKey, boolean withinMap, boolean converted, @JsonProperty("enum") boolean isEnum, EnumAcceptedValues enumAcceptedValues, String defaultValue, String javadocSiteLink, boolean deprecated) { - super(sourceClass, sourceName, path, type, deprecated); + super(sourceClass, sourceName, sourceType, path, type, deprecated); this.phase = phase; this.additionalPaths = additionalPaths != null ? Collections.unmodifiableList(additionalPaths) : List.of(); this.typeDescription = typeDescription; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java index 1cbacbae64998..8200970c5b87d 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java @@ -11,9 +11,9 @@ public final class ConfigSection extends AbstractConfigItem implements ConfigIte private final List items = new ArrayList<>(); private final int level; - public ConfigSection(String sourceClass, String sourceName, SectionPath path, String type, int level, + public ConfigSection(String sourceClass, String sourceName, SourceType sourceType, SectionPath path, String type, int level, boolean generated, boolean deprecated) { - super(sourceClass, sourceName, path, type, deprecated); + super(sourceClass, sourceName, sourceType, path, type, deprecated); this.generated = generated; this.level = level; } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java new file mode 100644 index 0000000000000..6da161b75714e --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java @@ -0,0 +1,7 @@ +package io.quarkus.annotation.processor.documentation.config.model; + +public enum SourceType { + + METHOD, + FIELD; +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java index 9e79083966110..a71bd8348ce20 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java @@ -133,7 +133,8 @@ private void resolveProperty(ConfigRoot configRoot, Map e configSection.appendState(discoveryConfigProperty.isSectionGenerated(), deprecated); } else { configSection = new ConfigSection(discoveryConfigProperty.getSourceClass(), - discoveryConfigProperty.getSourceName(), new SectionPath(path), typeQualifiedName, + discoveryConfigProperty.getSourceName(), discoveryConfigProperty.getSourceType(), + new SectionPath(path), typeQualifiedName, context.getSectionLevel(), discoveryConfigProperty.isSectionGenerated(), deprecated); context.getItemCollection().addItem(configSection); existingRootConfigSections.put(path, configSection); @@ -200,7 +201,9 @@ private void resolveProperty(ConfigRoot configRoot, Map e // this is a standard property ConfigProperty configProperty = new ConfigProperty(phase, discoveryConfigProperty.getSourceClass(), - discoveryConfigProperty.getSourceName(), propertyPath, additionalPropertyPaths, + discoveryConfigProperty.getSourceName(), + discoveryConfigProperty.getSourceType(), + propertyPath, additionalPropertyPaths, typeQualifiedName, typeSimplifiedName, discoveryConfigProperty.getType().isMap(), discoveryConfigProperty.getType().isList(), optional, discoveryConfigProperty.getMapKey(), diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java index 988805d7f532d..8fc04a9e77547 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java @@ -15,6 +15,7 @@ import io.quarkus.annotation.processor.documentation.config.discovery.DiscoveryRootElement; import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType; import io.quarkus.annotation.processor.documentation.config.model.ConfigPhase; +import io.quarkus.annotation.processor.documentation.config.model.SourceType; import io.quarkus.annotation.processor.documentation.config.util.ConfigNamingUtil; import io.quarkus.annotation.processor.documentation.config.util.Markers; import io.quarkus.annotation.processor.documentation.config.util.Types; @@ -126,7 +127,7 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem String sourceName = method.getSimpleName().toString(); DiscoveryConfigProperty.Builder builder = DiscoveryConfigProperty.builder(clazz.getQualifiedName().toString(), - sourceName, resolvedType); + sourceName, SourceType.METHOD, resolvedType); AnnotationMirror deprecatedAnnotation = methodAnnotations.get(Deprecated.class.getName()); if (deprecatedAnnotation != null) { diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java index bc7aadb4dc150..9e17e76c05abb 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java @@ -16,6 +16,7 @@ import io.quarkus.annotation.processor.documentation.config.discovery.DiscoveryRootElement; import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType; import io.quarkus.annotation.processor.documentation.config.model.ConfigPhase; +import io.quarkus.annotation.processor.documentation.config.model.SourceType; import io.quarkus.annotation.processor.documentation.config.util.ConfigNamingUtil; import io.quarkus.annotation.processor.documentation.config.util.Markers; import io.quarkus.annotation.processor.documentation.config.util.Types; @@ -126,7 +127,7 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme String name = ConfigNamingUtil.hyphenate(sourceName); DiscoveryConfigProperty.Builder builder = DiscoveryConfigProperty.builder(clazz.getQualifiedName().toString(), - sourceName, resolvedType); + sourceName, SourceType.FIELD, resolvedType); AnnotationMirror deprecatedAnnotation = fieldAnnotations.get(Deprecated.class.getName()); if (deprecatedAnnotation != null) { From 1de0071b159fa72b2e1acf1e42ef318d82fa776c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 30 Aug 2024 16:18:32 +0200 Subject: [PATCH 11/21] Config Doc - Collect more information about deprecation This will be useful for the IDE config doc. --- .../discovery/DiscoveryConfigProperty.java | 23 +++++++++------- .../config/discovery/ParsedJavadoc.java | 4 +-- .../discovery/ParsedJavadocSection.java | 6 ++++- .../JavadocToAsciidocTransformer.java | 22 ++++++++++++---- .../config/model/AbstractConfigItem.java | 13 +++++++--- .../config/model/ConfigProperty.java | 4 +-- .../config/model/ConfigRoot.java | 4 +-- .../config/model/ConfigSection.java | 12 ++++----- .../config/model/Deprecation.java | 5 ++++ .../config/model/JavadocElements.java | 2 +- .../config/resolver/ConfigResolver.java | 26 ++++++++++--------- .../scanner/AbstractConfigListener.java | 7 +++++ .../AbstractJavadocConfigListener.java | 3 ++- .../config/scanner/ConfigMappingListener.java | 6 ----- .../scanner/JavadocConfigMappingListener.java | 6 +++-- .../JavadocLegacyConfigRootListener.java | 6 +++-- .../scanner/LegacyConfigRootListener.java | 6 ----- 17 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Deprecation.java diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java index f98923e40e713..cbff969922b77 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/DiscoveryConfigProperty.java @@ -1,5 +1,6 @@ package io.quarkus.annotation.processor.documentation.config.discovery; +import io.quarkus.annotation.processor.documentation.config.model.Deprecation; import io.quarkus.annotation.processor.documentation.config.model.SourceType; import io.quarkus.annotation.processor.documentation.config.util.TypeUtil; import io.quarkus.annotation.processor.util.Strings; @@ -12,7 +13,7 @@ public class DiscoveryConfigProperty { private final SourceType sourceType; private final String defaultValue; private final String defaultValueForDoc; - private final boolean deprecated; + private final Deprecation deprecation; private final String mapKey; private final boolean unnamedMapKey; private final ResolvedType type; @@ -23,7 +24,7 @@ public class DiscoveryConfigProperty { public DiscoveryConfigProperty(String path, String sourceClass, String sourceName, SourceType sourceType, String defaultValue, - String defaultValueForDoc, boolean deprecated, String mapKey, boolean unnamedMapKey, + String defaultValueForDoc, Deprecation deprecation, String mapKey, boolean unnamedMapKey, ResolvedType type, boolean converted, boolean enforceHyphenateEnumValue, boolean section, boolean sectionGenerated) { this.path = path; @@ -32,7 +33,7 @@ public DiscoveryConfigProperty(String path, String sourceClass, String sourceNam this.sourceType = sourceType; this.defaultValue = defaultValue; this.defaultValueForDoc = defaultValueForDoc; - this.deprecated = deprecated; + this.deprecation = deprecation; this.mapKey = mapKey; this.unnamedMapKey = unnamedMapKey; this.type = type; @@ -66,8 +67,12 @@ public String getDefaultValueForDoc() { return defaultValueForDoc; } + public Deprecation getDeprecation() { + return deprecation; + } + public boolean isDeprecated() { - return deprecated; + return deprecation != null; } public String getMapKey() { @@ -114,7 +119,7 @@ public String toString(String prefix) { if (defaultValueForDoc != null) { sb.append(prefix + "defaultValueForDoc = " + defaultValueForDoc + "\n"); } - if (deprecated) { + if (deprecation != null) { sb.append(prefix + "deprecated = true\n"); } if (mapKey != null) { @@ -143,7 +148,7 @@ public static class Builder { private final ResolvedType type; private String defaultValue; private String defaultValueForDoc; - private boolean deprecated = false; + private Deprecation deprecation; private String mapKey; private boolean unnamedMapKey = false; private boolean converted = false; @@ -173,8 +178,8 @@ public Builder defaultValueForDoc(String defaultValueForDoc) { return this; } - public Builder deprecated() { - this.deprecated = true; + public Builder deprecated(String since, String replacement, String reason) { + this.deprecation = new Deprecation(since, replacement, reason); return this; } @@ -213,7 +218,7 @@ public DiscoveryConfigProperty build() { } return new DiscoveryConfigProperty(name, sourceClass, sourceName, sourceType, defaultValue, defaultValueForDoc, - deprecated, mapKey, unnamedMapKey, type, converted, enforceHyphenateEnumValue, section, sectionGenerated); + deprecation, mapKey, unnamedMapKey, type, converted, enforceHyphenateEnumValue, section, sectionGenerated); } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadoc.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadoc.java index 45a13153619c1..287be0b9639f4 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadoc.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadoc.java @@ -1,9 +1,9 @@ package io.quarkus.annotation.processor.documentation.config.discovery; -public record ParsedJavadoc(String description, String since, JavadocFormat originalFormat) { +public record ParsedJavadoc(String description, String since, String deprecated, JavadocFormat originalFormat) { public static ParsedJavadoc empty() { - return new ParsedJavadoc(null, null, null); + return new ParsedJavadoc(null, null, null, null); } public boolean isEmpty() { diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadocSection.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadocSection.java index e93fce5e7b400..5f56acacb9cb3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadocSection.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ParsedJavadocSection.java @@ -1,4 +1,8 @@ package io.quarkus.annotation.processor.documentation.config.discovery; -public record ParsedJavadocSection(String title, String details) { +public record ParsedJavadocSection(String title, String details, String deprecated) { + + public static ParsedJavadocSection empty() { + return new ParsedJavadocSection(null, null, null); + } } \ No newline at end of file diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformer.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformer.java index 2775f2360a6cf..fd5a67640d0b3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformer.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/formatter/JavadocToAsciidocTransformer.java @@ -111,16 +111,22 @@ public ParsedJavadoc parseConfigItemJavadoc(String rawJavadoc) { .map(JavadocDescription::toText) .findFirst(); + Optional deprecated = javadoc.getBlockTags().stream() + .filter(t -> t.getType() == Type.DEPRECATED) + .map(JavadocBlockTag::getContent) + .map(JavadocDescription::toText) + .findFirst(); + if (description != null && description.isBlank()) { description = null; } - return new ParsedJavadoc(description, since.isPresent() ? since.get() : null, originalFormat); + return new ParsedJavadoc(description, since.orElse(null), deprecated.orElse(null), originalFormat); } public ParsedJavadocSection parseConfigSectionJavadoc(String javadocComment) { if (javadocComment == null || javadocComment.trim().isEmpty()) { - return new ParsedJavadocSection(null, null); + return ParsedJavadocSection.empty(); } // the parser expects all the lines to start with "* " @@ -128,6 +134,12 @@ public ParsedJavadocSection parseConfigSectionJavadoc(String javadocComment) { javadocComment = START_OF_LINE.matcher(javadocComment).replaceAll("* "); Javadoc javadoc = StaticJavaParser.parseJavadoc(javadocComment); + Optional deprecated = javadoc.getBlockTags().stream() + .filter(t -> t.getType() == Type.DEPRECATED) + .map(JavadocBlockTag::getContent) + .map(JavadocDescription::toText) + .findFirst(); + String asciidoc; if (isAsciidoc(javadoc)) { asciidoc = handleEolInAsciidoc(javadoc); @@ -136,7 +148,7 @@ public ParsedJavadocSection parseConfigSectionJavadoc(String javadocComment) { } if (asciidoc == null || asciidoc.isBlank()) { - return new ParsedJavadocSection(null, null); + return ParsedJavadocSection.empty(); } final int newLineIndex = asciidoc.indexOf(NEW_LINE); @@ -152,12 +164,12 @@ public ParsedJavadocSection parseConfigSectionJavadoc(String javadocComment) { if (endOfTitleIndex == -1) { final String title = asciidoc.replaceAll("^([^\\w])+", "").trim(); - return new ParsedJavadocSection(title, null); + return new ParsedJavadocSection(title, null, deprecated.orElse(null)); } else { final String title = asciidoc.substring(0, endOfTitleIndex).replaceAll("^([^\\w])+", "").trim(); final String details = asciidoc.substring(endOfTitleIndex + 1).trim(); - return new ParsedJavadocSection(title, details.isBlank() ? null : details); + return new ParsedJavadocSection(title, details.isBlank() ? null : details, deprecated.orElse(null)); } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java index 151dd87cd48f8..643ed96d28620 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java @@ -14,16 +14,16 @@ public sealed abstract class AbstractConfigItem implements Comparable configSections, Co public boolean hasDurationType() { for (AbstractConfigItem item : items) { - if (item.hasDurationType() && !item.deprecated) { + if (item.hasDurationType() && !item.isDeprecated()) { return true; } } @@ -115,7 +115,7 @@ public boolean hasDurationType() { public boolean hasMemorySizeType() { for (AbstractConfigItem item : items) { - if (item.hasMemorySizeType() && !item.deprecated) { + if (item.hasMemorySizeType() && !item.isDeprecated()) { return true; } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java index 8200970c5b87d..da33958996e84 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java @@ -12,8 +12,8 @@ public final class ConfigSection extends AbstractConfigItem implements ConfigIte private final int level; public ConfigSection(String sourceClass, String sourceName, SourceType sourceType, SectionPath path, String type, int level, - boolean generated, boolean deprecated) { - super(sourceClass, sourceName, sourceType, path, type, deprecated); + boolean generated, Deprecation deprecation) { + super(sourceClass, sourceName, sourceType, path, type, deprecation); this.generated = generated; this.level = level; } @@ -58,13 +58,13 @@ public int getLevel() { * It can happen when for instance a path is both used at a given level and in an unnamed map. * For instance in: HibernateOrmConfig. */ - public void appendState(boolean generated, boolean deprecated) { + public void appendState(boolean generated, Deprecation deprecation) { // we generate the section if at least one of the sections should be generated // (the output will contain all the items of the section) this.generated = this.generated || generated; // we unmark the section as deprecated if one of the merged section is not deprecated // as we will have to generate the section - this.deprecated = this.deprecated && deprecated; + this.deprecation = this.deprecation != null && deprecation != null ? this.deprecation : null; } /** @@ -94,7 +94,7 @@ public void merge(ConfigSection other, Map existingConfig @Override public boolean hasDurationType() { for (AbstractConfigItem item : items) { - if (item.hasDurationType() && !item.deprecated) { + if (item.hasDurationType() && !item.isDeprecated()) { return true; } } @@ -104,7 +104,7 @@ public boolean hasDurationType() { @Override public boolean hasMemorySizeType() { for (AbstractConfigItem item : items) { - if (item.hasMemorySizeType() && !item.deprecated) { + if (item.hasMemorySizeType() && !item.isDeprecated()) { return true; } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Deprecation.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Deprecation.java new file mode 100644 index 0000000000000..2f8b4e18fee30 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Deprecation.java @@ -0,0 +1,5 @@ +package io.quarkus.annotation.processor.documentation.config.model; + +public record Deprecation(String since, String replacement, String reason) { + +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java index 0ba9cd41526a5..9ef41190a3268 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/JavadocElements.java @@ -6,7 +6,7 @@ public record JavadocElements(Extension extension, Map elements) { - public record JavadocElement(String description, String since, @JsonIgnore String rawJavadoc) { + public record JavadocElement(String description, String since, String deprecated, @JsonIgnore String rawJavadoc) { } public boolean isEmpty() { diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java index a71bd8348ce20..d02e20f324b80 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java @@ -26,6 +26,7 @@ import io.quarkus.annotation.processor.documentation.config.model.ConfigRoot; import io.quarkus.annotation.processor.documentation.config.model.ConfigSection; import io.quarkus.annotation.processor.documentation.config.model.ConfigSection.SectionPath; +import io.quarkus.annotation.processor.documentation.config.model.Deprecation; import io.quarkus.annotation.processor.documentation.config.model.EnumAcceptedValues; import io.quarkus.annotation.processor.documentation.config.model.EnumAcceptedValues.EnumAcceptedValue; import io.quarkus.annotation.processor.documentation.config.model.JavadocElements; @@ -76,7 +77,7 @@ public ResolvedModel resolveModel() { configRoot.addQualifiedName(discoveryConfigRoot.getQualifiedName()); ResolutionContext context = new ResolutionContext(configRoot.getPrefix(), new ArrayList<>(), discoveryConfigRoot, - configRoot, 0, false, false, false); + configRoot, 0, false, false, null); for (DiscoveryConfigProperty discoveryConfigProperty : discoveryConfigRoot.getProperties().values()) { resolveProperty(configRoot, existingRootConfigSections, discoveryConfigRoot.getPhase(), context, discoveryConfigProperty); @@ -95,7 +96,8 @@ private void resolveProperty(ConfigRoot configRoot, Map e List additionalPaths = context.getAdditionalPaths().stream() .map(p -> appendPath(p, discoveryConfigProperty.getPath())) .collect(Collectors.toCollection(ArrayList::new)); - boolean deprecated = context.isDeprecated() || discoveryConfigProperty.isDeprecated(); + Deprecation deprecation = discoveryConfigProperty.getDeprecation() != null ? discoveryConfigProperty.getDeprecation() + : context.getDeprecation(); String typeQualifiedName = discoveryConfigProperty.getType().qualifiedName(); @@ -130,22 +132,22 @@ private void resolveProperty(ConfigRoot configRoot, Map e ConfigSection configSection = existingRootConfigSections.get(path); if (configSection != null) { - configSection.appendState(discoveryConfigProperty.isSectionGenerated(), deprecated); + configSection.appendState(discoveryConfigProperty.isSectionGenerated(), deprecation); } else { configSection = new ConfigSection(discoveryConfigProperty.getSourceClass(), discoveryConfigProperty.getSourceName(), discoveryConfigProperty.getSourceType(), new SectionPath(path), typeQualifiedName, - context.getSectionLevel(), discoveryConfigProperty.isSectionGenerated(), deprecated); + context.getSectionLevel(), discoveryConfigProperty.isSectionGenerated(), deprecation); context.getItemCollection().addItem(configSection); existingRootConfigSections.put(path, configSection); } configGroupContext = new ResolutionContext(potentiallyMappedPath, additionalPaths, discoveryConfigGroup, - configSection, context.getSectionLevel() + 1, isWithinMap, isWithMapWithUnnamedKey, deprecated); + configSection, context.getSectionLevel() + 1, isWithinMap, isWithMapWithUnnamedKey, deprecation); } else { configGroupContext = new ResolutionContext(potentiallyMappedPath, additionalPaths, discoveryConfigGroup, context.getItemCollection(), context.getSectionLevel(), isWithinMap, isWithMapWithUnnamedKey, - deprecated); + deprecation); } for (DiscoveryConfigProperty configGroupProperty : discoveryConfigGroup.getProperties().values()) { @@ -212,7 +214,7 @@ private void resolveProperty(ConfigRoot configRoot, Map e discoveryConfigProperty.getType().isEnum(), enumAcceptedValues, defaultValue, JavadocUtil.getJavadocSiteLink(typeBinaryName), - deprecated); + deprecation); context.getItemCollection().addItem(configProperty); } } @@ -262,18 +264,18 @@ private static class ResolutionContext { private final int sectionLevel; private final boolean withinMap; private final boolean withinMapWithUnnamedKey; - private final boolean deprecated; + private final Deprecation deprecation; private ResolutionContext(String path, List additionalPaths, DiscoveryRootElement discoveryRootElement, ConfigItemCollection itemCollection, - int sectionLevel, boolean withinMap, boolean withinMapWithUnnamedKey, boolean deprecated) { + int sectionLevel, boolean withinMap, boolean withinMapWithUnnamedKey, Deprecation deprecation) { this.path = path; this.additionalPaths = additionalPaths; this.discoveryRootElement = discoveryRootElement; this.itemCollection = itemCollection; this.withinMap = withinMap; this.withinMapWithUnnamedKey = withinMapWithUnnamedKey; - this.deprecated = deprecated; + this.deprecation = deprecation; this.sectionLevel = sectionLevel; } @@ -305,8 +307,8 @@ public boolean isWithinMapWithUnnamedKey() { return withinMapWithUnnamedKey; } - public boolean isDeprecated() { - return deprecated; + public Deprecation getDeprecation() { + return deprecation; } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java index 54ce54500882f..4ab20e4ee24e3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java @@ -69,6 +69,13 @@ public void onResolvedEnum(TypeElement enumTypeElement) { protected void handleCommonPropertyAnnotations(DiscoveryConfigProperty.Builder builder, Map propertyAnnotations, ResolvedType resolvedType, String sourceName) { + AnnotationMirror deprecatedAnnotation = propertyAnnotations.get(Deprecated.class.getName()); + if (deprecatedAnnotation != null) { + String since = (String) utils.element().getAnnotationValues(deprecatedAnnotation).get("since"); + // TODO add more information about the deprecation, typically the reason and a replacement + builder.deprecated(since, null, null); + } + AnnotationMirror configDocSectionAnnotation = propertyAnnotations.get(Types.ANNOTATION_CONFIG_DOC_SECTION); if (configDocSectionAnnotation != null) { Boolean sectionGenerated = (Boolean) utils.element().getAnnotationValues(configDocSectionAnnotation) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java index 65dacda0e95b3..1300d0b9b7e67 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractJavadocConfigListener.java @@ -47,7 +47,8 @@ public void onResolvedEnum(TypeElement enumTypeElement) { configCollector.addJavadocElement( enumTypeElement.getQualifiedName().toString() + Markers.DOT + enumElement.getSimpleName() .toString(), - new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), rawJavadoc.get())); + new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), parsedJavadoc.deprecated(), + rawJavadoc.get())); } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java index 8fc04a9e77547..d6f790f7ecce3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java @@ -129,12 +129,6 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem DiscoveryConfigProperty.Builder builder = DiscoveryConfigProperty.builder(clazz.getQualifiedName().toString(), sourceName, SourceType.METHOD, resolvedType); - AnnotationMirror deprecatedAnnotation = methodAnnotations.get(Deprecated.class.getName()); - if (deprecatedAnnotation != null) { - builder.deprecated(); - // TODO add more information about the deprecated forRemoval/since/comment - } - String name = ConfigNamingUtil.hyphenate(sourceName); AnnotationMirror withNameAnnotation = methodAnnotations.get(Types.ANNOTATION_CONFIG_WITH_NAME); if (withNameAnnotation != null) { diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java index 385fbf401fbe1..121a4affbab36 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocConfigMappingListener.java @@ -56,13 +56,15 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem configCollector.addJavadocElement( clazz.getQualifiedName().toString() + Markers.DOT + method.getSimpleName().toString(), - new JavadocElement(parsedJavadocSection.title(), null, rawJavadoc.get())); + new JavadocElement(parsedJavadocSection.title(), null, parsedJavadocSection.deprecated(), + rawJavadoc.get())); } else { ParsedJavadoc parsedJavadoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(rawJavadoc.get()); configCollector.addJavadocElement( clazz.getQualifiedName().toString() + Markers.DOT + method.getSimpleName().toString(), - new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), rawJavadoc.get())); + new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), parsedJavadoc.deprecated(), + rawJavadoc.get())); } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java index 8b08f37a6fac4..bfbc991d0ff28 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/JavadocLegacyConfigRootListener.java @@ -56,13 +56,15 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme configCollector.addJavadocElement( clazz.getQualifiedName().toString() + Markers.DOT + field.getSimpleName().toString(), - new JavadocElement(parsedJavadocSection.title(), null, rawJavadoc.get())); + new JavadocElement(parsedJavadocSection.title(), null, parsedJavadocSection.deprecated(), + rawJavadoc.get())); } else { ParsedJavadoc parsedJavadoc = JavadocToAsciidocTransformer.INSTANCE.parseConfigItemJavadoc(rawJavadoc.get()); configCollector.addJavadocElement( clazz.getQualifiedName().toString() + Markers.DOT + field.getSimpleName().toString(), - new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), rawJavadoc.get())); + new JavadocElement(parsedJavadoc.description(), parsedJavadoc.since(), parsedJavadoc.deprecated(), + rawJavadoc.get())); } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java index 9e17e76c05abb..301129548fd90 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java @@ -129,12 +129,6 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme DiscoveryConfigProperty.Builder builder = DiscoveryConfigProperty.builder(clazz.getQualifiedName().toString(), sourceName, SourceType.FIELD, resolvedType); - AnnotationMirror deprecatedAnnotation = fieldAnnotations.get(Deprecated.class.getName()); - if (deprecatedAnnotation != null) { - builder.deprecated(); - // TODO add more information about the deprecated forRemoval/since/comment - } - AnnotationMirror configItemAnnotation = fieldAnnotations.get(Types.ANNOTATION_CONFIG_ITEM); if (configItemAnnotation != null) { Map configItemValues = utils.element().getAnnotationValues(configItemAnnotation); From a42be4dcff7fd4e52059c9ab938ea1c60a4e2ddd Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 30 Aug 2024 17:53:20 +0300 Subject: [PATCH 12/21] Fix OTel DelayedAttributes bean handling Relates to: https://github.com/keycloak/keycloak/issues/32490 --- .../deployment/tracing/TracerProcessor.java | 23 +++++++++---- .../runtime/tracing/TracerRecorder.java | 33 ++++++++++--------- .../runtime/tracing/cdi/TracerProducer.java | 6 ---- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java index 4f67ed23eacdc..557749f5c104a 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java @@ -15,6 +15,7 @@ import java.util.function.BooleanSupplier; import jakarta.enterprise.inject.spi.EventContext; +import jakarta.inject.Singleton; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; @@ -24,6 +25,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.IdGenerator; @@ -31,9 +33,9 @@ import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; -import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem; import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem.ObserverConfiguratorBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.DotNames; import io.quarkus.builder.Version; @@ -50,6 +52,7 @@ import io.quarkus.gizmo.ResultHandle; import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.SecurityEvents.SecurityEventType; +import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; import io.quarkus.opentelemetry.runtime.tracing.cdi.TracerProducer; import io.quarkus.opentelemetry.runtime.tracing.security.EndUserSpanProcessor; @@ -169,14 +172,22 @@ void dropNames( @BuildStep @Record(ExecutionTime.STATIC_INIT) - void staticInitSetup( + SyntheticBeanBuildItem setupDelayedAttribute(TracerRecorder recorder, ApplicationInfoBuildItem appInfo) { + return SyntheticBeanBuildItem.configure(DelayedAttributes.class).types(Attributes.class) + .supplier(recorder.delayedAttributes(Version.getVersion(), + appInfo.getName(), appInfo.getVersion())) + .scope(Singleton.class) + .setRuntimeInit() + .done(); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void setupSampler( TracerRecorder recorder, - ApplicationInfoBuildItem appInfo, - BeanContainerBuildItem beanContainerBuildItem, DropNonApplicationUrisBuildItem dropNonApplicationUris, DropStaticResourcesBuildItem dropStaticResources) { - recorder.setAttributes(beanContainerBuildItem.getValue(), Version.getVersion(), - appInfo.getName(), appInfo.getVersion()); + recorder.setupSampler( dropNonApplicationUris.getDropNames(), dropStaticResources.getDropNames()); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java index 393aa22568fb1..a2c082ead70ec 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java @@ -3,11 +3,11 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.semconv.ResourceAttributes; -import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.annotations.StaticInit; @@ -18,22 +18,24 @@ public class TracerRecorder { public static final Set dropStaticResourceTargets = new HashSet<>(); @StaticInit - public void setAttributes( - BeanContainer beanContainer, - String quarkusVersion, + public Supplier delayedAttributes(String quarkusVersion, String serviceName, String serviceVersion) { - - DelayedAttributes delayedAttributes = beanContainer.beanInstance(DelayedAttributes.class); - - delayedAttributes.setAttributesDelegate(Resource.getDefault() - .merge(Resource.create( - Attributes.of( - ResourceAttributes.SERVICE_NAME, serviceName, - ResourceAttributes.SERVICE_VERSION, serviceVersion, - ResourceAttributes.WEBENGINE_NAME, "Quarkus", - ResourceAttributes.WEBENGINE_VERSION, quarkusVersion))) - .getAttributes()); + return new Supplier<>() { + @Override + public DelayedAttributes get() { + var result = new DelayedAttributes(); + result.setAttributesDelegate(Resource.getDefault() + .merge(Resource.create( + Attributes.of( + ResourceAttributes.SERVICE_NAME, serviceName, + ResourceAttributes.SERVICE_VERSION, serviceVersion, + ResourceAttributes.WEBENGINE_NAME, "Quarkus", + ResourceAttributes.WEBENGINE_VERSION, quarkusVersion))) + .getAttributes()); + return result; + } + }; } @StaticInit @@ -43,4 +45,5 @@ public void setupSampler( dropNonApplicationUriTargets.addAll(dropNonApplicationUris); dropStaticResourceTargets.addAll(dropStaticResources); } + } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java index 6c44fb6edbca3..057110bdd4333 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java @@ -22,15 +22,9 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.quarkus.arc.DefaultBean; -import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; @Singleton public class TracerProducer { - @Produces - @Singleton - public DelayedAttributes getDelayedAttributes() { - return new DelayedAttributes(); - } @Produces @ApplicationScoped From 7c81d83b288c82c71905928c98ec73f1246e2b0f Mon Sep 17 00:00:00 2001 From: mariofusco Date: Fri, 30 Aug 2024 18:10:53 +0200 Subject: [PATCH 13/21] Fix Jackson serializers generation for interfaces and boxed primitive types --- .../processor/JacksonSerializerFactory.java | 37 +++++++++++++++++-- .../reactive/jackson/deployment/test/Cat.java | 4 ++ .../jackson/deployment/test/ContainerDTO.java | 4 ++ .../deployment/test/NestedInterface.java | 28 ++++++++++++++ .../deployment/test/SimpleJsonResource.java | 6 +++ .../deployment/test/SimpleJsonTest.java | 17 ++++++++- ...JsonWithReflectionFreeSerializersTest.java | 3 +- 7 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ContainerDTO.java create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/NestedInterface.java diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonSerializerFactory.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonSerializerFactory.java index 7ebd7ae9b40a4..691d102c35ddf 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonSerializerFactory.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonSerializerFactory.java @@ -318,7 +318,7 @@ private void writeField(ClassInfo classInfo, FieldSpecs fieldSpecs, BytecodeCrea if (primitiveMethodName != null) { MethodDescriptor primitiveWriter = MethodDescriptor.ofMethod(JSON_GEN_CLASS_NAME, primitiveMethodName, "void", - typeName); + fieldSpecs.writtenType()); bytecode.invokeVirtualMethod(primitiveWriter, jsonGenerator, arg); return; } @@ -372,7 +372,7 @@ private void registerTypeToBeGenerated(String typeName) { private String writeMethodForPrimitiveFields(String typeName) { return switch (typeName) { - case "java.lang.String" -> "writeString"; + case "java.lang.String", "char", "java.lang.Character" -> "writeString"; case "short", "java.lang.Short", "int", "java.lang.Integer", "long", "java.lang.Long", "float", "java.lang.Float", "double", "java.lang.Double" -> "writeNumber"; @@ -590,8 +590,37 @@ boolean hasUnknownAnnotation() { } ResultHandle toValueReaderHandle(BytecodeCreator bytecode, ResultHandle valueHandle) { - return methodInfo != null ? bytecode.invokeVirtualMethod(MethodDescriptor.of(methodInfo), valueHandle) - : bytecode.readInstanceField(FieldDescriptor.of(fieldInfo), valueHandle); + ResultHandle handle = accessorHandle(bytecode, valueHandle); + + handle = switch (fieldType.name().toString()) { + case "char", "java.lang.Character" -> bytecode.invokeStaticMethod( + MethodDescriptor.ofMethod(Character.class, "toString", String.class, char.class), handle); + default -> handle; + }; + + return handle; + } + + private ResultHandle accessorHandle(BytecodeCreator bytecode, ResultHandle valueHandle) { + if (methodInfo != null) { + if (methodInfo.declaringClass().isInterface()) { + return bytecode.invokeInterfaceMethod(MethodDescriptor.of(methodInfo), valueHandle); + } + return bytecode.invokeVirtualMethod(MethodDescriptor.of(methodInfo), valueHandle); + } + return bytecode.readInstanceField(FieldDescriptor.of(fieldInfo), valueHandle); + } + + String writtenType() { + return switch (fieldType.name().toString()) { + case "char", "java.lang.Character" -> "java.lang.String"; + case "java.lang.Integer" -> "int"; + case "java.lang.Short" -> "short"; + case "java.lang.Long" -> "long"; + case "java.lang.Double" -> "double"; + case "java.lang.Float" -> "float"; + default -> fieldType.name().toString(); + }; } private String[] rolesAllowed() { diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Cat.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Cat.java index e2aa7a01955cc..ab57f3ea4d3c4 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Cat.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Cat.java @@ -14,4 +14,8 @@ public int getPrivateAge() { public void setPrivateAge(int privateAge) { this.privateAge = privateAge; } + + public char getInitial() { + return getPublicName().charAt(0); + } } diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ContainerDTO.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ContainerDTO.java new file mode 100644 index 0000000000000..7638012b16f01 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ContainerDTO.java @@ -0,0 +1,4 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +public record ContainerDTO(NestedInterface nestedInterface) { +} diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/NestedInterface.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/NestedInterface.java new file mode 100644 index 0000000000000..aae283c86bafe --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/NestedInterface.java @@ -0,0 +1,28 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +public interface NestedInterface { + + NestedInterface INSTANCE = new NestedInterface() { + @Override + public String getString() { + return "response"; + } + + @Override + public Integer getInt() { + return 42; + } + + @Override + public char getCharacter() { + return 'a'; + } + }; + + String getString(); + + Integer getInt(); + + char getCharacter(); + +} diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java index a7da6fb828626..6a48584164760 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java @@ -442,6 +442,12 @@ public String genericInputTest(DataItem item) { return item.getContent().getName(); } + @GET + @Path("/interface") + public ContainerDTO interfaceTest() { + return new ContainerDTO(NestedInterface.INSTANCE); + } + public static class UnquotedFieldsPersonSerialization implements BiFunction { public static final AtomicInteger count = new AtomicInteger(); diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java index 0c9cdff2a766d..3db48396587fa 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java @@ -34,7 +34,8 @@ public JavaArchive get() { NoopReaderInterceptor.class, TestIdentityProvider.class, TestIdentityController.class, AbstractPet.class, Dog.class, Cat.class, Veterinarian.class, AbstractNamedPet.class, AbstractUnsecuredPet.class, UnsecuredPet.class, SecuredPersonInterface.class, Frog.class, - Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class) + Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class, ContainerDTO.class, + NestedInterface.class) .addAsResource(new StringAsset("admin-expression=admin\n" + "user-expression=user\n" + "birth-date-roles=alice,bob\n"), "application.properties"); @@ -506,6 +507,18 @@ public void testGenericInput() { .body(is("foo")); } + @Test + public void testInterface() { + RestAssured + .with() + .get("/simple/interface") + .then() + .statusCode(200) + .body("nestedInterface.int", Matchers.is(42)) + .body("nestedInterface.character", Matchers.is("a")) + .body("nestedInterface.string", Matchers.is("response")); + } + @Test public void testSecureFieldOnAbstractClass() { // implementor with / without @SecureField returned @@ -602,6 +615,7 @@ private static void testSecuredFieldOnAbstractClass(String catPath, String dogPa .then() .statusCode(200) .body("publicName", Matchers.is("Garfield")) + .body("initial", Matchers.is("G")) .body("privateName", Matchers.nullValue()) .body("veterinarian.name", Matchers.is("Dolittle")) .body("veterinarian.title", Matchers.nullValue()) @@ -625,6 +639,7 @@ private static void testSecuredFieldOnAbstractClass(String catPath, String dogPa .then() .statusCode(200) .body("publicName", Matchers.is("Garfield")) + .body("initial", Matchers.is("G")) .body("privateName", Matchers.is("Monday")) .body("privateAge", Matchers.is(4)) .body("veterinarian.name", Matchers.is("Dolittle")) diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java index 24fa1af4fb106..0254e48598fe8 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java @@ -24,7 +24,8 @@ public JavaArchive get() { NoopReaderInterceptor.class, TestIdentityProvider.class, TestIdentityController.class, AbstractPet.class, Dog.class, Cat.class, Veterinarian.class, AbstractNamedPet.class, AbstractUnsecuredPet.class, UnsecuredPet.class, SecuredPersonInterface.class, Frog.class, - Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class) + Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class, ContainerDTO.class, + NestedInterface.class) .addAsResource(new StringAsset("admin-expression=admin\n" + "user-expression=user\n" + "birth-date-roles=alice,bob\n" + From 639462292fbccd17be596105b240b6bd8a65161c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 30 Aug 2024 12:46:34 +0200 Subject: [PATCH 14/21] Gradle - Correctly merge classes dir when using dev mode We were returning an empty dir before which would trigger the use of the resources dir instead. Also simplify the logic as it was quite hard to follow and it's actually quite simple. Fixes #42860 --- .../io/quarkus/gradle/tasks/QuarkusDev.java | 2 +- .../gradle/tasks/QuarkusGradleUtils.java | 63 ++++++++----------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 8ffbba22d9ef9..d748b2d1e078b 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -633,7 +633,7 @@ private void addLocalProject(ResolvedDependency project, GradleDevModeLauncher.B } } Path classesDir = classesDirs.isEmpty() ? null - : QuarkusGradleUtils.mergeClassesDirs(classesDirs, project.getWorkspaceModule().getBuildDir(), root, false); + : QuarkusGradleUtils.mergeClassesDirs(classesDirs, project.getWorkspaceModule().getBuildDir(), true, false); Path generatedSourcesPath = sources.getSourceDirs().isEmpty() ? null : sources.getSourceDirs().iterator().next().getAptSourcesDir(); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java index fc38292cf7498..d5e924f6dd27c 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java @@ -7,7 +7,7 @@ import java.nio.file.Path; import java.util.Collection; import java.util.HashSet; -import java.util.Iterator; +import java.util.List; import java.util.Set; import org.gradle.api.Project; @@ -46,44 +46,35 @@ public static String getClassesDir(SourceSet sourceSet, File tmpDir, boolean pop } public static Path mergeClassesDirs(Collection classesDirs, File tmpDir, boolean populated, boolean test) { - Path classesDir = null; - final Iterator i = classesDirs.iterator(); - int dirCount = 0; - while (i.hasNext()) { - final Path next = i.next(); - if (!Files.exists(next)) { - continue; + List existingClassesDirs = classesDirs.stream().filter(p -> Files.exists(p)).toList(); + + if (existingClassesDirs.size() == 0) { + return null; + } + + if (existingClassesDirs.size() == 1) { + return existingClassesDirs.get(0); + } + + try { + Path mergedClassesDir = tmpDir.toPath().resolve("quarkus-app-classes" + (test ? "-test" : "")); + + if (!populated) { + return mergedClassesDir; } - try { - switch (dirCount++) { - case 0: - classesDir = next; - break; - case 1: - //there does not seem to be any sane way of dealing with multiple output dirs, as there does not seem - //to be a way to map them. We will need to address this at some point, but for now we just stick them - //all in a temp dir - final Path tmpClassesDir = tmpDir.toPath().resolve("quarkus-app-classes" + (test ? "-test" : "")); - if (!populated) { - return tmpClassesDir; - } - if (Files.exists(tmpClassesDir)) { - IoUtils.recursiveDelete(tmpClassesDir); - } - IoUtils.copy(classesDir, tmpClassesDir); - classesDir = tmpClassesDir; - default: - IoUtils.copy(next, classesDir); - - } - } catch (IOException e) { - throw new UncheckedIOException(ERROR_COLLECTING_PROJECT_CLASSES, e); + + if (Files.exists(mergedClassesDir)) { + IoUtils.recursiveDelete(mergedClassesDir); } + + for (Path classesDir : existingClassesDirs) { + IoUtils.copy(classesDir, mergedClassesDir); + } + + return mergedClassesDir; + } catch (IOException e) { + throw new UncheckedIOException(ERROR_COLLECTING_PROJECT_CLASSES, e); } - if (classesDir == null) { - return null; - } - return classesDir; } } From 7caa6d5567f9c91f475f89efe358d35d8e86e79d Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 30 Aug 2024 13:33:01 -0500 Subject: [PATCH 15/21] Update SmallRye Common to 2.6.0 Fixes #42858. Fixes part of #42248. --- bom/application/pom.xml | 2 +- .../io/quarkus/deployment/logging/LoggingResourceProcessor.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 03b8dcd23d3dd..4dec430b428fc 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -51,7 +51,7 @@ 2.1 2.0 3.1.1 - 2.5.0 + 2.6.0 3.9.1 4.1.0 4.0.0 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index 9505e778c425b..1cea8cf1406cb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -223,6 +223,7 @@ void miscSetup( Consumer provider) { runtimeInit.accept(new RuntimeReinitializedClassBuildItem(ConsoleHandler.class.getName())); runtimeInit.accept(new RuntimeReinitializedClassBuildItem("io.smallrye.common.ref.References$ReaperThread")); + runtimeInit.accept(new RuntimeReinitializedClassBuildItem("io.smallrye.common.os.Process")); systemProp .accept(new NativeImageSystemPropertyBuildItem("java.util.logging.manager", "org.jboss.logmanager.LogManager")); provider.accept( From ca9de5694ed499a1441ef5a6bd8388b4fe3491a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 22:00:06 +0000 Subject: [PATCH 16/21] Bump io.quarkus.bot:build-reporter-maven-extension from 3.9.1 to 3.9.2 Bumps [io.quarkus.bot:build-reporter-maven-extension](https://github.com/quarkusio/build-reporter) from 3.9.1 to 3.9.2. - [Commits](https://github.com/quarkusio/build-reporter/compare/3.9.1...3.9.2) --- updated-dependencies: - dependency-name: io.quarkus.bot:build-reporter-maven-extension dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45f8b17982219..5ba9858a497dc 100644 --- a/pom.xml +++ b/pom.xml @@ -183,7 +183,7 @@ io.quarkus.bot build-reporter-maven-extension - 3.9.1 + 3.9.2 From ffe4cb5330b6a07d41cb71fc535191bef02eef19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 22:03:19 +0000 Subject: [PATCH 17/21] Bump hibernate-search.version from 7.2.0.Final to 7.2.1.Final Bumps `hibernate-search.version` from 7.2.0.Final to 7.2.1.Final. Updates `org.hibernate.search:hibernate-search-bom` from 7.2.0.Final to 7.2.1.Final - [Release notes](https://github.com/hibernate/hibernate-search/releases) - [Changelog](https://github.com/hibernate/hibernate-search/blob/7.2.1.Final/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-search/compare/7.2.0.Final...7.2.1.Final) Updates `org.hibernate.search:hibernate-search-mapper-orm` from 7.2.0.Final to 7.2.1.Final - [Release notes](https://github.com/hibernate/hibernate-search/releases) - [Changelog](https://github.com/hibernate/hibernate-search/blob/7.2.1.Final/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-search/compare/7.2.0.Final...7.2.1.Final) Updates `org.hibernate.search:hibernate-search-engine` from 7.2.0.Final to 7.2.1.Final - [Release notes](https://github.com/hibernate/hibernate-search/releases) - [Changelog](https://github.com/hibernate/hibernate-search/blob/7.2.1.Final/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-search/compare/7.2.0.Final...7.2.1.Final) Updates `org.hibernate.search:hibernate-search-mapper-pojo-base` from 7.2.0.Final to 7.2.1.Final - [Release notes](https://github.com/hibernate/hibernate-search/releases) - [Changelog](https://github.com/hibernate/hibernate-search/blob/7.2.1.Final/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-search/compare/7.2.0.Final...7.2.1.Final) Updates `org.hibernate.search:hibernate-search-util-common` from 7.2.0.Final to 7.2.1.Final - [Release notes](https://github.com/hibernate/hibernate-search/releases) - [Changelog](https://github.com/hibernate/hibernate-search/blob/7.2.1.Final/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-search/compare/7.2.0.Final...7.2.1.Final) --- updated-dependencies: - dependency-name: org.hibernate.search:hibernate-search-bom dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.hibernate.search:hibernate-search-mapper-orm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.hibernate.search:hibernate-search-engine dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.hibernate.search:hibernate-search-mapper-pojo-base dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.hibernate.search:hibernate-search-util-common dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45f8b17982219..9506e7d1792a3 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 7.0.1.Final 2.4.0.Final 8.0.1.Final - 7.2.0.Final + 7.2.1.Final 1.65.1 From d973e4d16a5cc74ad05a53d6c54e7c484bb05770 Mon Sep 17 00:00:00 2001 From: xstefank Date: Sat, 31 Aug 2024 08:58:22 +0200 Subject: [PATCH 18/21] Add missing dot to GraalVM not found message --- .../io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 937f5285edf8e..d0c07388df714 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -355,7 +355,7 @@ public NativeImageRunnerBuildItem resolveNativeImageBuildRunner(NativeConfig nat } String executableName = getNativeImageExecutableName(); String errorMessage = "Cannot find the `" + executableName - + "` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image`"; + + "` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image`."; if (!SystemUtils.IS_OS_LINUX) { // Delay the error: if we're just building native sources, we may not need the build runner at all. return new NativeImageRunnerBuildItem(new NativeImageBuildRunnerError(errorMessage)); From 50cce0094a9a9847f790c47a219c832ae95d1539 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Aug 2024 07:59:39 +0000 Subject: [PATCH 19/21] Bump io.smallrye.common:smallrye-common-bom from 2.5.0 to 2.6.0 Bumps [io.smallrye.common:smallrye-common-bom](https://github.com/smallrye/smallrye-common) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/smallrye/smallrye-common/releases) - [Commits](https://github.com/smallrye/smallrye-common/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: io.smallrye.common:smallrye-common-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 9b468e6681ec8..9e10e40b5f590 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -70,7 +70,7 @@ 1.26 2.0 3.5.1 - 2.5.0 + 2.6.0 1.5.2 8.9 0.0.10 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 36c412d48459c..f953417f1f0b8 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -57,7 +57,7 @@ 3.1.0 2.6.2 - 2.5.0 + 2.6.0 4.5.9 5.5.0 1.0.0.Final From 59bd3858ac730fe0d0691427e2b9082d4797dc25 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 31 Aug 2024 15:35:30 +0200 Subject: [PATCH 20/21] Config Doc - Avoid annotations in primitive type name While this was already correctly handled for declared types, primitive types don't have an API to get the name without the annotations being present (toString() contains the annotations and there is no other way to get a string representation of the type...). Fixing it by making sure we get to the type name. This avoids having things like: @io.smallrye.config.WithConverter(io.quarkus.runtime.init.InitRuntimeConfig.BooleanConverter.class) boolean in the doc. Or Bean Validation constraints when they are used. --- .../config/discovery/ResolvedType.java | 6 ++-- .../config/resolver/ConfigResolver.java | 4 +-- .../scanner/ConfigAnnotationScanner.java | 7 +++-- .../config/scanner/ConfigMappingListener.java | 2 +- .../scanner/LegacyConfigRootListener.java | 2 +- .../processor/util/ElementUtil.java | 28 +++++++++++++++++++ 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java index f10b93b94fa68..01d845e500a40 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/discovery/ResolvedType.java @@ -34,10 +34,8 @@ public final String toString() { return unwrappedType.toString(); } - public static ResolvedType ofPrimitive(TypeMirror unwrappedType) { - String primitiveName = unwrappedType.toString(); - - return new ResolvedType(unwrappedType, unwrappedType, primitiveName, primitiveName, primitiveName, true, false, false, + public static ResolvedType ofPrimitive(TypeMirror unwrappedType, String typeName) { + return new ResolvedType(unwrappedType, unwrappedType, typeName, typeName, typeName, true, false, false, false, false, false, false, false, false, false); } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java index d02e20f324b80..d6fa963d6dd12 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java @@ -183,7 +183,7 @@ private void resolveProperty(ConfigRoot configRoot, Map e if (discoveryConfigProperty.getType().isMap()) { // it is a leaf pass through map, it is always optional optional = true; - typeQualifiedName = discoveryConfigProperty.getType().wrapperType().toString(); + typeQualifiedName = utils.element().getQualifiedName(discoveryConfigProperty.getType().wrapperType()); typeSimplifiedName = utils.element().simplifyGenericType(discoveryConfigProperty.getType().wrapperType()); potentiallyMappedPath += ConfigNamingUtil.getMapKey(discoveryConfigProperty.getMapKey()); @@ -191,7 +191,7 @@ private void resolveProperty(ConfigRoot configRoot, Map e .map(p -> p + ConfigNamingUtil.getMapKey(discoveryConfigProperty.getMapKey())) .collect(Collectors.toCollection(ArrayList::new)); } else if (discoveryConfigProperty.getType().isList()) { - typeQualifiedName = discoveryConfigProperty.getType().wrapperType().toString(); + typeQualifiedName = utils.element().getQualifiedName(discoveryConfigProperty.getType().wrapperType()); } PropertyPath propertyPath = new PropertyPath(potentiallyMappedPath, diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigAnnotationScanner.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigAnnotationScanner.java index 5ae2110b70d55..0ee2a08b30455 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigAnnotationScanner.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigAnnotationScanner.java @@ -197,7 +197,8 @@ private void scanElement(List listeners, DiscoveryRoot } } else { TypeMirror superclass = clazz.getSuperclass(); - if (superclass.getKind() != TypeKind.NONE && !superclass.toString().equals(Object.class.getName())) { + if (superclass.getKind() != TypeKind.NONE + && !utils.element().getQualifiedName(superclass).equals(Object.class.getName())) { TypeElement superclassTypeElement = (TypeElement) ((DeclaredType) superclass).asElement(); debug("Detected superclass: " + superclassTypeElement, clazz); @@ -324,7 +325,7 @@ private boolean isEnumAlreadyHandled(TypeElement clazz) { private ResolvedType resolveType(TypeMirror typeMirror) { if (typeMirror.getKind().isPrimitive()) { - return ResolvedType.ofPrimitive(typeMirror); + return ResolvedType.ofPrimitive(typeMirror, utils.element().getQualifiedName(typeMirror)); } if (typeMirror.getKind() == TypeKind.ARRAY) { ResolvedType resolvedType = resolveType(((ArrayType) typeMirror).getComponentType()); @@ -369,7 +370,7 @@ private ResolvedType resolveType(TypeMirror typeMirror) { isConfigGroup = utils.element().isAnnotationPresent(typeElement, Types.ANNOTATION_CONFIG_GROUP); } else if (typeElement.getKind() == ElementKind.CLASS) { isClass = true; - isDuration = typeMirror.toString().equals(Duration.class.getName()); + isDuration = utils.element().getQualifiedName(typeMirror).equals(Duration.class.getName()); isConfigGroup = utils.element().isAnnotationPresent(typeElement, Types.ANNOTATION_CONFIG_GROUP); } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java index d6f790f7ecce3..1847c8e203503 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java @@ -43,7 +43,7 @@ public Optional onConfigRoot(TypeElement configRoot) { AnnotationMirror configDocFileNameAnnotation = null; for (AnnotationMirror annotationMirror : configRoot.getAnnotationMirrors()) { - String annotationName = annotationMirror.getAnnotationType().toString(); + String annotationName = utils.element().getQualifiedName(annotationMirror.getAnnotationType()); if (annotationName.equals(Types.ANNOTATION_CONFIG_ROOT)) { configRootAnnotation = annotationMirror; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java index 301129548fd90..6a1ca922d3da3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java @@ -44,7 +44,7 @@ public Optional onConfigRoot(TypeElement configRoot) { AnnotationMirror configDocFileNameAnnotation = null; for (AnnotationMirror annotationMirror : configRoot.getAnnotationMirrors()) { - String annotationName = annotationMirror.getAnnotationType().toString(); + String annotationName = utils.element().getQualifiedName(annotationMirror.getAnnotationType()); if (annotationName.equals(Types.ANNOTATION_CONFIG_ROOT)) { configRootAnnotation = annotationMirror; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java index a62011274c905..eaf41d1ec52a5 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/util/ElementUtil.java @@ -29,6 +29,34 @@ public class ElementUtil { this.processingEnv = processingEnv; } + public String getQualifiedName(TypeMirror type) { + switch (type.getKind()) { + case BOOLEAN: + return "boolean"; + case BYTE: + return "byte"; + case CHAR: + return "char"; + case DOUBLE: + return "double"; + case FLOAT: + return "float"; + case INT: + return "int"; + case LONG: + return "long"; + case SHORT: + return "short"; + case DECLARED: + return ((TypeElement) ((DeclaredType) type).asElement()).getQualifiedName().toString(); + default: + // note that it includes annotations, which is something we don't want + // thus why all this additional work above... + // this default should never be triggered AFAIK, it's there to be extra safe + return type.toString(); + } + } + public String getBinaryName(TypeElement clazz) { return processingEnv.getElementUtils().getBinaryName(clazz).toString(); } From 01ea052235e9a6f36b762e11916d506bdddfd3a5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 31 Aug 2024 18:03:20 +0200 Subject: [PATCH 21/21] Config Doc - Fix base URL of JDK classes Apparently it has changed to include the module name so let's update it. --- .../processor/documentation/config/util/JavadocUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java index 0e4115d278b86..8ca9e307b0ca0 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/util/JavadocUtil.java @@ -9,7 +9,7 @@ public final class JavadocUtil { static final String VERTX_JAVA_DOC_SITE = "https://vertx.io/docs/apidocs/"; - static final String OFFICIAL_JAVA_DOC_BASE_LINK = "https://docs.oracle.com/en/java/javase/17/docs/api/"; + static final String OFFICIAL_JAVA_DOC_BASE_LINK = "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/"; static final String AGROAL_API_JAVA_DOC_SITE = "https://javadoc.io/doc/io.agroal/agroal-api/latest/"; static final String LOG_LEVEL_REDIRECT_URL = "https://javadoc.io/doc/org.jboss.logmanager/jboss-logmanager/latest/org/jboss/logmanager/Level.html";