From 5369d7ff233d3afe84ecd9160c541fba52b38e69 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 24 Mar 2023 19:57:33 +0000 Subject: [PATCH 001/333] Encrypt OIDC session cookie value by default --- .../io/quarkus/oidc/OidcTenantConfig.java | 8 +-- .../runtime/CodeAuthenticationMechanism.java | 9 ++-- .../runtime/DefaultTokenStateManager.java | 4 +- .../io/quarkus/oidc/runtime/OidcUtils.java | 4 +- .../oidc/runtime/TenantConfigContext.java | 34 +++++++++---- .../src/main/resources/application.properties | 2 +- .../io/quarkus/it/keycloak/CodeFlowTest.java | 51 +++++++++++++------ .../keycloak/CustomTenantConfigResolver.java | 1 + .../src/main/resources/application.properties | 2 +- .../keycloak/CodeFlowAuthorizationTest.java | 33 ++++++++---- 10 files changed, 98 insertions(+), 50 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index dd2e9435a00811..63cac12d78d680 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -374,8 +374,8 @@ public enum Strategy { /** * Requires that the tokens are encrypted before being stored in the cookies. */ - @ConfigItem(defaultValueDocumentation = "false") - public Optional encryptionRequired = Optional.empty(); + @ConfigItem(defaultValue = "true") + public boolean encryptionRequired = true; /** * Secret which will be used to encrypt the tokens. @@ -385,12 +385,12 @@ public enum Strategy { @ConfigItem public Optional encryptionSecret = Optional.empty(); - public Optional isEncryptionRequired() { + public boolean isEncryptionRequired() { return encryptionRequired; } public void setEncryptionRequired(boolean encryptionRequired) { - this.encryptionRequired = Optional.of(encryptionRequired); + this.encryptionRequired = encryptionRequired; } public Optional getEncryptionSecret() { 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 0dde01e5b97c2c..1a4592bf8dd997 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 @@ -287,10 +287,7 @@ public Uni apply(SecurityIdentity identity) { if (isBackChannelLogoutPendingAndValid(configContext, identity) || isFrontChannelLogoutValid(context, configContext, identity)) { - return OidcUtils - .removeSessionCookie(context, configContext.oidcConfig, - sessionCookie.getName(), - resolver.getTokenStateManager()) + return removeSessionCookie(context, configContext.oidcConfig) .map(new Function() { @Override public Void apply(Void t) { @@ -742,7 +739,9 @@ public Throwable apply(Throwable tInner) { LOG.debugf("Starting the final redirect"); return tInner; } - LOG.errorf("ID token verification has failed: %s", tInner.getMessage()); + String message = tInner.getCause() != null ? tInner.getCause().getMessage() + : tInner.getMessage(); + LOG.errorf("ID token verification has failed: %s", message); return new AuthenticationCompletionException(tInner); } }); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java index 0b8bc172cc2bb7..01c622f4bab363 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java @@ -129,7 +129,7 @@ private static String getRefreshTokenCookieName(OidcTenantConfig oidcConfig) { } private String encryptToken(String token, RoutingContext context, OidcTenantConfig oidcConfig) { - if (oidcConfig.tokenStateManager.encryptionRequired.orElse(false)) { + if (oidcConfig.tokenStateManager.encryptionRequired) { TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); try { return OidcUtils.encryptString(token, configContext.getTokenEncSecretKey()); @@ -141,7 +141,7 @@ private String encryptToken(String token, RoutingContext context, OidcTenantConf } private String decryptToken(String token, RoutingContext context, OidcTenantConfig oidcConfig) { - if (oidcConfig.tokenStateManager.encryptionRequired.orElse(false)) { + if (oidcConfig.tokenStateManager.encryptionRequired) { TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); try { return OidcUtils.decryptString(token, configContext.getTokenEncSecretKey()); 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 a9b245bf4e1da0..1141e45b6418f3 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 @@ -455,7 +455,7 @@ public static String encryptJson(JsonObject json, SecretKey key) throws Exceptio public static String encryptString(String jweString, SecretKey key) throws Exception { JsonWebEncryption jwe = new JsonWebEncryption(); - jwe.setAlgorithmHeaderValue(KeyEncryptionAlgorithm.A256KW.getAlgorithm()); + jwe.setAlgorithmHeaderValue(KeyEncryptionAlgorithm.A256GCMKW.getAlgorithm()); jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithm.A256GCM.getAlgorithm()); jwe.setKey(key); jwe.setPlaintext(jweString); @@ -467,7 +467,7 @@ public static JsonObject decryptJson(String jweString, Key key) throws Exception } public static String decryptString(String jweString, Key key) throws Exception { - return decryptString(jweString, key, KeyEncryptionAlgorithm.A256KW); + return decryptString(jweString, key, KeyEncryptionAlgorithm.A256GCMKW); } public static String decryptString(String jweString, Key key, KeyEncryptionAlgorithm algorithm) throws JoseException { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java index 4e1e2190307a07..674320d38ebf0d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java @@ -1,12 +1,20 @@ package io.quarkus.oidc.runtime; +import java.nio.charset.StandardCharsets; + +import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.jboss.logging.Logger; +import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.smallrye.jwt.util.KeyUtils; public class TenantConfigContext { + private static final Logger LOG = Logger.getLogger(TenantConfigContext.class); /** * OIDC Provider @@ -39,8 +47,8 @@ public TenantConfigContext(OidcProvider client, OidcTenantConfig config, boolean this.oidcConfig = config; this.ready = ready; - pkceSecretKey = createPkceSecretKey(config); - tokenEncSecretKey = createTokenEncSecretKey(config); + pkceSecretKey = provider != null && provider.client != null ? createPkceSecretKey(config) : null; + tokenEncSecretKey = provider != null && provider.client != null ? createTokenEncSecretKey(config) : null; } private static SecretKey createPkceSecretKey(OidcTenantConfig config) { @@ -59,16 +67,24 @@ private static SecretKey createPkceSecretKey(OidcTenantConfig config) { } private static SecretKey createTokenEncSecretKey(OidcTenantConfig config) { - if (config.tokenStateManager.encryptionRequired.orElse(false)) { + if (config.tokenStateManager.encryptionRequired) { String encSecret = config.tokenStateManager.encryptionSecret .orElse(OidcCommonUtils.clientSecret(config.credentials)); - if (encSecret == null) { - throw new RuntimeException("Secret key for encrypting tokens is missing"); - } - if (encSecret.length() != 32) { - throw new RuntimeException("Secret key for encrypting tokens must be 32 characters long"); + try { + if (encSecret == null) { + LOG.warn("Secret key for encrypting tokens is missing, auto-generating it"); + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(256); + return keyGenerator.generateKey(); + } + byte[] secretBytes = encSecret.getBytes(StandardCharsets.UTF_8); + if (secretBytes.length < 32) { + LOG.warn("Secret key for encrypting tokens should be 32 characters long"); + } + return new SecretKeySpec(OidcUtils.getSha256Digest(secretBytes), "AES"); + } catch (Exception ex) { + throw new OIDCException(ex); } - return KeyUtils.createSecretKeyFromSecret(encSecret); } return null; } 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 e6f10e50207a9a..2df9b465b5a004 100644 --- a/integration-tests/oidc-code-flow/src/main/resources/application.properties +++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties @@ -10,6 +10,7 @@ quarkus.oidc.authentication.cookie-domain=localhost quarkus.oidc.authentication.extra-params.max-age=60 quarkus.oidc.application-type=web-app quarkus.oidc.authentication.cookie-suffix=test +quarkus.oidc.token-state-manager.encryption-required=false # OIDC client configuration quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} @@ -145,7 +146,6 @@ quarkus.oidc.tenant-split-tokens.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc.tenant-split-tokens.client-id=quarkus-app quarkus.oidc.tenant-split-tokens.credentials.secret=secret quarkus.oidc.tenant-split-tokens.token-state-manager.split-tokens=true -quarkus.oidc.tenant-split-tokens.token-state-manager.encryption-required=true quarkus.oidc.tenant-split-tokens.token-state-manager.encryption-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU quarkus.oidc.tenant-split-tokens.application-type=web-app 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 a67cd4eb3f94e3..43eb3ff67f284a 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 @@ -17,6 +17,9 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + import org.hamcrest.Matchers; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -275,7 +278,15 @@ public void testCodeFlowForceHttpsRedirectUriWithQueryAndPkce() throws Exception Cookie sessionCookie = getSessionCookie(webClient, "tenant-https_test"); assertNotNull(sessionCookie); - JsonObject idToken = OidcUtils.decodeJwtContent(sessionCookie.getValue().split("\\|")[0]); + + String encryptedIdToken = sessionCookie.getValue().split("\\|")[0]; + + SecretKey key = new SecretKeySpec(OidcUtils + .getSha256Digest("secret".getBytes(StandardCharsets.UTF_8)), + "AES"); + String encodedIdToken = OidcUtils.decryptString(encryptedIdToken, key); + + JsonObject idToken = OidcUtils.decodeJwtContent(encodedIdToken); String expiresAt = idToken.getInteger("exp").toString(); page = webClient.getPage(endpointLocationWithoutQueryUri.toURL()); String response = page.getBody().asNormalizedText(); @@ -850,7 +861,7 @@ public void testDefaultSessionManagerIdTokenOnly() throws IOException, Interrupt assertEquals("tenant-idtoken-only:no refresh", page.getBody().asNormalizedText()); Cookie idTokenCookie = getSessionCookie(page.getWebClient(), "tenant-idtoken-only"); - checkSingleTokenCookie(idTokenCookie, "ID"); + checkSingleTokenCookie(idTokenCookie, "ID", "secret"); assertNull(getSessionAtCookie(webClient, "tenant-idtoken-only")); assertNull(getSessionRtCookie(webClient, "tenant-idtoken-only")); @@ -860,7 +871,7 @@ public void testDefaultSessionManagerIdTokenOnly() throws IOException, Interrupt } @Test - public void testDefaultSessionManagerIdRefreshTokens() throws IOException, InterruptedException { + public void testDefaultSessionManagerIdRefreshTokens() throws Exception { try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/web-app/tenant-id-refresh-token"); assertNotNull(getStateCookie(webClient, "tenant-id-refresh-token")); @@ -881,11 +892,16 @@ public void testDefaultSessionManagerIdRefreshTokens() throws IOException, Inter assertEquals("tenant-id-refresh-token:RT injected", page.getBody().asNormalizedText()); Cookie idTokenCookie = getSessionCookie(page.getWebClient(), "tenant-id-refresh-token"); + + SecretKey key = new SecretKeySpec(OidcUtils + .getSha256Digest("secret".getBytes(StandardCharsets.UTF_8)), + "AES"); + String[] parts = idTokenCookie.getValue().split("\\|"); assertEquals(3, parts.length); - assertEquals("ID", OidcUtils.decodeJwtContent(parts[0]).getString("typ")); + assertEquals("ID", OidcUtils.decodeJwtContent(OidcUtils.decryptString(parts[0], key)).getString("typ")); assertEquals("", parts[1]); - assertEquals("Refresh", OidcUtils.decodeJwtContent(parts[2]).getString("typ")); + assertEquals("Refresh", OidcUtils.decodeJwtContent(OidcUtils.decryptString(parts[2], key)).getString("typ")); assertNull(getSessionAtCookie(webClient, "tenant-id-refresh-token")); assertNull(getSessionRtCookie(webClient, "tenant-id-refresh-token")); @@ -916,14 +932,15 @@ public void testDefaultSessionManagerSplitTokens() throws IOException, Interrupt page = webClient.getPage("http://localhost:8081/web-app/refresh/tenant-split-tokens"); assertEquals("tenant-split-tokens:RT injected", page.getBody().asNormalizedText()); + final String decryptSecret = "eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU"; Cookie idTokenCookie = getSessionCookie(page.getWebClient(), "tenant-split-tokens"); - checkSingleTokenCookie(idTokenCookie, "ID", true); + checkSingleTokenCookie(idTokenCookie, "ID", decryptSecret); Cookie atTokenCookie = getSessionAtCookie(page.getWebClient(), "tenant-split-tokens"); - checkSingleTokenCookie(atTokenCookie, "Bearer", true); + checkSingleTokenCookie(atTokenCookie, "Bearer", decryptSecret); Cookie rtTokenCookie = getSessionRtCookie(page.getWebClient(), "tenant-split-tokens"); - checkSingleTokenCookie(rtTokenCookie, "Refresh", true); + checkSingleTokenCookie(rtTokenCookie, "Refresh", decryptSecret); // verify all the cookies are cleared after the session timeout webClient.getOptions().setRedirectEnabled(false); @@ -972,12 +989,12 @@ public void testDefaultSessionManagerIdRefreshSplitTokens() throws IOException, assertEquals("tenant-split-id-refresh-token:RT injected", page.getBody().asNormalizedText()); Cookie idTokenCookie = getSessionCookie(page.getWebClient(), "tenant-split-id-refresh-token"); - checkSingleTokenCookie(idTokenCookie, "ID"); + checkSingleTokenCookie(idTokenCookie, "ID", "secret"); assertNull(getSessionAtCookie(page.getWebClient(), "tenant-split-id-refresh-token")); Cookie rtTokenCookie = getSessionRtCookie(page.getWebClient(), "tenant-split-id-refresh-token"); - checkSingleTokenCookie(rtTokenCookie, "Refresh"); + checkSingleTokenCookie(rtTokenCookie, "Refresh", "secret"); // verify all the cookies are cleared after the session timeout webClient.getOptions().setRedirectEnabled(false); @@ -1005,26 +1022,30 @@ public Boolean call() throws Exception { } private void checkSingleTokenCookie(Cookie tokenCookie, String type) { - checkSingleTokenCookie(tokenCookie, type, false); + checkSingleTokenCookie(tokenCookie, type, null); } - private void checkSingleTokenCookie(Cookie tokenCookie, String type, boolean decrypt) { + private void checkSingleTokenCookie(Cookie tokenCookie, String type, String decryptSecret) { String[] cookieParts = tokenCookie.getValue().split("\\|"); assertEquals(1, cookieParts.length); String token = cookieParts[0]; String[] tokenParts = token.split("\\."); - if (decrypt) { + if (decryptSecret != null) { assertEquals(5, tokenParts.length); try { - token = OidcUtils.decryptString(token, KeyUtils.createSecretKeyFromSecret("eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU")); + SecretKey key = new SecretKeySpec(OidcUtils + .getSha256Digest(decryptSecret.getBytes(StandardCharsets.UTF_8)), + "AES"); + token = OidcUtils.decryptString(token, key); tokenParts = token.split("\\."); } catch (Exception ex) { fail("Token decryption has failed"); } } assertEquals(3, tokenParts.length); - assertEquals(type, OidcUtils.decodeJwtContent(token).getString("typ")); + JsonObject json = OidcUtils.decodeJwtContent(token); + assertEquals(type, json.getString("typ")); } @Test diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java index 5c8ce5297d4df9..219738a57ebd3f 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java @@ -139,6 +139,7 @@ public OidcTenantConfig get() { config.setJwksPath(jwksUri); config.getToken().setIssuer("any"); config.tokenStateManager.setSplitTokens(true); + config.tokenStateManager.setEncryptionRequired(false); config.getAuthentication().setSessionAgeExtension(Duration.ofMinutes(1)); config.getAuthentication().setIdTokenRequired(false); return config; diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index cd675540342b3b..c318ee68b82acd 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -36,7 +36,7 @@ quarkus.oidc.code-flow-encrypted-id-token-pem.token.decryption-key-location=priv quarkus.oidc.code-flow-form-post.auth-server-url=${keycloak.url}/realms/quarkus-form-post/ quarkus.oidc.code-flow-form-post.client-id=quarkus-web-app -quarkus.oidc.code-flow-form-post.credentials.secret=secret +quarkus.oidc.code-flow-form-post.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow quarkus.oidc.code-flow-form-post.application-type=web-app quarkus.oidc.code-flow-form-post.authentication.response-mode=form_post quarkus.oidc.code-flow-form-post.discovery-enabled=false diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java index d3273facb0eb87..57d95610e7d362 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java @@ -13,8 +13,12 @@ import java.io.IOException; import java.net.URI; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Set; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -154,7 +158,7 @@ public void testCodeFlowFormPostAndBackChannelLogout() throws IOException { } @Test - public void testCodeFlowFormPostAndFrontChannelLogout() throws IOException { + public void testCodeFlowFormPostAndFrontChannelLogout() throws Exception { defineCodeFlowLogoutStub(); try (final WebClient webClient = createWebClient()) { webClient.getOptions().setRedirectEnabled(true); @@ -174,9 +178,7 @@ public void testCodeFlowFormPostAndFrontChannelLogout() throws IOException { assertEquals("alice", page.getBody().asNormalizedText()); // Session is still active - Cookie sessionCookie = getSessionCookie(webClient, "code-flow-form-post"); - assertNotNull(sessionCookie); - JsonObject idTokenClaims = OidcUtils.decodeJwtContent(sessionCookie.getValue().split("\\|")[0]); + JsonObject idTokenClaims = decryptIdToken(webClient, "code-flow-form-post"); webClient.getOptions().setRedirectEnabled(false); @@ -238,7 +240,7 @@ public void testCodeFlowTokenIntrospection() throws Exception { } } - private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetime) throws IOException { + private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetime) throws Exception { try (final WebClient webClient = createWebClient()) { webClient.getOptions().setRedirectEnabled(true); HtmlPage page = webClient.getPage("http://localhost:8081/" + tenantId); @@ -251,9 +253,7 @@ private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetim assertEquals("alice:alice:alice, cache size: 1", page.getBody().asNormalizedText()); - Cookie sessionCookie = getSessionCookie(webClient, tenantId); - assertNotNull(sessionCookie); - JsonObject idTokenClaims = OidcUtils.decodeJwtContent(sessionCookie.getValue().split("\\|")[0]); + JsonObject idTokenClaims = decryptIdToken(webClient, tenantId); assertNull(idTokenClaims.getJsonObject(OidcUtils.USER_INFO_ATTRIBUTE)); long issuedAt = idTokenClaims.getLong("iat"); long expiresAt = idTokenClaims.getLong("exp"); @@ -263,6 +263,19 @@ private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetim } } + private JsonObject decryptIdToken(WebClient webClient, String tenantId) throws Exception { + Cookie sessionCookie = getSessionCookie(webClient, tenantId); + assertNotNull(sessionCookie); + String encryptedIdToken = sessionCookie.getValue().split("\\|")[0]; + + SecretKey key = new SecretKeySpec(OidcUtils + .getSha256Digest("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" + .getBytes(StandardCharsets.UTF_8)), + "AES"); + String encodedIdToken = OidcUtils.decryptString(encryptedIdToken, key); + return OidcUtils.decodeJwtContent(encodedIdToken); + } + private void doTestCodeFlowUserInfoCashedInIdToken() throws Exception { try (final WebClient webClient = createWebClient()) { webClient.getOptions().setRedirectEnabled(true); @@ -276,9 +289,7 @@ private void doTestCodeFlowUserInfoCashedInIdToken() throws Exception { assertEquals("alice:alice:alice, cache size: 0", page.getBody().asNormalizedText()); - Cookie sessionCookie = getSessionCookie(webClient, "code-flow-user-info-github-cached-in-idtoken"); - assertNotNull(sessionCookie); - JsonObject idTokenClaims = OidcUtils.decodeJwtContent(sessionCookie.getValue().split("\\|")[0]); + JsonObject idTokenClaims = decryptIdToken(webClient, "code-flow-user-info-github-cached-in-idtoken"); assertNotNull(idTokenClaims.getJsonObject(OidcUtils.USER_INFO_ATTRIBUTE)); // refresh From e48416bcbb762e1406053b02c53dd64815f1fbd7 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 29 Mar 2023 13:42:08 +0100 Subject: [PATCH 002/333] Create a single encrypted session cookie value --- ...oidc-code-flow-authentication-concept.adoc | 10 ++++--- .../oidc/common/runtime/OidcCommonUtils.java | 6 +++- .../oidc/test/CodeFlowDevModeTestCase.java | 2 +- .../runtime/DefaultTokenStateManager.java | 29 +++++++++++++------ .../oidc/runtime/TenantConfigContext.java | 3 ++ .../io/quarkus/it/keycloak/CodeFlowTest.java | 16 +++++----- .../keycloak/CodeFlowAuthorizationTest.java | 7 +++-- 7 files changed, 49 insertions(+), 24 deletions(-) diff --git a/docs/src/main/asciidoc/security-oidc-code-flow-authentication-concept.adoc b/docs/src/main/asciidoc/security-oidc-code-flow-authentication-concept.adoc index d9ef36c4e9c6e7..a2a86d94b6d693 100644 --- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication-concept.adoc +++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication-concept.adoc @@ -371,7 +371,7 @@ Note that some endpoints do not require the access token. An access token is onl If the ID, access and refresh tokens are JWT tokens then combining all of them (if the strategy is the default `keep-all-tokens`) or only ID and refresh tokens (if the strategy is `id-refresh-token`) may produce a session cookie value larger than 4KB and the browsers may not be able to keep this cookie. In such cases, you can use `quarkus.oidc.token-state-manager.split-tokens=true` to have a unique session token per each of these tokens. -You can also configure the default `TokenStateManager` to encrypt the tokens before storing them as cookie values which may be necessary if the tokens contain sensitive claim values. +Note that `TokenStateManager` will encrypt the tokens before storing them in the session cookie. For example, here is how you configure it to split the tokens and encrypt them: [source, properties] @@ -381,12 +381,14 @@ quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret quarkus.oidc.application-type=web-app quarkus.oidc.token-state-manager.split-tokens=true -quarkus.oidc.token-state-manager.encryption-required=true quarkus.oidc.token-state-manager.encryption-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU ---- -The token encryption secret must be 32 characters long. Note that you only have to set `quarkus.oidc.token-state-manager.encryption-secret` if you prefer not to use -`quarkus.oidc.credentials.secret` for encrypting the tokens or if `quarkus.oidc.credentials.secret` length is less than 32 characters. +The token encryption secret must be at least 32 characters long. If this key is not configured then either `quarkus.oidc.credentials.secret` or `quarkus.oidc.credentials.jwt.secret` will be hashed to create an encryption key. + +`quarkus.oidc.token-state-manager.encryption-secret` should be configured if Quarkus authenticates to OpenId Connect Provider using either mTLS or `private_key_jwt` (where a private RSA or EC key is used to sign a JWT token) authentication methods, otherwise a random key will be generated which will be problematic if the Quarkus application is running in the cloud with multiple pods managing the requests. + +If you need you can disable encrypting the tokens in the session cookie with `quarkus.oidc.token-state-manager.encryption-required=false`. Register your own `io.quarkus.oidc.TokenStateManager' implementation as an `@ApplicationScoped` CDI bean if you need to customize the way the tokens are associated with the session cookie. For example, you may want to keep the tokens in a database and have only a database pointer stored in a session cookie. Note though that it may present some challenges in making the tokens available across multiple microservices nodes. diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index c81938e0794e1c..7ddcf22aee2018 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -280,6 +280,10 @@ public static String clientSecret(Credentials creds) { return creds.secret.orElse(creds.clientSecret.value.orElseGet(fromCredentialsProvider(creds.clientSecret.provider))); } + public static String jwtSecret(Credentials creds) { + return creds.jwt.secret.orElseGet(fromCredentialsProvider(creds.jwt.secretProvider)); + } + public static Secret.Method clientSecretMethod(Credentials creds) { return creds.clientSecret.method.orElseGet(() -> Secret.Method.BASIC); } @@ -304,7 +308,7 @@ public String get() { public static Key clientJwtKey(Credentials creds) { if (creds.jwt.secret.isPresent() || creds.jwt.secretProvider.key.isPresent()) { return KeyUtils - .createSecretKeyFromSecret(creds.jwt.secret.orElseGet(fromCredentialsProvider(creds.jwt.secretProvider))); + .createSecretKeyFromSecret(jwtSecret(creds)); } else { Key key = null; try { diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java index 00e003db3d7dae..b0767525bf6291 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java @@ -97,7 +97,7 @@ public void testAccessAndRefreshTokenInjectionDevMode() throws IOException, Inte assertEquals("alice", page.getBody().asNormalizedText()); - assertEquals("custom", page.getWebClient().getCookieManager().getCookie("q_session").getValue().split("\\|")[3]); + assertEquals("custom", page.getWebClient().getCookieManager().getCookie("q_session").getValue().split("\\|")[1]); webClient.getOptions().setRedirectEnabled(false); WebResponse webResponse = webClient diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java index 01c622f4bab363..19623c965c0cd2 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java @@ -21,14 +21,19 @@ public class DefaultTokenStateManager implements TokenStateManager { @Override public Uni createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, AuthorizationCodeTokens tokens, OidcRequestContext requestContext) { + + boolean encryptAll = !oidcConfig.tokenStateManager.splitTokens; + StringBuilder sb = new StringBuilder(); - sb.append(encryptToken(tokens.getIdToken(), routingContext, oidcConfig)); + sb.append(encryptAll ? tokens.getIdToken() : encryptToken(tokens.getIdToken(), routingContext, oidcConfig)); if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.KEEP_ALL_TOKENS) { if (!oidcConfig.tokenStateManager.splitTokens) { sb.append(CodeAuthenticationMechanism.COOKIE_DELIM) - .append(encryptToken(tokens.getAccessToken(), routingContext, oidcConfig)) + .append(encryptAll ? tokens.getAccessToken() + : encryptToken(tokens.getAccessToken(), routingContext, oidcConfig)) .append(CodeAuthenticationMechanism.COOKIE_DELIM) - .append(encryptToken(tokens.getRefreshToken(), routingContext, oidcConfig)); + .append(encryptAll ? tokens.getRefreshToken() + : encryptToken(tokens.getRefreshToken(), routingContext, oidcConfig)); } else { CodeAuthenticationMechanism.createCookie(routingContext, oidcConfig, @@ -48,7 +53,8 @@ public Uni createTokenState(RoutingContext routingContext, OidcTenantCon sb.append(CodeAuthenticationMechanism.COOKIE_DELIM) .append("") .append(CodeAuthenticationMechanism.COOKIE_DELIM) - .append(encryptToken(tokens.getRefreshToken(), routingContext, oidcConfig)); + .append(encryptAll ? tokens.getRefreshToken() + : encryptToken(tokens.getRefreshToken(), routingContext, oidcConfig)); } else { if (tokens.getRefreshToken() != null) { CodeAuthenticationMechanism.createCookie(routingContext, @@ -59,21 +65,26 @@ public Uni createTokenState(RoutingContext routingContext, OidcTenantCon } } } - return Uni.createFrom().item(sb.toString()); + String state = encryptAll ? encryptToken(sb.toString(), routingContext, oidcConfig) : sb.toString(); + return Uni.createFrom().item(state); } @Override public Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, OidcRequestContext requestContext) { + boolean decryptAll = !oidcConfig.tokenStateManager.splitTokens; + + tokenState = decryptAll ? decryptToken(tokenState, routingContext, oidcConfig) : tokenState; + String[] tokens = CodeAuthenticationMechanism.COOKIE_PATTERN.split(tokenState); - String idToken = decryptToken(tokens[0], routingContext, oidcConfig); + String idToken = decryptAll ? tokens[0] : decryptToken(tokens[0], routingContext, oidcConfig); String accessToken = null; String refreshToken = null; if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.KEEP_ALL_TOKENS) { if (!oidcConfig.tokenStateManager.splitTokens) { - accessToken = decryptToken(tokens[1], routingContext, oidcConfig); - refreshToken = decryptToken(tokens[2], routingContext, oidcConfig); + accessToken = decryptAll ? tokens[1] : decryptToken(tokens[1], routingContext, oidcConfig); + refreshToken = decryptAll ? tokens[2] : decryptToken(tokens[2], routingContext, oidcConfig); } else { Cookie atCookie = getAccessTokenCookie(routingContext, oidcConfig); if (atCookie != null) { @@ -86,7 +97,7 @@ public Uni getTokens(RoutingContext routingContext, Oid } } else if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.ID_REFRESH_TOKENS) { if (!oidcConfig.tokenStateManager.splitTokens) { - refreshToken = decryptToken(tokens[2], routingContext, oidcConfig); + refreshToken = decryptAll ? tokens[2] : decryptToken(tokens[2], routingContext, oidcConfig); } else { Cookie rtCookie = getRefreshTokenCookie(routingContext, oidcConfig); if (rtCookie != null) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java index 674320d38ebf0d..9ff389f5acd2f4 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContext.java @@ -70,6 +70,9 @@ private static SecretKey createTokenEncSecretKey(OidcTenantConfig config) { if (config.tokenStateManager.encryptionRequired) { String encSecret = config.tokenStateManager.encryptionSecret .orElse(OidcCommonUtils.clientSecret(config.credentials)); + if (encSecret == null) { + encSecret = OidcCommonUtils.jwtSecret(config.credentials); + } try { if (encSecret == null) { LOG.warn("Secret key for encrypting tokens is missing, auto-generating it"); 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 43eb3ff67f284a..9637fd1b762b66 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 @@ -279,12 +279,12 @@ public void testCodeFlowForceHttpsRedirectUriWithQueryAndPkce() throws Exception Cookie sessionCookie = getSessionCookie(webClient, "tenant-https_test"); assertNotNull(sessionCookie); - String encryptedIdToken = sessionCookie.getValue().split("\\|")[0]; - SecretKey key = new SecretKeySpec(OidcUtils .getSha256Digest("secret".getBytes(StandardCharsets.UTF_8)), "AES"); - String encodedIdToken = OidcUtils.decryptString(encryptedIdToken, key); + String sessionCookieValue = OidcUtils.decryptString(sessionCookie.getValue(), key); + + String encodedIdToken = sessionCookieValue.split("\\|")[0]; JsonObject idToken = OidcUtils.decodeJwtContent(encodedIdToken); String expiresAt = idToken.getInteger("exp").toString(); @@ -891,17 +891,19 @@ public void testDefaultSessionManagerIdRefreshTokens() throws Exception { page = webClient.getPage("http://localhost:8081/web-app/refresh/tenant-id-refresh-token"); assertEquals("tenant-id-refresh-token:RT injected", page.getBody().asNormalizedText()); - Cookie idTokenCookie = getSessionCookie(page.getWebClient(), "tenant-id-refresh-token"); + Cookie sessionCookie = getSessionCookie(page.getWebClient(), "tenant-id-refresh-token"); SecretKey key = new SecretKeySpec(OidcUtils .getSha256Digest("secret".getBytes(StandardCharsets.UTF_8)), "AES"); - String[] parts = idTokenCookie.getValue().split("\\|"); + String sessionCookieValue = OidcUtils.decryptString(sessionCookie.getValue(), key); + + String[] parts = sessionCookieValue.split("\\|"); assertEquals(3, parts.length); - assertEquals("ID", OidcUtils.decodeJwtContent(OidcUtils.decryptString(parts[0], key)).getString("typ")); + assertEquals("ID", OidcUtils.decodeJwtContent(parts[0]).getString("typ")); assertEquals("", parts[1]); - assertEquals("Refresh", OidcUtils.decodeJwtContent(OidcUtils.decryptString(parts[2], key)).getString("typ")); + assertEquals("Refresh", OidcUtils.decodeJwtContent(parts[2]).getString("typ")); assertNull(getSessionAtCookie(webClient, "tenant-id-refresh-token")); assertNull(getSessionRtCookie(webClient, "tenant-id-refresh-token")); diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java index 57d95610e7d362..ac8c97a55304d9 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java @@ -266,13 +266,16 @@ private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetim private JsonObject decryptIdToken(WebClient webClient, String tenantId) throws Exception { Cookie sessionCookie = getSessionCookie(webClient, tenantId); assertNotNull(sessionCookie); - String encryptedIdToken = sessionCookie.getValue().split("\\|")[0]; SecretKey key = new SecretKeySpec(OidcUtils .getSha256Digest("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" .getBytes(StandardCharsets.UTF_8)), "AES"); - String encodedIdToken = OidcUtils.decryptString(encryptedIdToken, key); + + String decryptedSessionCookie = OidcUtils.decryptString(sessionCookie.getValue(), key); + + String encodedIdToken = decryptedSessionCookie.split("\\|")[0]; + return OidcUtils.decodeJwtContent(encodedIdToken); } From e22b3e6650deab20e6421e6a00e6ca9abc7dbd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 11 Apr 2023 14:16:10 +0200 Subject: [PATCH 003/333] Pass method arguments to permissions on RESTEasy Reactive endpoints closes: #32030 --- ...ity-authorize-web-endpoints-reference.adoc | 16 ++---- .../AbstractPermissionsAllowedTestCase.java | 54 +++++++++++++++++++ .../CustomPermissionWithExtraArgs.java | 51 ++++++++++++++++++ .../LazyAuthPermissionsAllowedTestCase.java | 2 +- ...NonBlockingPermissionsAllowedResource.java | 13 +++++ .../security/PermissionsAllowedResource.java | 12 +++++ .../PermissionsIdentityAugmentor.java | 9 +++- ...oactiveAuthPermissionsAllowedTestCase.java | 2 +- .../security/EagerSecurityHandler.java | 15 +++++- .../security/spi/runtime/SecurityCheck.java | 14 +++++ .../check/PermissionSecurityCheck.java | 5 ++ 11 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/CustomPermissionWithExtraArgs.java diff --git a/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc b/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc index 3ed8c5f1b89430..f5f3d4660a917c 100644 --- a/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc +++ b/docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc @@ -591,13 +591,7 @@ However this option comes with a price, as the `LibraryPermission` must be insta <2> Here, the first `Library` parameter is `migrate`, therefore we marked `library` parameter explicitly via `PermissionsAllowed#params`. Please note that both Permission constructor and annotated method must have parameter `library`, otherwise validation will fail. -CAUTION: If you would like to pass method parameters to a custom `Permission` constructor from RESTEasy Reactive endpoints, -make sure you have `@PermissionsAllowed` annotation set not on the JAX-RS resource method itself, but on the injected CDI -bean to which this method will delegate to. Setting `@PermissionsAllowed` on the JAX-RS resource method will not work -because RESTEasy Reactive performs the security checks before the deserialization. -These limitations are demonstrated in the example below. - -.Example of endpoint limitations when it comes to passing annotated method arguments to the Permission constructor +.Example of resource secured with the `LibraryPermission` [source,java] ---- @@ -610,7 +604,7 @@ public class LibraryResource { @PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class) @PUT @Path("/id/{id}") - public Library updateLibrary(@PathParam("id") Integer id, Library library) { <1> + public Library updateLibrary(@PathParam("id") Integer id, Library library) { ... } @@ -618,15 +612,11 @@ public class LibraryResource { @Path("/service-way/id/{id}") public Library updateLibrarySvc(@PathParam("id") Integer id, Library library) { String newDescription = "new description " + id; - return libraryService.updateLibrary(newDescription, library); <2> + return libraryService.updateLibrary(newDescription, library); } } ---- -<1> In the RESTEasy Reactive, the endpoint argument `library` won't ever be passed to the `LibraryPermission`, because it is not available. -Instead, Quarkus will pass `null` for the argument `library`. -That gives you option to reuse your custom Permission when the method argument (like `library`) is optional. -<2> Argument `library` will be passed to the `LibraryPermission` constructor as the `LibraryService#updateLibrary` method is not an endpoint. Similarly to the `CRUDResource` example, we can use permission to role mapping and grant user with role `admin` right to update `MediaLibrary`: diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractPermissionsAllowedTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractPermissionsAllowedTestCase.java index 4e9a69c0f71bd5..1ff428bba91601 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractPermissionsAllowedTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/AbstractPermissionsAllowedTestCase.java @@ -6,6 +6,7 @@ import io.quarkus.security.test.utils.TestIdentityController; import io.restassured.RestAssured; +import io.restassured.response.ValidatableResponse; public abstract class AbstractPermissionsAllowedTestCase { @@ -123,4 +124,57 @@ public void testCustomPermission() { RestAssured.given().auth().basic("admin", "admin").param("greeting", "hi") .get("/permissions/custom-permission").then().statusCode(403); } + + @Test + public void testCustomPermissionWithAdditionalArgs() { + // === autodetected method params && non-blocking endpoint + // admin has permission with place 'Ostrava' + reqAutodetectedExtraArgs("admin", "Ostrava") + .statusCode(200) + .body(Matchers.equalTo("so long Nelson 3 Ostrava")); + // user has permission with place 'Prague' + reqAutodetectedExtraArgs("user", "Prague") + .statusCode(200) + .body(Matchers.equalTo("so long Nelson 3 Prague")); + reqAutodetectedExtraArgs("user", "Ostrava") + .statusCode(403); + // viewer has no permission + reqAutodetectedExtraArgs("viewer", "Ostrava") + .statusCode(403); + + // === explicitly marked method params && blocking endpoint + // admin has permission with place 'Ostrava' + reqExplicitlyMarkedExtraArgs("admin", "Ostrava") + .statusCode(200) + .body(Matchers.equalTo("so long Nelson 3 Ostrava")); + // user has permission with place 'Prague' + reqExplicitlyMarkedExtraArgs("user", "Prague") + .statusCode(200) + .body(Matchers.equalTo("so long Nelson 3 Prague")); + reqExplicitlyMarkedExtraArgs("user", "Ostrava") + .statusCode(403); + // viewer has no permission + reqExplicitlyMarkedExtraArgs("viewer", "Ostrava") + .statusCode(403); + } + + private static ValidatableResponse reqAutodetectedExtraArgs(String user, String place) { + return RestAssured.given() + .auth().basic(user, user) + .pathParam("goodbye", "so long") + .header("toWhom", "Nelson") + .cookie("day", 3) + .body(place) + .post("/permissions-non-blocking/custom-perm-with-args/{goodbye}").then(); + } + + private static ValidatableResponse reqExplicitlyMarkedExtraArgs(String user, String place) { + return RestAssured.given() + .auth().basic(user, user) + .pathParam("goodbye", "so long") + .header("toWhom", "Nelson") + .cookie("day", 3) + .body(place) + .post("/permissions/custom-perm-with-args/{goodbye}").then(); + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/CustomPermissionWithExtraArgs.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/CustomPermissionWithExtraArgs.java new file mode 100644 index 00000000000000..787fd0227cbc76 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/CustomPermissionWithExtraArgs.java @@ -0,0 +1,51 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import java.security.Permission; +import java.util.Objects; + +public class CustomPermissionWithExtraArgs extends Permission { + + private final String permName; + private final String goodbye; + private final String toWhom; + private final int day; + private final String place; + + public CustomPermissionWithExtraArgs(String permName, String goodbye, String toWhom, int day, String place) { + super(permName); + this.permName = permName; + this.goodbye = goodbye; + this.toWhom = toWhom; + this.day = day; + this.place = place; + } + + @Override + public boolean implies(Permission permission) { + if (permission instanceof CustomPermissionWithExtraArgs) { + return permission.equals(this); + } + return false; + } + + @Override + public String getActions() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + CustomPermissionWithExtraArgs that = (CustomPermissionWithExtraArgs) o; + return day == that.day && Objects.equals(permName, that.permName) && Objects.equals(goodbye, that.goodbye) + && Objects.equals(toWhom, that.toWhom) && Objects.equals(place, that.place); + } + + @Override + public int hashCode() { + return Objects.hash(permName, goodbye, toWhom, day, place); + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthPermissionsAllowedTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthPermissionsAllowedTestCase.java index acf2946823f293..582301287e51c5 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthPermissionsAllowedTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/LazyAuthPermissionsAllowedTestCase.java @@ -14,7 +14,7 @@ public class LazyAuthPermissionsAllowedTestCase extends AbstractPermissionsAllow .withApplicationRoot((jar) -> jar .addClasses(PermissionsAllowedResource.class, TestIdentityProvider.class, TestIdentityController.class, NonBlockingPermissionsAllowedResource.class, CustomPermission.class, - PermissionsIdentityAugmentor.class) + PermissionsIdentityAugmentor.class, CustomPermissionWithExtraArgs.class) .addAsResource(new StringAsset("quarkus.http.auth.proactive=false\n"), "application.properties")); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/NonBlockingPermissionsAllowedResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/NonBlockingPermissionsAllowedResource.java index 220931072e9892..ff7321f769b92a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/NonBlockingPermissionsAllowedResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/NonBlockingPermissionsAllowedResource.java @@ -7,6 +7,10 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; +import org.jboss.resteasy.reactive.RestCookie; +import org.jboss.resteasy.reactive.RestHeader; +import org.jboss.resteasy.reactive.RestPath; + import io.quarkus.security.PermissionsAllowed; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.smallrye.mutiny.Uni; @@ -45,4 +49,13 @@ public Uni getSecurityIdentity() { public Uni greetings(@QueryParam("greeting") String greeting) { return Uni.createFrom().item(greeting); } + + @PermissionsAllowed(value = "farewell", permission = CustomPermissionWithExtraArgs.class) + @Path("/custom-perm-with-args/{goodbye}") + @POST + public Uni farewell(@RestPath String goodbye, @RestHeader("toWhom") String toWhom, @RestCookie int day, + String place) { + String farewell = String.join(" ", new String[] { goodbye, toWhom, Integer.toString(day), place }); + return Uni.createFrom().item(farewell); + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsAllowedResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsAllowedResource.java index 41c58363031894..1060318bdff7ab 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsAllowedResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsAllowedResource.java @@ -6,6 +6,10 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; +import org.jboss.resteasy.reactive.RestCookie; +import org.jboss.resteasy.reactive.RestHeader; +import org.jboss.resteasy.reactive.RestPath; + import io.quarkus.security.PermissionsAllowed; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.smallrye.common.annotation.NonBlocking; @@ -45,4 +49,12 @@ public String greetings(@QueryParam("greeting") String greeting) { return greeting; } + @PermissionsAllowed(value = "farewell", permission = CustomPermissionWithExtraArgs.class, params = { "goodbye", "toWhom", + "day", "place" }) + @Path("/custom-perm-with-args/{goodbye}") + @POST + public String farewell(@RestPath String goodbye, @RestHeader("toWhom") String toWhom, @RestCookie int day, String place) { + String farewell = String.join(" ", new String[] { goodbye, toWhom, Integer.toString(day), place }); + return farewell; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsIdentityAugmentor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsIdentityAugmentor.java index 2f94cfd69b31c3..70124e336727fb 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsIdentityAugmentor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/PermissionsIdentityAugmentor.java @@ -30,10 +30,12 @@ SecurityIdentity build(SecurityIdentity identity) { switch (identity.getPrincipal().getName()) { case "admin": builder.addPermissionChecker(new PermissionCheckBuilder().addPermission("update").addPermission("create") - .addPermission("read", "resource-admin").addCustomPermission().build()); + .addPermission("read", "resource-admin").addCustomPermission() + .addCustomPermission("farewell", "so long", "Nelson", 3, "Ostrava").build()); break; case "user": builder.addPermissionChecker(new PermissionCheckBuilder().addPermission("update").addPermission("get-identity") + .addCustomPermission("farewell", "so long", "Nelson", 3, "Prague") .addPermission("read", "resource-admin").build()); break; case "viewer": @@ -62,6 +64,11 @@ PermissionCheckBuilder addCustomPermission() { return this; } + PermissionCheckBuilder addCustomPermission(String permName, String goodbye, String toWhom, int day, String place) { + permissionSet.add(new CustomPermissionWithExtraArgs(permName, goodbye, toWhom, day, place)); + return this; + } + Function> build() { final var immutablePermissions = Set.copyOf(permissionSet); return new Function>() { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthPermissionsAllowedTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthPermissionsAllowedTestCase.java index 18f033468d652e..5c35810b53ac67 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthPermissionsAllowedTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/ProactiveAuthPermissionsAllowedTestCase.java @@ -13,6 +13,6 @@ public class ProactiveAuthPermissionsAllowedTestCase extends AbstractPermissions .withApplicationRoot((jar) -> jar .addClasses(PermissionsAllowedResource.class, TestIdentityProvider.class, TestIdentityController.class, NonBlockingPermissionsAllowedResource.class, CustomPermission.class, - PermissionsIdentityAugmentor.class)); + PermissionsIdentityAugmentor.class, CustomPermissionWithExtraArgs.class)); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java index 39df69163b7ca4..e4db35f435c013 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java @@ -16,6 +16,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.InjectableInstance; +import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.spi.runtime.AuthorizationController; @@ -97,8 +98,18 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti deferredIdentity.flatMap(new Function>() { @Override public Uni apply(SecurityIdentity securityIdentity) { - preventRepeatedSecurityChecks(requestContext, methodDescription); - return theCheck.nonBlockingApply(securityIdentity, methodDescription, requestContext.getParameters()); + if (theCheck.requiresMethodArguments()) { + // if security check requires method arguments, we can't perform it now + // however we only allow to pass authenticated requests to avoid security risks + if (securityIdentity.isAnonymous()) { + throw new UnauthorizedException(); + } + // security check will be performed by CDI interceptor + return Uni.createFrom().nullItem(); + } else { + preventRepeatedSecurityChecks(requestContext, methodDescription); + return theCheck.nonBlockingApply(securityIdentity, methodDescription, requestContext.getParameters()); + } } }) .subscribe().withSubscriber(new UniSubscriber() { diff --git a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java index 4318d06d93733d..0b2535705ac0ee 100644 --- a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java +++ b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheck.java @@ -24,4 +24,18 @@ default Uni nonBlockingApply(SecurityIdentity identity, MethodDescription met default boolean isPermitAll() { return false; } + + /** + * Security checks may be performed before the secured method is actually invoked. + * This happens to make sure they are run before serialization and fully asynchronous checks work as expected. + * However, if the security checks requires arguments of invoked method, it is possible to postpone this check + * to the moment when arguments are available. + * + * IMPORTANT: in order to avoid security risks, all requests with postponed security checks must be authenticated + * + * @return true if the security check needs method parameters to work correctly + */ + default boolean requiresMethodArguments() { + return false; + } } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermissionSecurityCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermissionSecurityCheck.java index 2499aec04caf1b..5888f5e2c792dc 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermissionSecurityCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermissionSecurityCheck.java @@ -65,6 +65,11 @@ public Uni nonBlockingApply(SecurityIdentity identity, MethodDescription meth return checkPermissions(identity, getPermissions(parameters), 0); } + @Override + public boolean requiresMethodArguments() { + return useComputedPermissions; + } + private static void throwException(SecurityIdentity identity) { if (identity.isAnonymous()) { throw new UnauthorizedException(); From 62d4fd2e8a306f4f79416709b7862b2b15e4741d Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 12 Apr 2023 17:13:44 +0100 Subject: [PATCH 004/333] Add JWT authentication tests to management-interface-auth --- .../management-interface-auth/pom.xml | 17 +++++ .../it/management/GreetingResource.java | 12 ++++ .../src/main/resources/application.properties | 28 ++++++++ .../src/main/resources/privateKey.pem | 28 ++++++++ .../src/main/resources/publicKey.pem | 9 +++ .../ManagementInterfaceTestCase.java | 67 ++++++++++++++++++- 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 integration-tests/management-interface-auth/src/main/resources/privateKey.pem create mode 100644 integration-tests/management-interface-auth/src/main/resources/publicKey.pem diff --git a/integration-tests/management-interface-auth/pom.xml b/integration-tests/management-interface-auth/pom.xml index 7bdf390f8b792e..2c2a74773dc53c 100644 --- a/integration-tests/management-interface-auth/pom.xml +++ b/integration-tests/management-interface-auth/pom.xml @@ -30,6 +30,10 @@ io.quarkus quarkus-elytron-security-properties-file + + io.quarkus + quarkus-smallrye-jwt + io.quarkus quarkus-junit5 @@ -99,6 +103,19 @@ + + io.quarkus + quarkus-smallrye-jwt-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/management-interface-auth/src/main/java/io/quarkus/it/management/GreetingResource.java b/integration-tests/management-interface-auth/src/main/java/io/quarkus/it/management/GreetingResource.java index 1557a8d3bd7916..6526c8505b21af 100644 --- a/integration-tests/management-interface-auth/src/main/java/io/quarkus/it/management/GreetingResource.java +++ b/integration-tests/management-interface-auth/src/main/java/io/quarkus/it/management/GreetingResource.java @@ -17,4 +17,16 @@ public String hello() { public String goodbye() { return "goodbye"; } + + @GET + @Path("/goodmorning") + public String goodmorning() { + return "goodmorning"; + } + + @GET + @Path("/goodevening") + public String goodevening() { + return "goodevening"; + } } diff --git a/integration-tests/management-interface-auth/src/main/resources/application.properties b/integration-tests/management-interface-auth/src/main/resources/application.properties index 51b3e507c06c31..6844b7e6ce612d 100644 --- a/integration-tests/management-interface-auth/src/main/resources/application.properties +++ b/integration-tests/management-interface-auth/src/main/resources/application.properties @@ -1,15 +1,28 @@ quarkus.management.enabled=true +# Management router authentication: +# /q/health/* - basic authentication only, `management` role is required +# /q/metrics/* - basic and jwt authentications are allowed, any role is allowed quarkus.management.auth.basic=true quarkus.management.auth.policy.role-policy.roles-allowed=management quarkus.management.auth.permission.health.paths=/q/health/* quarkus.management.auth.permission.health.policy=role-policy +quarkus.management.auth.permission.health.auth-mechanism=basic + quarkus.management.auth.permission.metrics.paths=/q/metrics/* quarkus.management.auth.permission.metrics.policy=authenticated +# Main router authentication: +# /service/hello/* - public resource +# /service/goodbye/* - basic authentication only, `greeting` role is allowed +# /service/goodmorning/* - JWT bearer authentication only, `admin` role is allowed +# /service/goodevening/* - JWT bearer authentication only, `user` role is allowed + quarkus.http.auth.basic=true quarkus.http.auth.policy.role-policy.roles-allowed=greeting + quarkus.http.auth.permission.main.paths=/service/goodbye quarkus.http.auth.permission.main.policy=role-policy +quarkus.http.auth.permission.main.auth-mechanism=basic quarkus.security.users.embedded.enabled=true quarkus.security.users.embedded.plain-text=true @@ -18,3 +31,18 @@ quarkus.security.users.embedded.roles.alice=management quarkus.security.users.embedded.users.bob=bob quarkus.security.users.embedded.roles.bob=greeting + +quarkus.http.auth.policy.role-policy-jwt-admin.roles-allowed=admin +quarkus.http.auth.permission.main-jwt-admin.paths=/service/goodmorning +quarkus.http.auth.permission.main-jwt-admin.policy=role-policy-jwt-admin +quarkus.http.auth.permission.main-jwt-admin.auth-mechanism=bearer + +quarkus.http.auth.policy.role-policy-jwt-user.roles-allowed=user +quarkus.http.auth.permission.main-jwt-user.paths=/service/goodevening +quarkus.http.auth.permission.main-jwt-user.policy=role-policy-jwt-user +quarkus.http.auth.permission.main-jwt-user.auth-mechanism=bearer + +mp.jwt.verify.publickey.location=/publicKey.pem +mp.jwt.verify.issuer=https://server.example.com +smallrye.jwt.sign.key.location=/privateKey.pem +smallrye.jwt.new-token.issuer=https://server.example.com \ No newline at end of file diff --git a/integration-tests/management-interface-auth/src/main/resources/privateKey.pem b/integration-tests/management-interface-auth/src/main/resources/privateKey.pem new file mode 100644 index 00000000000000..27543a434a1eb3 --- /dev/null +++ b/integration-tests/management-interface-auth/src/main/resources/privateKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa +PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H +OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN +qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh +nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM +uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6 +oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv +6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY +URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6 +96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB +Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3 +zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF +KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP +iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B +m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS +34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG +5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2 +tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL +WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y +b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09 +nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB +MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d +Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe +Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt +FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8 +f3cg+fr8aou7pr9SHhJlZCU= +-----END PRIVATE KEY----- diff --git a/integration-tests/management-interface-auth/src/main/resources/publicKey.pem b/integration-tests/management-interface-auth/src/main/resources/publicKey.pem new file mode 100644 index 00000000000000..6dc936fca34856 --- /dev/null +++ b/integration-tests/management-interface-auth/src/main/resources/publicKey.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq +Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR +TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e +UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9 +AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn +sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x +nQIDAQAB +-----END PUBLIC KEY----- diff --git a/integration-tests/management-interface-auth/src/test/java/io/quarkus/it/management/ManagementInterfaceTestCase.java b/integration-tests/management-interface-auth/src/test/java/io/quarkus/it/management/ManagementInterfaceTestCase.java index 48f9a68972add5..ba49cf5b1f8130 100644 --- a/integration-tests/management-interface-auth/src/test/java/io/quarkus/it/management/ManagementInterfaceTestCase.java +++ b/integration-tests/management-interface-auth/src/test/java/io/quarkus/it/management/ManagementInterfaceTestCase.java @@ -1,10 +1,13 @@ package io.quarkus.it.management; +import java.util.Set; + import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.smallrye.jwt.build.Jwt; @QuarkusTest public class ManagementInterfaceTestCase { @@ -29,6 +32,11 @@ void verifyThatHealthChecksAreExposedOnManagementInterface() { .then().statusCode(200) .body(Matchers.containsString("UP")); + RestAssured.given().auth().oauth2(getAdminToken()).get(getPrefix() + "/q/health") + .then().statusCode(401); + RestAssured.given().auth().oauth2(getUserToken()).get(getPrefix() + "/q/health") + .then().statusCode(401); + RestAssured.get("/q/health") .then().statusCode(404); } @@ -44,12 +52,19 @@ void verifyThatMetricsAreExposedOnManagementInterface() { RestAssured.given().auth().basic("john", "john").get(getPrefix() + "/q/metrics") .then().statusCode(401); + RestAssured.given().auth().oauth2(getAdminToken()).get(getPrefix() + "/q/metrics") + .then().statusCode(200); + RestAssured.given().auth().oauth2(getUserToken()).get(getPrefix() + "/q/metrics") + .then().statusCode(200); + RestAssured.given().auth().oauth2("wrongtoken").get(getPrefix() + "/q/metrics") + .then().statusCode(401); + RestAssured.get("/q/metrics") .then().statusCode(404); } @Test - void verifyMainEndpoint() { + void verifyMainEndpointBasicAuth() { RestAssured.get("/service/hello").then().statusCode(200) .body(Matchers.equalTo("hello")); @@ -62,5 +77,55 @@ void verifyMainEndpoint() { RestAssured.given().auth().basic("bob", "bob").get("/service/goodbye") .then().statusCode(200) .body(Matchers.equalTo("goodbye")); + + RestAssured.given().auth().oauth2(getAdminToken()).get("/service/goodbye") + .then().statusCode(401); + RestAssured.given().auth().oauth2(getUserToken()).get("/service/goodbye") + .then().statusCode(401); + + } + + @Test + void verifyMainEndpointJwtAuth() { + RestAssured.get("/service/hello").then().statusCode(200) + .body(Matchers.equalTo("hello")); + + RestAssured.given().auth().preemptive().basic("john", "john").get("/service/goodmorning") + .then().statusCode(401); + RestAssured.given().auth().preemptive().basic("john", "john").get("/service/goodevening") + .then().statusCode(401); + + RestAssured.given().auth().preemptive().basic("alice", "alice").get("/service/goodmorning") + .then().statusCode(401); + RestAssured.given().auth().preemptive().basic("alice", "alice").get("/service/goodevening") + .then().statusCode(401); + + RestAssured.given().auth().basic("bob", "bob").get("/service/goodmorning") + .then().statusCode(401); + RestAssured.given().auth().basic("bob", "bob").get("/service/goodevening") + .then().statusCode(401); + + RestAssured.given().auth().oauth2(getAdminToken()).get("/service/goodmorning") + .then().statusCode(200) + .body(Matchers.equalTo("goodmorning")); + RestAssured.given().auth().oauth2(getUserToken()).get("/service/goodmorning") + .then().statusCode(403); + + RestAssured.given().auth().oauth2(getAdminToken()).get("/service/goodevening") + .then().statusCode(200) + .body(Matchers.equalTo("goodevening")); + RestAssured.given().auth().oauth2(getUserToken()).get("/service/goodevening") + .then().statusCode(200) + .body(Matchers.equalTo("goodevening")); + } + + private String getAdminToken() { + return Jwt.upn("alice").groups(Set.of("admin", "user")).sign(); + } + + private String getUserToken() { + return Jwt.subject("bob").groups("user").sign(); + } + } From 1bb90175f3ebdd8dfd5537e5692bd0bb752ae068 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 22:01:11 +0000 Subject: [PATCH 005/333] Bump mongodb-crypt from 1.7.1 to 1.7.3 Bumps [mongodb-crypt](https://github.com/mongodb/libmongocrypt) from 1.7.1 to 1.7.3. - [Release notes](https://github.com/mongodb/libmongocrypt/releases) - [Changelog](https://github.com/mongodb/libmongocrypt/blob/1.7.3/CHANGELOG.md) - [Commits](https://github.com/mongodb/libmongocrypt/compare/1.7.1...1.7.3) --- updated-dependencies: - dependency-name: org.mongodb:mongodb-crypt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 8c689f17106db4..970a66bfe60076 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -171,7 +171,7 @@ 2.0 6.0.0 4.9.1 - 1.7.1 + 1.7.3 0.34.1 3.25.1 3.14.9 From 07e6cbef4bc105745beef29f60f4952488e68c62 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 12 Apr 2023 19:12:47 +0200 Subject: [PATCH 006/333] Bump Gradle from 8.0.2 to 8.1 --- build-parent/pom.xml | 2 +- devtools/gradle/gradle/wrapper/gradle-wrapper.properties | 5 +++-- independent-projects/bootstrap/pom.xml | 2 +- .../codestarts/quarkus/tooling/gradle-wrapper/codestart.yml | 2 +- .../devtools-testing/src/main/resources/fake-catalog.json | 2 +- independent-projects/tools/pom.xml | 2 +- .../gradle/gradle/wrapper/gradle-wrapper.properties | 5 +++-- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 6c61cac1a87be4..89b2d95825de8b 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -65,7 +65,7 @@ 3.8.8 3.2.0 - 8.0.2 + 8.1 ${project.version} ${project.version} 3.8.1 diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties index a2efda8857b1b4..d5e7b2ac5e4170 100644 --- a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=47a5bfed9ef814f90f8debcbbb315e8e7c654109acd224595ea39fca95c5d4da -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip +# https://gradle.org/release-checksums/ +distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index df396f74b67b20..e1f3a0173fe29f 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -77,7 +77,7 @@ 3.5.1 2.1.0 1.1.0 - 8.0.2 + 8.1 0.0.9 0.1.3 2.22.0 diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml index 031e2b3791c9de..e67e0bfd333e54 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml @@ -6,7 +6,7 @@ language: base: data: gradle: - version: 8.0.2 + version: 8.1 shared-data: buildtool: cli: ./gradlew diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index a5f64b1b928e75..97e6b6045cc25a 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -388,7 +388,7 @@ "supported-maven-versions": "[3.6.2,)", "proposed-maven-version": "3.8.8", "maven-wrapper-version": "3.2.0", - "gradle-wrapper-version": "8.0.2" + "gradle-wrapper-version": "8.1" } }, "codestarts-artifacts": [ diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 74c66ebe8ae7ab..003456dfc2dec4 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -42,7 +42,7 @@ 3.8.8 3.2.0 - 8.0.2 + 8.1 diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties index a2efda8857b1b4..d5e7b2ac5e4170 100644 --- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=47a5bfed9ef814f90f8debcbbb315e8e7c654109acd224595ea39fca95c5d4da -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip +# https://gradle.org/release-checksums/ +distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 3c2a8c4d02dbdada763b0211454beb4989a24a61 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 12 Apr 2023 17:27:20 +0200 Subject: [PATCH 007/333] Minor configuration-cache related fix for `buildNative` --- .../src/main/java/io/quarkus/gradle/QuarkusPlugin.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 55348a663780cc..1e6faad2163596 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -233,9 +233,8 @@ public boolean isSatisfiedBy(Task t) { tasks.register(BUILD_NATIVE_TASK_NAME, DefaultTask.class, task -> { task.finalizedBy(quarkusBuild); - task.doFirst(t -> project.getLogger() + task.doFirst(t -> t.getLogger() .warn("The 'buildNative' task has been deprecated in favor of 'build -Dquarkus.package.type=native'")); - }); configureBuildNativeTask(project); From a05d523c2095f27fad4c19eff7fc79d20a017ef6 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 12 Apr 2023 18:38:53 +0200 Subject: [PATCH 008/333] Fix test-project "test resources vs main resources" The test-project for `TestResourcesVsMainResourcesTest` did not work with "recent Quarkus versions", see `integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java` (was still using `io.quarkus.arc.config.ConfigProperties`, failed to compile, but the IT didn't fail.) --- .../src/main/java/org/acme/Config.java | 8 ++++---- .../src/main/java/org/acme/ExampleResource.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java index f92b0bd9b98a39..9e6b13f1770702 100644 --- a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java +++ b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java @@ -1,8 +1,8 @@ package org.acme; -import io.quarkus.arc.config.ConfigProperties; +import io.smallrye.config.ConfigMapping; -@ConfigProperties(prefix = "example") -public class Config { - public String message; +@ConfigMapping(prefix = "example") +public interface Config { + public String message(); } diff --git a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java index 35f468a85b3549..a17aa6569ab7cb 100644 --- a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java +++ b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java @@ -15,6 +15,6 @@ public class ExampleResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { - return config.message; + return config.message(); } } From dfd5206daa563ba6da4b8ad1d5fb47327d837055 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:27:19 +0200 Subject: [PATCH 009/333] Add a system property to forcefully limit the heap size of Quarkus' Gradle worker processes --- .../src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java | 5 +++++ integration-tests/gradle/gradle.properties | 3 +++ 2 files changed, 8 insertions(+) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java index a67a3a8a40052a..1fc804e23f468b 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java @@ -62,6 +62,11 @@ private void configureProcessWorkerSpec(ProcessWorkerSpec processWorkerSpec, Map customizations.forEach(a -> a.execute(forkOptions)); + String quarkusWorkerMaxHeap = System.getProperty("quarkus.gradle-worker.max-heap"); + if (quarkusWorkerMaxHeap != null && forkOptions.getAllJvmArgs().stream().noneMatch(arg -> arg.startsWith("-Xmx"))) { + forkOptions.jvmArgs("-Xmx" + quarkusWorkerMaxHeap); + } + // Pass all environment variables forkOptions.environment(System.getenv()); diff --git a/integration-tests/gradle/gradle.properties b/integration-tests/gradle/gradle.properties index e35f70a5669def..9d6ae70b30592b 100644 --- a/integration-tests/gradle/gradle.properties +++ b/integration-tests/gradle/gradle.properties @@ -2,3 +2,6 @@ version=999-SNAPSHOT # Idle timeout in milliseconds org.gradle.daemon.idletimeout=10000 + +# Override Java default heap size calculation for the Quarkus Gradle plugin worker processes, important for CI +systemProp.quarkus.gradle-worker.max-heap=256m From b421c8f9b4d7eeae9089378eae2e6fe67a27ab88 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:34:32 +0200 Subject: [PATCH 010/333] Fix property for the Gradle Daemon idle timeout --- integration-tests/gradle/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/gradle/gradle.properties b/integration-tests/gradle/gradle.properties index 9d6ae70b30592b..eb407a7e714e17 100644 --- a/integration-tests/gradle/gradle.properties +++ b/integration-tests/gradle/gradle.properties @@ -1,7 +1,7 @@ version=999-SNAPSHOT # Idle timeout in milliseconds -org.gradle.daemon.idletimeout=10000 +systemProp.org.gradle.daemon.idletimeout=10000 # Override Java default heap size calculation for the Quarkus Gradle plugin worker processes, important for CI systemProp.quarkus.gradle-worker.max-heap=256m From b70569ddb90a68123b7e5e7a8d96a35c9d5c4548 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:35:02 +0200 Subject: [PATCH 011/333] Limit heap size for Gradle daemons, force some code related system properties --- integration-tests/gradle/gradle.properties | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/integration-tests/gradle/gradle.properties b/integration-tests/gradle/gradle.properties index eb407a7e714e17..cfd6f9d25cf4fc 100644 --- a/integration-tests/gradle/gradle.properties +++ b/integration-tests/gradle/gradle.properties @@ -5,3 +5,8 @@ systemProp.org.gradle.daemon.idletimeout=10000 # Override Java default heap size calculation for the Quarkus Gradle plugin worker processes, important for CI systemProp.quarkus.gradle-worker.max-heap=256m + +# Use the miniumu Java heap sizes for a Gradle daemon, important for CI +org.gradle.jvmargs=-Xms128m -Xmx256m \ + -Dfile.encoding=UTF-8 \ + -Duser.language=en -Duser.country=US -Duser.variant= From 4b0d84566d69696cfba5761b644f59a4379ea0c9 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:36:04 +0200 Subject: [PATCH 012/333] Increase CI timeout for Gradle jobs from 80 to 120 minutes (Windows job takes about 70 minutes to succeed) --- .github/workflows/ci-actions-incremental.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 52b1a2b0cdc403..89765207d8c9bb 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -430,7 +430,7 @@ jobs: MAVEN_OPTS: -Xmx1g # Skip main in forks if: "needs.calculate-test-jobs.outputs.run_gradle == 'true' && (github.repository == 'quarkusio/quarkus' || !endsWith(github.ref, '/main'))" - timeout-minutes: 80 + timeout-minutes: 120 strategy: fail-fast: false matrix: From c37644dc5e468e28314988e50f4c235d8080e130 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:44:58 +0200 Subject: [PATCH 013/333] Allow running Gradle ITs in IntelliJ (JAVA_HOME's not set by default) --- .../quarkus/gradle/QuarkusGradleWrapperTestBase.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java index 612d7f498e5ed7..3eee7c07b75824 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java @@ -39,13 +39,18 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx File logOutput = new File(projectDir, "command-output.log"); System.out.println("$ " + String.join(" ", command)); - Process p = new ProcessBuilder() + ProcessBuilder pb = new ProcessBuilder() .directory(projectDir) .command(command) .redirectInput(ProcessBuilder.Redirect.INHERIT) - .redirectError(logOutput) .redirectOutput(logOutput) - .start(); + // Should prevent "fragmented" output (parts of stdout and stderr interleaved) + .redirectErrorStream(true); + if (System.getenv("JAVA_HOME") == null) { + // This helps running the tests in IntelliJ w/o configuring an explicit JAVA_HOME env var. + pb.environment().put("JAVA_HOME", System.getProperty("java.home")); + } + Process p = pb.start(); //long timeout for native tests //that may also need to download docker From 92fdf8cc4e7335c9ddcd389e4cb665880ad2f883 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:50:23 +0200 Subject: [PATCH 014/333] Fail Gradle ITs that expect no error (and vice versa) according to Gradle exit code --- .../gradle/GrpcMultiModuleQuarkusBuildTest.java | 2 +- .../gradle/QuarkusGradleWrapperTestBase.java | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java index 4ae97ee4a8c223..f19fdb23241522 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java @@ -35,7 +35,7 @@ public void testProtocErrorOutput() throws Exception { final Path protoDirectory = new File(projectDir, "application/src/main/proto/").toPath(); Files.copy(projectDir.toPath().resolve("invalid.proto"), protoDirectory.resolve("invalid.proto")); try { - final BuildResult buildResult = runGradleWrapper(projectDir, ":application:quarkusBuild", "--info"); + final BuildResult buildResult = runGradleWrapper(true, projectDir, ":application:quarkusBuild", "--info"); assertTrue(buildResult.getOutput().contains("invalid.proto:5:1: Missing field number.")); } finally { Files.delete(protoDirectory.resolve("invalid.proto")); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java index 3eee7c07b75824..5e6ea52ae59a4e 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java @@ -11,6 +11,8 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.Assertions; + public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase { private static final String GRADLE_WRAPPER_WINDOWS = "gradlew.bat"; @@ -23,7 +25,13 @@ protected void setupTestCommand() { } + public BuildResult runGradleWrapper(File projectDir, String... args) throws IOException, InterruptedException { + return runGradleWrapper(false, projectDir, args); + } + + public BuildResult runGradleWrapper(boolean expectError, File projectDir, String... args) + throws IOException, InterruptedException { setupTestCommand(); List command = new ArrayList<>(); command.add(getGradleWrapperCommand()); @@ -60,8 +68,14 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx } final BuildResult commandResult = BuildResult.of(logOutput); int exitCode = p.exitValue(); - if (exitCode != 0) { + + // The test failed, if the Gradle build exits with != 0 and the tests expects no failure, or if the test + // expects a failure and the exit code is 0. + if (expectError == (exitCode == 0)) { + // Only print the output, if the test does not expect a failure. printCommandOutput(projectDir, command, commandResult, exitCode); + // Fail hard, if the test does not expect a failure. + Assertions.fail("Gradle build failed with exit code %d", exitCode); } return commandResult; } From bf5a675efe51c867ae986f8f1196caf4d6f941e8 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 10:53:28 +0200 Subject: [PATCH 015/333] Programmatically disable some Gradle features in ITs --- .../gradle/BuildConfigurationTest.java | 2 -- .../gradle/FastJarFormatWorksTest.java | 4 +-- .../quarkus/gradle/JandexMultiModuleTest.java | 3 +++ .../gradle/QuarkusGradleDevToolsTestBase.java | 1 + .../gradle/QuarkusGradleWrapperTestBase.java | 26 +++++++++++++++++-- .../devmode/QuarkusDevGradleTestBase.java | 5 ++++ .../extension/ExtensionUnitTestTest.java | 2 ++ 7 files changed, 36 insertions(+), 7 deletions(-) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java index 67c522460b6e8c..22022d77024fd2 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java @@ -101,8 +101,6 @@ void verify(String packageType) { private void verifyBuild(String override) throws IOException, InterruptedException, URISyntaxException { File rootDir = getProjectDir(ROOT_PROJECT_NAME); BuildResult buildResult = runGradleWrapper(rootDir, "clean", "quarkusBuild", - // Package type is not included in the Gradle cache inputs, see https://github.com/quarkusio/quarkus/issues/30852 - "--no-build-cache", override != null ? "-Dquarkus.package.type=" + override : "-Dfoo=bar"); soft.assertThat(buildResult.unsuccessfulTasks()).isEmpty(); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java index 030a181377fa4f..6bfa73d0c71a27 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java @@ -22,9 +22,7 @@ public void testFastJarFormatWorks() throws Exception { final File projectDir = getProjectDir("test-that-fast-jar-format-works"); - BuildResult result = runGradleWrapper(projectDir, "clean", "build"); - result.getTasks().forEach((k, v) -> System.err.println(" " + k + " --> " + v)); - System.err.println(result.getOutput()); + runGradleWrapper(projectDir, "clean", "build"); final Path quarkusApp = projectDir.toPath().resolve("build").resolve("quarkus-app"); assertThat(quarkusApp).exists(); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java index 5fd64106e76972..20f71b1d5f9baa 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java @@ -10,11 +10,14 @@ public class JandexMultiModuleTest extends QuarkusGradleWrapperTestBase { @Test public void testBasicMultiModuleBuildKordamp() throws Exception { + // Kordamp Jandex plugin's not compatible w/ the Gradle configuration cache + gradleConfigurationCache(false); jandexTest("jandex-basic-multi-module-project-kordamp", ":common:jandex"); } @Test public void testBasicMultiModuleBuildJandex() throws Exception { + gradleConfigurationCache(true); jandexTest("jandex-basic-multi-module-project-vlsi", ":common:processJandexIndex"); } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java index 7a51b0246a45c5..cfd18d149b4502 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java @@ -29,6 +29,7 @@ static void disableDevToolsTestConfig() { @Override protected void setupTestCommand() { + gradleNoWatchFs(false); for (Map.Entry prop : devToolsProps.entrySet()) { setSystemProperty(prop.getKey().toString(), prop.getValue().toString()); } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java index 5e6ea52ae59a4e..3ac4c821771092 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java @@ -21,10 +21,28 @@ public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase { private Map systemProps; + private boolean configurationCacheEnable = true; + private boolean noWatchFs = true; + protected void setupTestCommand() { } + /** + * Gradle's configuration cache is enabled by default for all tests. This option can be used to disable the + * configuration test. + */ + protected void gradleConfigurationCache(boolean configurationCacheEnable) { + this.configurationCacheEnable = configurationCacheEnable; + } + + /** + * Gradle is run by default with {@code --no-watch-fs} to reduce I/O load during tests. Some tests might run into issues + * with this option. + */ + protected void gradleNoWatchFs(boolean noWatchFs) { + this.noWatchFs = noWatchFs; + } public BuildResult runGradleWrapper(File projectDir, String... args) throws IOException, InterruptedException { return runGradleWrapper(false, projectDir, args); @@ -37,9 +55,13 @@ public BuildResult runGradleWrapper(boolean expectError, File projectDir, String command.add(getGradleWrapperCommand()); addSystemProperties(command); command.add("-Dorg.gradle.console=plain"); - command.add("-Dorg.gradle.daemon=false"); - command.add("--configuration-cache"); + if (configurationCacheEnable) { + command.add("--configuration-cache"); + } command.add("--stacktrace"); + if (noWatchFs) { + command.add("--no-watch-fs"); + } command.add("--info"); command.add("--daemon"); command.addAll(Arrays.asList(args)); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java index fc62794cebc425..3746d3ecef9840 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java @@ -27,6 +27,11 @@ public abstract class QuarkusDevGradleTestBase extends QuarkusGradleWrapperTestB private Future quarkusDev; protected File projectDir; + @Override + protected void setupTestCommand() { + gradleNoWatchFs(false); + } + @Test public void main() throws Exception { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java index e664f3ffe50db1..946138dc2cb3d1 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java @@ -13,6 +13,8 @@ public class ExtensionUnitTestTest extends QuarkusGradleWrapperTestBase { @Test public void shouldRunTestWithSuccess() throws Exception { + gradleConfigurationCache(false); + File projectDir = getProjectDir("extensions/simple-extension"); BuildResult buildResult = runGradleWrapper(projectDir, "clean", ":deployment:test", "--no-build-cache"); From ccf168a0e5cb6d3fe6cec59fcfd50b919a888c8a Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 13:09:16 +0200 Subject: [PATCH 016/333] Correct JDK-19 -> 20 exclusions (Gradle 8.1) --- integration-tests/gradle/pom.xml | 6 +++--- .../devmode/BasicKotlinApplicationModuleDevModeTest.java | 2 +- .../gradle/devmode/MultiModuleKotlinProjectDevModeTest.java | 5 +---- .../gradle/devmode/MultiSourceProjectDevModeTest.java | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/integration-tests/gradle/pom.xml b/integration-tests/gradle/pom.xml index 40d9246502ea7d..c85a73c94a0458 100644 --- a/integration-tests/gradle/pom.xml +++ b/integration-tests/gradle/pom.xml @@ -430,13 +430,13 @@ - jdk19-workarounds + jdk20-workarounds - [19,) + [20,) - failsOnJDK19 + failsOnJDK20 diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java index 56c7dafba0f16e..80d3ca4602e146 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java @@ -6,7 +6,7 @@ import com.google.common.collect.ImmutableMap; -@org.junit.jupiter.api.Tag("failsOnJDK19") +@org.junit.jupiter.api.Tag("failsOnJDK20") public class BasicKotlinApplicationModuleDevModeTest extends QuarkusDevGradleTestBase { @Override diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java index a59a5d9c0004c5..a86c659a423808 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java @@ -2,12 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.jupiter.api.Disabled; - import com.google.common.collect.ImmutableMap; -@Disabled -@org.junit.jupiter.api.Tag("failsOnJDK19") +@org.junit.jupiter.api.Tag("failsOnJDK20") public class MultiModuleKotlinProjectDevModeTest extends QuarkusDevGradleTestBase { @Override diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java index f9420d4a123c72..2ed7b5895a71dd 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java @@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableMap; -@org.junit.jupiter.api.Tag("failsOnJDK19") +@org.junit.jupiter.api.Tag("failsOnJDK20") public class MultiSourceProjectDevModeTest extends QuarkusDevGradleTestBase { @Override From cc8b86b4af6151fb9f3c8a2f8e22ddffa482e6f2 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Fri, 14 Apr 2023 13:47:39 +0200 Subject: [PATCH 017/333] Increase dev-mode timeout on Windows to 90 seconds --- .../gradle/devmode/QuarkusDevGradleTestBase.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java index 3746d3ecef9840..d773b91b08f811 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -122,7 +123,7 @@ protected String getHttpResponse() { } protected String getHttpResponse(String path) { - return getHttpResponse(path, 1, TimeUnit.MINUTES); + return getHttpResponse(path, devModeTimeoutSeconds(), TimeUnit.SECONDS); } protected String getHttpResponse(String path, long timeout, TimeUnit tu) { @@ -146,7 +147,16 @@ protected void replace(String srcFile, Map tokens) { } protected void assertUpdatedResponseContains(String path, String value) { - assertUpdatedResponseContains(path, value, 1, TimeUnit.MINUTES); + assertUpdatedResponseContains(path, value, devModeTimeoutSeconds(), TimeUnit.SECONDS); + } + + protected int devModeTimeoutSeconds() { + // It's a wild guess, but maybe Windows is just slower - at least: a successful Gradle-CI-jobs on Windows is + // 2.5x slower than the same Gradle-CI-job on Linux. + if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")) { + return 90; + } + return 60; } protected void assertUpdatedResponseContains(String path, String value, long waitAtMost, TimeUnit timeUnit) { From 32f74e09f323c66d41becc5d24a9d92d7a3f5737 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 12 Apr 2023 13:14:44 +0200 Subject: [PATCH 018/333] Gradle integration tests: tackle CI issues / final changes The major issue was (very likely) that in the `Gradle Tests - JDK 11 Windows` CI job (at least) the Quarkus process running in dev-mode somehow "survived". The observed pattern is that one dev-mode test ran successfully and all following dev-mode tests failed with test timeout exceptions (awaitability). Adding some code to collect the `List` of child `ProcessHandle`s before calling `ProcessHandle.destroy()` plus a (potentially over-cautious) `ProcessHandle.destroyForcibly()` seems to solve the Gradle-Windows-CI issue. A couple of CI runs did not fail. This change adds "process dumps" (not really useful w/ Windows :( ) from before and after the test run to the failed test outputs. --- .../devmode/QuarkusDevGradleTestBase.java | 74 +++++++++++++------ .../test/devmode/util/DevModeTestUtils.java | 17 ++++- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java index d773b91b08f811..2602e2e0e502fb 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java @@ -7,6 +7,8 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutorService; @@ -15,6 +17,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; @@ -35,22 +39,49 @@ protected void setupTestCommand() { @Test public void main() throws Exception { - projectDir = getProjectDir(); beforeQuarkusDev(); ExecutorService executor = null; AtomicReference buildResult = new AtomicReference<>(); + List processesBeforeTest = dumpProcesses(); + List processesAfterTest = Collections.emptyList(); try { - executor = Executors.newSingleThreadExecutor(); - quarkusDev = executor.submit(() -> { - try { - buildResult.set(build()); - } catch (Exception e) { - throw new IllegalStateException("Failed to build the project", e); + try { + executor = Executors.newSingleThreadExecutor(); + quarkusDev = executor.submit(() -> { + try { + buildResult.set(build()); + } catch (Exception e) { + throw new IllegalStateException("Failed to build the project", e); + } + }); + testDevMode(); + } finally { + processesAfterTest = dumpProcesses(); + + if (quarkusDev != null) { + quarkusDev.cancel(true); + } + if (executor != null) { + executor.shutdownNow(); } - }); - testDevMode(); + + // Kill all processes that were (indirectly) spawned by the current process. + List childProcesses = DevModeTestUtils.killDescendingProcesses(); + + DevModeTestUtils.awaitUntilServerDown(); + + // sanity: forcefully terminate left-over processes + childProcesses.forEach(ProcessHandle::destroyForcibly); + } } catch (Exception | AssertionError e) { + System.err.println("PROCESSES BEFORE TEST:"); + processesBeforeTest.forEach(System.err::println); + System.err.println("PROCESSES AFTER TEST (BEFORE CLEANUP):"); + processesAfterTest.forEach(System.err::println); + System.err.println("PROCESSES AFTER CLEANUP:"); + dumpProcesses().forEach(System.err::println); + if (buildResult.get() != null) { System.err.println("BELOW IS THE CAPTURED LOGGING OF THE FAILED GRADLE TEST PROJECT BUILD"); System.err.println(buildResult.get().getOutput()); @@ -71,24 +102,25 @@ public void main() throws Exception { } throw e; } finally { - if (quarkusDev != null) { - quarkusDev.cancel(true); - } - if (executor != null) { - executor.shutdownNow(); - } - - // Kill all processes that were (indirectly) spawned by the current process. - DevModeTestUtils.killDescendingProcesses(); - - DevModeTestUtils.awaitUntilServerDown(); - if (projectDir != null && projectDir.isDirectory()) { FileUtils.deleteQuietly(projectDir); } } } + public static List dumpProcesses() { + // ProcessHandle.Info.command()/arguments()/commandLine() are always empty on Windows: + // https://bugs.openjdk.java.net/browse/JDK-8176725 + ProcessHandle current = ProcessHandle.current(); + return Stream.concat(Stream.of(current), current.descendants()).map(p -> { + ProcessHandle.Info i = p.info(); + return String.format("PID %8d (%8d) started:%s CPU:%s - %s", p.pid(), + p.parent().map(ProcessHandle::pid).orElse(-1L), + i.startInstant().orElse(null), i.totalCpuDuration().orElse(null), + i.commandLine().orElse("")); + }).collect(Collectors.toList()); + } + protected BuildResult build() throws Exception { return runGradleWrapper(projectDir, buildArguments()); } diff --git a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java index 55c7315b2ea2b7..22c21602bc37da 100644 --- a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java +++ b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java @@ -9,25 +9,36 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; public class DevModeTestUtils { - public static void killDescendingProcesses() { + public static List killDescendingProcesses() { // Warning: Do not try to evaluate ProcessHandle.Info.arguments() or .commandLine() as those are always empty on Windows: // https://bugs.openjdk.java.net/browse/JDK-8176725 - ProcessHandle.current().descendants() + // + // Intentionally collecting the ProcessHandles before calling .destroy(), because it seemed that, at least on + // Windows, not all processes were properly killed, leaving (some) processes around, causing following dev-mode + // tests to time-out. + List childProcesses = ProcessHandle.current().descendants() // destroy younger descendants first .sorted((ph1, ph2) -> ph2.info().startInstant().orElse(Instant.EPOCH) .compareTo(ph1.info().startInstant().orElse(Instant.EPOCH))) - .forEach(ProcessHandle::destroy); + .collect(Collectors.toList()); + + childProcesses.forEach(ProcessHandle::destroy); + + // Returning all child processes for callers that want to do a "kill -9" + return childProcesses; } public static void filter(File input, Map variables) throws IOException { From 4d3a54329b39bc9d87679f78213dc5222efe7243 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 14 Apr 2023 16:21:24 +0200 Subject: [PATCH 019/333] Qute build steps - refactoring and performance improvements - replace the assignability cache and HierarchyIndexer with AssignabilityCheck and check supertypes instead of subtypes; this works better with computing index and allows the check to cache the misses - match extension method name first (before checking the assignability of the first parameter) - dev mode - do not regenerate the value resolvers for non-application classes during hot reload --- .../deployment/MessageBundleProcessor.java | 12 +- .../qute/deployment/QuteProcessor.java | 175 +++++++++++------- .../io/quarkus/qute/deployment/Types.java | 142 ++++---------- .../io/quarkus/qute/deployment/TypesTest.java | 26 ++- 4 files changed, 170 insertions(+), 185 deletions(-) diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 2ceb58c7bab1f3..96f61624605349 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -88,8 +88,7 @@ import io.quarkus.qute.deployment.QuteProcessor.JavaMemberLookupConfig; import io.quarkus.qute.deployment.QuteProcessor.MatchResult; import io.quarkus.qute.deployment.TemplatesAnalysisBuildItem.TemplateAnalysis; -import io.quarkus.qute.deployment.Types.AssignableInfo; -import io.quarkus.qute.deployment.Types.HierarchyIndexer; +import io.quarkus.qute.deployment.Types.AssignabilityCheck; import io.quarkus.qute.generator.Descriptors; import io.quarkus.qute.generator.ValueResolverGenerator; import io.quarkus.qute.i18n.Localized; @@ -455,8 +454,7 @@ public String apply(String id) { JavaMemberLookupConfig lookupConfig = new QuteProcessor.FixedJavaMemberLookupConfig(index, QuteProcessor.initDefaultMembersFilter(), false); - Map assignableCache = new HashMap<>(); - HierarchyIndexer hierarchyIndexer = new HierarchyIndexer(index); + AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index); // bundle name -> (key -> method) Map> bundleToMethods = new HashMap<>(); @@ -576,10 +574,10 @@ public String apply(String id) { implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, namespaceTemplateData, regularExtensionMethods, namespaceExtensionMethods, - assignableCache, hierarchyIndexer); + assignabilityCheck); MatchResult match = results.get(param.toOriginalString()); - if (match != null && !match.isEmpty() && !Types.isAssignableFrom(match.type(), - methodParams.get(idx), index, assignableCache)) { + if (match != null && !match.isEmpty() && !assignabilityCheck.isAssignableFrom(match.type(), + methodParams.get(idx))) { incorrectExpressions .produce(new IncorrectExpressionBuildItem(expression.toOriginalString(), "Message bundle method " + method.declaringClass().name() + "#" + diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 568b5441a7da2d..e0653607520062 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -63,6 +63,7 @@ import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem; +import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem; import io.quarkus.arc.deployment.QualifierRegistrarBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; @@ -83,6 +84,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; +import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; @@ -125,8 +127,7 @@ import io.quarkus.qute.deployment.TypeCheckExcludeBuildItem.TypeCheck; import io.quarkus.qute.deployment.TypeInfos.Info; import io.quarkus.qute.deployment.TypeInfos.TypeInfo; -import io.quarkus.qute.deployment.Types.AssignableInfo; -import io.quarkus.qute.deployment.Types.HierarchyIndexer; +import io.quarkus.qute.deployment.Types.AssignabilityCheck; import io.quarkus.qute.generator.ExtensionMethodGenerator; import io.quarkus.qute.generator.ExtensionMethodGenerator.NamespaceResolverCreator; import io.quarkus.qute.generator.ExtensionMethodGenerator.NamespaceResolverCreator.ResolveCreator; @@ -682,7 +683,7 @@ void validateCheckedFragments(List validatio return; } IndexView index = beanArchiveIndex.getIndex(); - Map assignableCache = new HashMap<>(); + AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index); String[] hintPrefixes = { LoopSectionHelper.Factory.HINT_PREFIX, WhenSectionHelper.Factory.HINT_PREFIX, SetSectionHelper.Factory.HINT_PREFIX }; Set globals = templateGlobals.stream().map(TemplateGlobalBuildItem::getName) @@ -747,7 +748,7 @@ void validateCheckedFragments(List validatio String paramName = e.getKey(); MethodParameterInfo param = validation.method.parameters().stream() .filter(mp -> mp.name().equals(paramName)).findFirst().orElse(null); - if (param == null || !Types.isAssignableFrom(e.getValue(), param.type(), index, assignableCache)) { + if (param == null || !assignabilityCheck.isAssignableFrom(e.getValue(), param.type())) { throw new TemplateException( validation.method.declaringClass().name().withoutPackagePrefix() + "#" + validation.method.name() + "() must declare a parameter of name [" + paramName @@ -831,6 +832,8 @@ void validateExpressions(TemplatesAnalysisBuildItem templatesAnalysis, List templateData, QuteConfig config) { + long start = System.nanoTime(); + // =================================================== // Initialize shared structures needed for validation // =================================================== @@ -862,8 +865,7 @@ public String apply(String id) { .filter(Predicate.not(TemplateExtensionMethodBuildItem::hasNamespace)).collect(Collectors.toList()); JavaMemberLookupConfig lookupConfig = new FixedJavaMemberLookupConfig(index, initDefaultMembersFilter(), false); - Map assignableCache = new HashMap<>(); - HierarchyIndexer hierarchyIndexer = new HierarchyIndexer(index); + AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index); int expressionsValidated = 0; final List> excludes = new ArrayList<>(); @@ -894,12 +896,12 @@ public String apply(String id) { incorrectExpressions, expression, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, namespaceTemplateData, regularExtensionMethods, - namespaceExtensionMethods, assignableCache, hierarchyIndexer); + namespaceExtensionMethods, assignabilityCheck); generatedIdsToMatches.put(expression.getGeneratedId(), match); } // Validate default values of parameter declarations - validateDefaultValuesOfParameterDeclarations(templateAnalysis, index, hierarchyIndexer, assignableCache, + validateDefaultValuesOfParameterDeclarations(templateAnalysis, index, assignabilityCheck, generatedIdsToMatches, templateIdToPathFun, incorrectExpressions); expressionMatches @@ -908,7 +910,8 @@ public String apply(String id) { expressionsValidated += generatedIdsToMatches.size(); } - LOGGER.debugf("Validated %s expressions", expressionsValidated); + LOGGER.debugf("Validated %s expressions in %s ms", expressionsValidated, + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); // ========================================================================== // Register implicit value resolvers for classes collected during validation @@ -927,7 +930,7 @@ public String apply(String id) { } private static void validateDefaultValuesOfParameterDeclarations(TemplateAnalysis templateAnalysis, IndexView index, - HierarchyIndexer hierarchyIndexer, Map assignableCache, + AssignabilityCheck assignabilityCheck, Map generatedIdsToMatches, Function templateIdToPathFun, BuildProducer incorrectExpressions) { for (ParameterDeclaration parameterDeclaration : templateAnalysis.parameterDeclarations) { @@ -935,7 +938,7 @@ private static void validateDefaultValuesOfParameterDeclarations(TemplateAnalysi if (defaultValue != null) { MatchResult match; if (defaultValue.isLiteral()) { - match = new MatchResult(hierarchyIndexer, assignableCache); + match = new MatchResult(assignabilityCheck); setMatchValues(match, defaultValue, generatedIdsToMatches, index); } else { match = generatedIdsToMatches.get(defaultValue.getGeneratedId()); @@ -953,7 +956,7 @@ private static void validateDefaultValuesOfParameterDeclarations(TemplateAnalysi + parameterDeclaration.getKey() + "] in " + defaultValue.getOrigin().toString()); } - if (!Types.isAssignableFrom(info.asTypeInfo().resolvedType, match.type(), index, assignableCache)) { + if (!assignabilityCheck.isAssignableFrom(info.asTypeInfo().resolvedType, match.type())) { incorrectExpressions.produce(new IncorrectExpressionBuildItem(defaultValue.toOriginalString(), "The type of the default value [" + match.type() + "] does not match the type of the parameter declaration [" @@ -1021,7 +1024,7 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis Map namespaceTemplateData, List regularExtensionMethods, Map> namespaceToExtensionMethods, - Map assignableCache, HierarchyIndexer hierarchyIndexer) { + AssignabilityCheck assignabilityCheck) { LOGGER.debugf("Validate %s from %s", expression, expression.getOrigin()); @@ -1031,9 +1034,9 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis validateParametersOfNestedVirtualMethods(config, templateAnalysis, results, excludes, incorrectExpressions, expression, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, namespaceTemplateData, regularExtensionMethods, - namespaceToExtensionMethods, assignableCache, hierarchyIndexer); + namespaceToExtensionMethods, assignabilityCheck); - MatchResult match = new MatchResult(hierarchyIndexer, assignableCache); + MatchResult match = new MatchResult(assignabilityCheck); // ====================== // Process the namespace @@ -1076,7 +1079,7 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis // Process the root part // ====================== RootResult rootResult = processRoot(expression, match, root, iterator, templateAnalysis, index, incorrectExpressions, - rootClazz, parts, results, generatedIdsToMatches, templateIdToPathFun, assignableCache, namespaceResult); + rootClazz, parts, results, generatedIdsToMatches, templateIdToPathFun, assignabilityCheck, namespaceResult); if (rootResult.ignoring) { return match; } @@ -1107,7 +1110,7 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis if (match.clazz() != null) { if (info.isVirtualMethod()) { member = findMethod(info.part.asVirtualMethod(), match.clazz(), expression, index, - templateIdToPathFun, results, lookupConfig, assignableCache); + templateIdToPathFun, results, lookupConfig, assignabilityCheck); if (member != null) { membersUsed.add(member.asMethod().name()); } @@ -1124,7 +1127,7 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis if (member == null) { // Try to find an extension method extensionMethod = findTemplateExtensionMethod(info, match.type(), regularExtensionMethods, expression, - index, templateIdToPathFun, results, assignableCache); + index, templateIdToPathFun, results, assignabilityCheck); if (extensionMethod != null) { type = resolveType(extensionMethod.getMethod(), match, index, extensionMethod, results, info); // Test whether the validation of extension method should be skipped @@ -1194,7 +1197,7 @@ private static RootResult processRoot(Expression expression, MatchResult match, Map results, Map generatedIdsToMatches, Function templateIdToPathFun, - Map assignableCache, + AssignabilityCheck assignabilityCheck, NamespaceResult namespace) { Iterator iterator = it; boolean ignoring = false; @@ -1203,7 +1206,7 @@ private static RootResult processRoot(Expression expression, MatchResult match, // Namespace is used and at least one namespace extension method exists for the given namespace TemplateExtensionMethodBuildItem extensionMethod = findTemplateExtensionMethod(root, null, namespace.extensionMethods, - expression, index, templateIdToPathFun, results, assignableCache); + expression, index, templateIdToPathFun, results, assignabilityCheck); if (extensionMethod != null) { MethodInfo method = extensionMethod.getMethod(); ClassInfo returnType = index.getClassByName(method.returnType().name()); @@ -1275,7 +1278,7 @@ private static boolean processArray(Info info, MatchResult match) { // myArray[0], myArray.1 try { Integer.parseInt(name); - match.setValues(null, match.type().asArrayType().component()); + match.setValues(null, match.type().asArrayType().constituent()); return true; } catch (NumberFormatException e) { // not an integer index @@ -1289,7 +1292,7 @@ private static boolean processArray(Info info, MatchResult match) { Expression param = params.get(0); Object literalValue = param.getLiteral(); if (literalValue == null || literalValue instanceof Integer) { - match.setValues(null, match.type().asArrayType().component()); + match.setValues(null, match.type().asArrayType().constituent()); return true; } } else if (name.equals("take") || name.equals("takeLast")) { @@ -1478,7 +1481,7 @@ private static void validateParametersOfNestedVirtualMethods(QuteConfig config, Map namespaceTemplateData, List regularExtensionMethods, Map> namespaceExtensionMethods, - Map assignableCache, HierarchyIndexer hierarchyIndexer) { + AssignabilityCheck assignabilityCheck) { for (Expression.Part part : expression.getParts()) { if (part.isVirtualMethod()) { for (Expression param : part.asVirtualMethod().getParameters()) { @@ -1490,8 +1493,7 @@ private static void validateParametersOfNestedVirtualMethods(QuteConfig config, validateNestedExpressions(config, templateAnalysis, null, results, excludes, incorrectExpressions, param, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, - namespaceTemplateData, regularExtensionMethods, namespaceExtensionMethods, assignableCache, - hierarchyIndexer); + namespaceTemplateData, regularExtensionMethods, namespaceExtensionMethods, assignabilityCheck); } } } @@ -1709,6 +1711,8 @@ void generateValueResolvers(QuteConfig config, BuildProducer panacheEntityClasses, List templateData, List templateGlobals, + LiveReloadBuildItem liveReloadBuildItem, + CompletedApplicationClassPredicateBuildItem applicationClassPredicate, BuildProducer generatedResolvers, BuildProducer reflectiveClass, BuildProducer generatedInitializers) { @@ -1738,6 +1742,12 @@ public String apply(String name) { } }); + ExistingValueResolvers existingValueResolvers = liveReloadBuildItem.getContextObject(ExistingValueResolvers.class); + if (existingValueResolvers == null) { + existingValueResolvers = new ExistingValueResolvers(); + liveReloadBuildItem.setContextObject(ExistingValueResolvers.class, existingValueResolvers); + } + ValueResolverGenerator.Builder builder = ValueResolverGenerator.builder() .setIndex(index).setClassOutput(classOutput); @@ -1760,11 +1770,15 @@ public Function apply(ClassInfo clazz) { Set controlled = new HashSet<>(); Map uncontrolled = new HashMap<>(); for (TemplateDataBuildItem data : templateData) { - processTemplateData(data, controlled, uncontrolled, builder); + processTemplateData(data, controlled, uncontrolled, builder, existingValueResolvers, applicationClassPredicate); } for (ImplicitValueResolverBuildItem implicit : implicitClasses) { DotName implicitClassName = implicit.getClazz().name(); + if (existingValueResolvers.contains(implicitClassName)) { + // A non-application value resolver already generated + continue; + } if (controlled.contains(implicitClassName)) { LOGGER.debugf("Implicit value resolver for %s ignored: class is annotated with @TemplateData", implicitClassName); @@ -1776,6 +1790,7 @@ public Function apply(ClassInfo clazz) { continue; } builder.addClass(implicit.getClazz(), implicit.getTemplateData()); + existingValueResolvers.add(implicitClassName, applicationClassPredicate); } ValueResolverGenerator generator = builder.build(); @@ -1789,6 +1804,11 @@ public Function apply(ClassInfo clazz) { Map namespaceToClass = new HashMap<>(); for (TemplateExtensionMethodBuildItem templateExtension : templateExtensionMethods) { + if (existingValueResolvers.contains(templateExtension.getMethod())) { + continue; + } + existingValueResolvers.add(templateExtension.getMethod(), applicationClassPredicate); + if (templateExtension.hasNamespace()) { // Group extension methods declared on the same class by namespace DotName declaringClassName = templateExtension.getMethod().declaringClass().name(); @@ -1846,7 +1866,7 @@ public Function apply(ClassInfo clazz) { generatedValueResolvers.addAll(extensionMethodGenerator.getGeneratedTypes()); - LOGGER.debugf("Generated value resolvers: %s", generatedValueResolvers); + LOGGER.debugf("Generated %s value resolvers: %s", generatedValueResolvers.size(), generatedValueResolvers); for (String generatedType : generatedValueResolvers) { generatedResolvers.produce(new GeneratedValueResolverBuildItem(generatedType)); @@ -1875,6 +1895,37 @@ public Function apply(ClassInfo clazz) { } } + /** + * Tracks non-application value resolvers that have already been generated. There is no need to spend time + * generating them again on a hot reload. + */ + static class ExistingValueResolvers { + + final Set identifiers = new HashSet<>(); + + boolean contains(DotName className) { + return identifiers.contains(className.toString()); + } + + boolean contains(MethodInfo extensionMethod) { + return identifiers.contains(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString()); + } + + boolean add(DotName className, Predicate applicationClassPredicate) { + if (!applicationClassPredicate.test(className)) { + return identifiers.add(className.toString()); + } + return false; + } + + boolean add(MethodInfo extensionMethod, Predicate applicationClassPredicate) { + if (!applicationClassPredicate.test(extensionMethod.declaringClass().name())) { + return identifiers.add(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString()); + } + return false; + } + } + @BuildStep void collectTemplates(ApplicationArchivesBuildItem applicationArchives, CurateOutcomeBuildItem curateOutcome, @@ -2502,7 +2553,7 @@ static void processLoopElementHint(MatchResult match, IndexView index, Expressio } Type matchType = null; if (match.isArray()) { - matchType = match.type().asArrayType().component(); + matchType = match.type().asArrayType().constituent(); } else if (match.isClass() || match.isParameterizedType()) { Set closure = Types.getTypeClosure(match.clazz, Types.buildResolvedMap( match.getParameterizedTypeArguments(), match.getTypeParameters(), new HashMap<>(), index), index); @@ -2564,15 +2615,13 @@ static Type extractMatchType(Set closure, DotName matchName, Function assignableCache; + private final AssignabilityCheck assignabilityCheck; private ClassInfo clazz; private Type type; - MatchResult(HierarchyIndexer indexer, Map assignableCache) { - this.indexer = indexer; - this.assignableCache = assignableCache; + MatchResult(AssignabilityCheck assignabilityCheck) { + this.assignabilityCheck = assignabilityCheck; } List getParameterizedTypeArguments() { @@ -2626,21 +2675,18 @@ boolean isEmpty() { void autoExtractType() { if (clazz != null) { - // Make sure that hierarchy of the matching class is indexed - indexer.indexHierarchy(clazz); - boolean hasCompletionStage = Types.isAssignableFrom(Names.COMPLETION_STAGE, clazz.name(), indexer.index, - assignableCache); - boolean hasUni = hasCompletionStage ? false - : Types.isAssignableFrom(Names.UNI, clazz.name(), indexer.index, assignableCache); + boolean hasCompletionStage = assignabilityCheck.isAssignableFrom(Names.COMPLETION_STAGE, clazz.name()); + boolean hasUni = hasCompletionStage ? false : assignabilityCheck.isAssignableFrom(Names.UNI, clazz.name()); if (hasCompletionStage || hasUni) { Set closure = Types.getTypeClosure(clazz, Types.buildResolvedMap( - getParameterizedTypeArguments(), getTypeParameters(), new HashMap<>(), indexer.index), - indexer.index); + getParameterizedTypeArguments(), getTypeParameters(), new HashMap<>(), + assignabilityCheck.computingIndex), + assignabilityCheck.computingIndex); // CompletionStage> => List // Uni> => List this.type = extractMatchType(closure, hasCompletionStage ? Names.COMPLETION_STAGE : Names.UNI, FIRST_PARAM_TYPE_EXTRACT_FUN); - this.clazz = indexer.index.getClassByName(type.name()); + this.clazz = assignabilityCheck.computingIndex.getClassByName(type.name()); } } } @@ -2649,22 +2695,21 @@ void autoExtractType() { private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info info, Type matchType, List templateExtensionMethods, Expression expression, IndexView index, Function templateIdToPathFun, Map results, - Map assignableCache) { + AssignabilityCheck assignabilityCheck) { if (!info.isProperty() && !info.isVirtualMethod()) { return null; } String name = info.isProperty() ? info.asProperty().name : info.asVirtualMethod().name; for (TemplateExtensionMethodBuildItem extensionMethod : templateExtensionMethods) { - if (matchType != null - && !Types.isAssignableFrom(extensionMethod.getMatchType(), matchType, index, assignableCache)) { - // If "Bar extends Foo" then Bar should be matched for the extension method "int get(Foo)" - continue; - } if (!extensionMethod.matchesName(name)) { // Name does not match continue; } - + if (matchType != null + && !assignabilityCheck.isAssignableFrom(extensionMethod.getMatchType(), matchType)) { + // If "Bar extends Foo" then Bar should be matched for the extension method "int get(Foo)" + continue; + } List evaluatedParams = extensionMethod.getParams().evaluated(); if (evaluatedParams.size() > 0 && !info.isVirtualMethod()) { // If method accepts additional params the info must be a virtual method @@ -2701,12 +2746,11 @@ private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info Type paramType; if (isVarArgs && (idx >= lastParamIdx)) { // Replace the type for varargs methods - paramType = evaluatedParams.get(lastParamIdx).type.asArrayType().component(); + paramType = evaluatedParams.get(lastParamIdx).type.asArrayType().constituent(); } else { paramType = evaluatedParams.get(idx).type; } - if (!Types.isAssignableFrom(paramType, - result.type, index, assignableCache)) { + if (!assignabilityCheck.isAssignableFrom(paramType, result.type)) { matches = false; break; } @@ -2800,7 +2844,7 @@ private static void addInterfaces(ClassInfo clazz, IndexView index, Set private static AnnotationTarget findMethod(VirtualMethodPart virtualMethod, ClassInfo clazz, Expression expression, IndexView index, Function templateIdToPathFun, Map results, - JavaMemberLookupConfig config, Map assignableCache) { + JavaMemberLookupConfig config, AssignabilityCheck assignabilityCheck) { // Find a method with the given name, matching number of params and assignable parameter types Set interfaceNames = config.declaredMembersOnly() ? null : new HashSet<>(); while (clazz != null) { @@ -2810,7 +2854,7 @@ private static AnnotationTarget findMethod(VirtualMethodPart virtualMethod, Clas for (MethodInfo method : clazz.methods()) { if (config.filter().test(method) && methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, results, - assignableCache)) { + assignabilityCheck)) { return method; } } @@ -2829,7 +2873,7 @@ && methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, for (MethodInfo method : interfaceClassInfo.methods()) { if (config.filter().test(method) && methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, results, - assignableCache)) { + assignabilityCheck)) { return method; } } @@ -2842,7 +2886,7 @@ && methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtualMethod, Expression expression, IndexView index, Function templateIdToPathFun, Map results, - Map assignableCache) { + AssignabilityCheck assignabilityCheck) { if (!method.name().equals(virtualMethod.getName())) { return false; @@ -2875,12 +2919,11 @@ private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtua Type paramType; if (isVarArgs && idx >= lastParamIdx) { // Replace the type for varargs methods - paramType = parameters.get(lastParamIdx).asArrayType().component(); + paramType = parameters.get(lastParamIdx).asArrayType().constituent(); } else { paramType = parameters.get(idx); } - if (!Types.isAssignableFrom(paramType, - result.type, index, assignableCache)) { + if (!assignabilityCheck.isAssignableFrom(paramType, result.type)) { matches = false; break; } @@ -2899,14 +2942,22 @@ private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtua } private void processTemplateData(TemplateDataBuildItem templateData, - Set controlled, Map uncontrolled, ValueResolverGenerator.Builder builder) { + Set controlled, Map uncontrolled, ValueResolverGenerator.Builder builder, + ExistingValueResolvers existingValueResolvers, + CompletedApplicationClassPredicateBuildItem applicationClassPredicate) { + DotName targetClassName = templateData.getTargetClass().name(); + if (existingValueResolvers.contains(targetClassName)) { + return; + } if (templateData.isTargetAnnotatedType()) { - controlled.add(templateData.getTargetClass().name()); + controlled.add(targetClassName); builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance()); + existingValueResolvers.add(targetClassName, applicationClassPredicate); } else { // At this point we can be sure that multiple unequal @TemplateData do not exist for a specific target - uncontrolled.computeIfAbsent(templateData.getTargetClass().name(), name -> { + uncontrolled.computeIfAbsent(targetClassName, name -> { builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance()); + existingValueResolvers.add(targetClassName, applicationClassPredicate); return templateData.getAnnotationInstance(); }); } diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Types.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Types.java index 722ec3220809d0..4cb99a9c95e8b0 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Types.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Types.java @@ -1,14 +1,13 @@ package io.quarkus.qute.deployment; -import java.util.Collection; +import java.util.ArrayDeque; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -127,122 +126,61 @@ static boolean containsTypeVariable(Type type) { } } if (type.kind() == Type.Kind.ARRAY) { - return containsTypeVariable(type.asArrayType().component()); + return containsTypeVariable(type.asArrayType().constituent()); } return false; } - static boolean isAssignableFrom(Type type1, Type type2, IndexView index, Map assignableCache) { - if (type1.kind() == Kind.ARRAY) { - return type2.kind() == Kind.ARRAY - ? isAssignableFrom(type1.asArrayType().component(), type2.asArrayType().component(), index, - assignableCache) - : false; - } - - return Types.isAssignableFrom(box(type1).name(), box(type2).name(), index, assignableCache); - } - - static class AssignableInfo { + // This class is not thread-safe and should not be used concurrently. + static class AssignabilityCheck { - static AssignableInfo from(ClassInfo classInfo, IndexView index) { - if (classInfo.isInterface()) { - return new AssignableInfo(null, toNames(index.getAllKnownImplementors(classInfo.name())), - toNames(index.getAllKnownSubinterfaces(classInfo.name()))); - } else { - return new AssignableInfo(toNames(index.getAllKnownSubclasses(classInfo.name())), null, null); - } - } + final Map> superTypesCache; + final IndexView computingIndex; - private static Set toNames(Collection classes) { - return classes.stream().map(ClassInfo::name).collect(Collectors.toSet()); + AssignabilityCheck(IndexView beanArchiveIndex) { + this.superTypesCache = new HashMap<>(); + this.computingIndex = beanArchiveIndex; } - final Set subclasses; - final Set implementors; - final Set subInterfaces; - - AssignableInfo(Set subclasses, Set implementors, Set subInterfaces) { - this.subclasses = subclasses; - this.implementors = implementors; - this.subInterfaces = subInterfaces; - } - - boolean isAssignableFrom(DotName clazz) { - if (clazz == null) { - return false; - } - if (subclasses != null && subclasses.contains(clazz)) { - return true; + boolean isAssignableFrom(Type type1, Type type2) { + if (type1.kind() == Kind.ARRAY) { + return type2.kind() == Kind.ARRAY + ? isAssignableFrom(type1.asArrayType().constituent(), type2.asArrayType().constituent()) + : false; } - if (implementors != null && implementors.contains(clazz)) { - return true; - } - return subInterfaces != null && subInterfaces.contains(clazz); + return isAssignableFrom(box(type1).name(), box(type2).name()); } - } - - static boolean isAssignableFrom(DotName className1, DotName className2, IndexView index, - Map assignableCache) { - // java.lang.Object is assignable from any type - if (className1.equals(DotNames.OBJECT)) { - return true; - } - // type1 is the same as type2 - if (className1.equals(className2)) { - return true; - } - ClassInfo class1 = index.getClassByName(className1); - if (class1 == null) { - // Not found in the index - return false; - } - AssignableInfo assignableInfo = assignableCache.get(className1); - if (assignableInfo == null) { - // No cached info - assignableInfo = AssignableInfo.from(class1, index); - assignableCache.put(className1, assignableInfo); - return assignableInfo.isAssignableFrom(className2); - } else { - if (assignableInfo.isAssignableFrom(className2)) { + boolean isAssignableFrom(DotName className1, DotName className2) { + // java.lang.Object is assignable from any type + if (className1.equals(DotNames.OBJECT)) { return true; } - // Cached info does not match - try to update the assignable info (a computing index is used) - assignableInfo = AssignableInfo.from(class1, index); - if (assignableInfo.isAssignableFrom(className2)) { - // Update the cache - assignableCache.put(className1, assignableInfo); + if (className1.equals(className2)) { return true; } - } - return false; - } - - // This class is not thread-safe - static class HierarchyIndexer { - - final IndexView index; - final Set processed; - - public HierarchyIndexer(IndexView index) { - this.index = Objects.requireNonNull(index); - this.processed = new HashSet<>(); - } - - void indexHierarchy(ClassInfo classInfo) { - if (classInfo != null && processed.add(classInfo.name())) { - LOG.debugf("Index hierarchy of: %s", classInfo); - // Interfaces - for (DotName interfaceName : classInfo.interfaceNames()) { - indexHierarchy(index.getClassByName(interfaceName)); - } - // Superclass - DotName superName = classInfo.superName(); - if (superName != null && !superName.equals(DotNames.OBJECT)) { - indexHierarchy(index.getClassByName(superName)); + return superTypesCache.computeIfAbsent(className2, this::findSuperTypes).contains(className1); + } + + private Set findSuperTypes(DotName name) { + LOG.debugf("Find supertypes/index hierarchy of: %s", name); + Set result = new HashSet<>(); + Deque queue = new ArrayDeque<>(); + queue.add(name); + while (!queue.isEmpty()) { + DotName type = queue.poll(); + if (result.add(type)) { + ClassInfo clazz = computingIndex.getClassByName(type); + if (clazz == null) { + continue; + } + if (clazz.superName() != null) { + queue.add(clazz.superName()); + } + queue.addAll(clazz.interfaceNames()); } } + return result; } } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypesTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypesTest.java index d6335613108d21..764a7635b1b06c 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypesTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypesTest.java @@ -7,8 +7,6 @@ import java.io.InputStream; import java.io.Serializable; import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; import org.jboss.jandex.ArrayType; import org.jboss.jandex.ClassType; @@ -20,7 +18,7 @@ import org.jboss.jandex.Type; import org.junit.jupiter.api.Test; -import io.quarkus.qute.deployment.Types.AssignableInfo; +import io.quarkus.qute.deployment.Types.AssignabilityCheck; public class TypesTest { @@ -37,28 +35,28 @@ public void testIsAssignableFrom() throws IOException { Type booleanType = Types.box(Primitive.BOOLEAN); ArrayType byteArrayType = ArrayType.create(PrimitiveType.BYTE, 1); ArrayType intArrayType = ArrayType.create(PrimitiveType.INT, 1); - Map cache = new HashMap<>(); + AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index); // byte[] is not assignable from String - assertFalse(Types.isAssignableFrom(byteArrayType, stringType, index, cache)); + assertFalse(assignabilityCheck.isAssignableFrom(byteArrayType, stringType)); // CharSequence is assignable from String - assertTrue(Types.isAssignableFrom(charSequenceType, stringType, index, cache)); + assertTrue(assignabilityCheck.isAssignableFrom(charSequenceType, stringType)); // String is not assignable from CharSequence - assertFalse(Types.isAssignableFrom(stringType, charSequenceType, index, cache)); + assertFalse(assignabilityCheck.isAssignableFrom(stringType, charSequenceType)); // String is not assignable from byte[] - assertFalse(Types.isAssignableFrom(stringType, byteArrayType, index, cache)); + assertFalse(assignabilityCheck.isAssignableFrom(stringType, byteArrayType)); // Object is assignable from any type - assertTrue(Types.isAssignableFrom(ClassType.OBJECT_TYPE, stringType, index, cache)); + assertTrue(assignabilityCheck.isAssignableFrom(ClassType.OBJECT_TYPE, stringType)); // boolean is assignable from Boolean - assertTrue(Types.isAssignableFrom(PrimitiveType.BOOLEAN, booleanType, index, cache)); + assertTrue(assignabilityCheck.isAssignableFrom(PrimitiveType.BOOLEAN, booleanType)); // boolean is not assignable from double - assertFalse(Types.isAssignableFrom(PrimitiveType.BOOLEAN, PrimitiveType.DOUBLE, index, cache)); + assertFalse(assignabilityCheck.isAssignableFrom(PrimitiveType.BOOLEAN, PrimitiveType.DOUBLE)); // Serializable is assignable from BigDecimal - assertTrue(Types.isAssignableFrom(serializableType, bigDecimalType, index, cache)); + assertTrue(assignabilityCheck.isAssignableFrom(serializableType, bigDecimalType)); // Number is assignable from BigDecimal - assertTrue(Types.isAssignableFrom(numberType, bigDecimalType, index, cache)); + assertTrue(assignabilityCheck.isAssignableFrom(numberType, bigDecimalType)); // byte[] is not assignable from int[] - assertFalse(Types.isAssignableFrom(byteArrayType, intArrayType, index, cache)); + assertFalse(assignabilityCheck.isAssignableFrom(byteArrayType, intArrayType)); } private static Index index(Class... classes) throws IOException { From 86e2a82e1c2bec4321637d66f9c43c1705aa5424 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Sat, 15 Apr 2023 02:30:37 +0900 Subject: [PATCH 020/333] Correct a minor error in native-reference.adoc --- docs/src/main/asciidoc/native-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/native-reference.adoc b/docs/src/main/asciidoc/native-reference.adoc index ebb42fa736d1f9..ab2cadd835fbad 100644 --- a/docs/src/main/asciidoc/native-reference.adoc +++ b/docs/src/main/asciidoc/native-reference.adoc @@ -58,7 +58,7 @@ This means that GraalVM’s adaptive GC policy tries to aggressively trigger GCs Up to version 2.13, Quarkus used the “space/time” GC collection policy by default, but starting with version 2.14, it switched to using the “adaptive” policy instead. The reason why Quarkus initially chose to use "space/time" is because at that time it had considerable performance improvements over "adaptive". -Recent performance experiments, however, indicate that the "space/time" policy can result in worse out-of-the-box experience compared to the "space/time" policy, +Recent performance experiments, however, indicate that the "space/time" policy can result in worse out-of-the-box experience compared to the "adaptive" policy, while at the same time the benefits it used to offer have diminished considerably after improvements made to the "adaptive" policy. As a result, the "adaptive" policy appears to be the best option for most, if not all, Quarkus applications. Full details on this switch can be read in link:https://github.com/quarkusio/quarkus/issues/28267[this issue]. From 9f8fb8b97994111cf2f4f7e408bdabf235be60b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 22:10:56 +0000 Subject: [PATCH 021/333] Bump bouncycastle.version from 1.72 to 1.73 Bumps `bouncycastle.version` from 1.72 to 1.73. Updates `bcprov-jdk18on` from 1.72 to 1.73 - [Release notes](https://github.com/bcgit/bc-java/releases) - [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `bctls-jdk18on` from 1.72 to 1.73 - [Release notes](https://github.com/bcgit/bc-java/releases) - [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `bcpkix-jdk18on` from 1.72 to 1.73 - [Release notes](https://github.com/bcgit/bc-java/releases) - [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `bcutil-jdk18on` from 1.72 to 1.73 - [Release notes](https://github.com/bcgit/bc-java/releases) - [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-type: direct:development update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bctls-jdk18on dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcpkix-jdk18on dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcutil-jdk18on dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 4a9dc0075cb59b..7378caf16ea797 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -15,7 +15,7 @@ 2.0.0 - 1.72 + 1.73 1.0.2.3 1.0.14.1 5.0.0 From ad78f04877075dc1ed1c6cb7653b66c3f9fab472 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 12 Apr 2023 20:00:43 +0100 Subject: [PATCH 022/333] Validator for Config only --- .../runtime/configuration/ConfigUtils.java | 2 - .../HibernateValidatorProcessor.java | 228 ++++++++++++++---- ...ConfigMappingInjectionInValidatorTest.java | 75 ++++++ .../test/config/ConfigMappingInvalidTest.java | 44 +++- ...ibernateBeanValidationConfigValidator.java | 45 +++- .../runtime/HibernateValidatorRecorder.java | 13 + .../io.smallrye.config.ConfigValidator | 1 - .../ConfigMappingStartupValidatorTest.java | 21 +- 8 files changed, 373 insertions(+), 56 deletions(-) create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInjectionInValidatorTest.java delete mode 100644 extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index e9fe18f97ed53a..7169fceea750ec 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -94,8 +94,6 @@ public static SmallRyeConfigBuilder configBuilder(final boolean runTime, final b builder.withSources(new RuntimeOverrideConfigSource(Thread.currentThread().getContextClassLoader())); } if (runTime || bootstrap) { - // Validator only for runtime. We cannot use the current validator for build time (chicken / egg problem) - builder.addDiscoveredValidator(); builder.withDefaultValue(UUID_KEY, UUID.randomUUID().toString()); } if (addDiscovered) { diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java index fa8efcb7360ae4..b8eea6efa36c6b 100644 --- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java +++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java @@ -63,6 +63,7 @@ import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -71,9 +72,12 @@ import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.builditem.StaticInitConfigBuilderBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem; @@ -81,9 +85,15 @@ import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; import io.quarkus.deployment.recording.RecorderContext; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.Gizmo; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import io.quarkus.hibernate.validator.ValidatorFactoryCustomizer; import io.quarkus.hibernate.validator.runtime.DisableLoggingFeature; +import io.quarkus.hibernate.validator.runtime.HibernateBeanValidationConfigValidator; import io.quarkus.hibernate.validator.runtime.HibernateValidatorBuildTimeConfig; import io.quarkus.hibernate.validator.runtime.HibernateValidatorRecorder; import io.quarkus.hibernate.validator.runtime.ValidationSupport; @@ -98,6 +108,12 @@ import io.quarkus.resteasy.common.spi.ResteasyDotNames; import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem; import io.quarkus.runtime.LocalesBuildTimeConfig; +import io.quarkus.runtime.configuration.ConfigBuilder; +import io.smallrye.config.ConfigMappingLoader; +import io.smallrye.config.ConfigMappingMetadata; +import io.smallrye.config.ConfigValidator; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.validator.BeanValidationConfigValidator; class HibernateValidatorProcessor { @@ -146,6 +162,167 @@ NativeImageFeatureBuildItem nativeImageFeature() { return new NativeImageFeatureBuildItem(DisableLoggingFeature.class); } + @BuildStep + void beanValidationAnnotations( + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, + CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer beanValidationAnnotations) { + + IndexView indexView = CompositeIndex.create(beanArchiveIndexBuildItem.getIndex(), combinedIndexBuildItem.getIndex()); + + Set constraints = new HashSet<>(); + Set builtinConstraints = ConstraintHelper.getBuiltinConstraints(); + + // Collect the constraint annotations provided by Hibernate Validator and Bean Validation + contributeBuiltinConstraints(builtinConstraints, constraints); + + // Add the constraint annotations present in the application itself + for (AnnotationInstance constraint : indexView.getAnnotations(DotName.createSimple(Constraint.class.getName()))) { + constraints.add(constraint.target().asClass().name()); + + if (constraint.target().asClass().annotationsMap().containsKey(REPEATABLE)) { + for (AnnotationInstance repeatableConstraint : constraint.target().asClass().annotationsMap() + .get(REPEATABLE)) { + constraints.add(repeatableConstraint.value().asClass().name()); + } + } + } + + Set allConsideredAnnotations = new HashSet<>(); + allConsideredAnnotations.addAll(constraints); + + // Also consider elements that are marked with @Valid + allConsideredAnnotations.add(VALID); + + // Also consider elements that are marked with @ValidateOnExecution + allConsideredAnnotations.add(VALIDATE_ON_EXECUTION); + + beanValidationAnnotations.produce(new BeanValidationAnnotationsBuildItem( + VALID, + constraints, + allConsideredAnnotations)); + } + + @BuildStep + void configValidator( + CombinedIndexBuildItem combinedIndex, + List configClasses, + BeanValidationAnnotationsBuildItem beanValidationAnnotations, + BuildProducer generatedClass, + BuildProducer reflectiveClass, + BuildProducer staticInitConfigBuilder, + BuildProducer runTimeConfigBuilder) { + + Set configMappings = new HashSet<>(); + Set configClassesToValidate = new HashSet<>(); + for (ConfigClassBuildItem configClass : configClasses) { + for (String generatedConfigClass : configClass.getGeneratedClasses()) { + DotName simple = DotName.createSimple(generatedConfigClass); + configClassesToValidate.add(simple); + } + + for (ConfigMappingMetadata mappingsMetadata : ConfigMappingLoader + .getConfigMappingsMetadata(configClass.getConfigClass())) { + configMappings.add(DotName.createSimple(mappingsMetadata.getInterfaceType())); + } + } + + Set configMappingsConstraints = new HashSet<>(); + for (DotName consideredAnnotation : beanValidationAnnotations.getAllAnnotations()) { + Collection annotationInstances = combinedIndex.getIndex().getAnnotations(consideredAnnotation); + + if (annotationInstances.isEmpty()) { + continue; + } + + for (AnnotationInstance annotation : annotationInstances) { + String builtinConstraintCandidate = BUILT_IN_CONSTRAINT_REPEATABLE_CONTAINER_PATTERN + .matcher(consideredAnnotation.toString()).replaceAll(""); + + if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) { + MethodInfo methodInfo = annotation.target().asMethod(); + ClassInfo declaringClass = methodInfo.declaringClass(); + if (configMappings.contains(declaringClass.name())) { + configMappingsConstraints.add(builtinConstraintCandidate); + } + } else if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { + ClassInfo classInfo = annotation.target().asClass(); + if (configMappings.contains(classInfo.name())) { + configMappingsConstraints.add(builtinConstraintCandidate); + } + } + } + } + + if (configMappingsConstraints.isEmpty()) { + return; + } + + String builderClassName = HibernateBeanValidationConfigValidator.class.getName() + "Builder"; + try (ClassCreator classCreator = ClassCreator.builder() + .classOutput(new GeneratedClassGizmoAdaptor(generatedClass, true)) + .className(builderClassName) + .interfaces(ConfigBuilder.class) + .setFinal(true) + .build()) { + + // Static Init Validator + MethodCreator clinit = classCreator + .getMethodCreator(MethodDescriptor.ofMethod(builderClassName, "", void.class)); + clinit.setModifiers(Opcodes.ACC_STATIC); + + ResultHandle constraints = clinit.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (String configMappingsConstraint : configMappingsConstraints) { + clinit.invokeVirtualMethod(MethodDescriptor.ofMethod(HashSet.class, "add", boolean.class, Object.class), + constraints, clinit.load(configMappingsConstraint)); + } + + ResultHandle classes = clinit.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (DotName configClassToValidate : configClassesToValidate) { + clinit.invokeVirtualMethod(MethodDescriptor.ofMethod(HashSet.class, "add", boolean.class, Object.class), + classes, clinit.loadClass(configClassToValidate.toString())); + } + + ResultHandle configValidator = clinit.newInstance( + MethodDescriptor.ofConstructor(HibernateBeanValidationConfigValidator.class, Set.class, Set.class), + constraints, classes); + + FieldDescriptor configValidatorField = FieldDescriptor.of(builderClassName, "configValidator", + BeanValidationConfigValidator.class); + classCreator.getFieldCreator(configValidatorField) + .setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE); + clinit.writeStaticField(configValidatorField, configValidator); + + clinit.returnNull(); + clinit.close(); + + MethodCreator configBuilderMethod = classCreator.getMethodCreator( + MethodDescriptor.ofMethod( + ConfigBuilder.class, "configBuilder", + SmallRyeConfigBuilder.class, SmallRyeConfigBuilder.class)); + ResultHandle configBuilder = configBuilderMethod.getMethodParam(0); + + // Add Validator to the builder + configBuilderMethod.invokeVirtualMethod( + MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, "withValidator", SmallRyeConfigBuilder.class, + ConfigValidator.class), + configBuilder, configBuilderMethod.readStaticField(configValidatorField)); + + configBuilderMethod.returnValue(configBuilder); + } + + reflectiveClass.produce(ReflectiveClassBuildItem.builder(builderClassName).build()); + staticInitConfigBuilder.produce(new StaticInitConfigBuilderBuildItem(builderClassName)); + runTimeConfigBuilder.produce(new RunTimeConfigBuilderBuildItem(builderClassName)); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void shutdownConfigValidator(HibernateValidatorRecorder hibernateValidatorRecorder, + ShutdownContextBuildItem shutdownContext) { + hibernateValidatorRecorder.shutdownConfigValidator(shutdownContext); + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) void registerAdditionalBeans(HibernateValidatorRecorder hibernateValidatorRecorder, @@ -207,7 +384,9 @@ public boolean test(BeanInfo beanInfo) { @BuildStep @Record(STATIC_INIT) - public void build(HibernateValidatorRecorder recorder, RecorderContext recorderContext, + public void build( + HibernateValidatorRecorder recorder, RecorderContext recorderContext, + BeanValidationAnnotationsBuildItem beanValidationAnnotations, BuildProducer reflectiveFields, BuildProducer reflectiveMethods, BuildProducer annotationsTransformers, @@ -215,7 +394,6 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer feature, BuildProducer beanContainerListener, - BuildProducer beanValidationAnnotations, BuildProducer unremovableBeans, ShutdownContextBuildItem shutdownContext, List configClasses, @@ -229,44 +407,11 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC // we use both indexes to support both generated beans and jars that contain no CDI beans but only Validation annotations IndexView indexView = CompositeIndex.create(beanArchiveIndexBuildItem.getIndex(), combinedIndexBuildItem.getIndex()); - Set constraints = new HashSet<>(); - - Set builtinConstraints = ConstraintHelper.getBuiltinConstraints(); - - // Collect the constraint annotations provided by Hibernate Validator and Bean Validation - contributeBuiltinConstraints(builtinConstraints, constraints); - - // Add the constraint annotations present in the application itself - for (AnnotationInstance constraint : indexView.getAnnotations(DotName.createSimple(Constraint.class.getName()))) { - constraints.add(constraint.target().asClass().name()); - - if (constraint.target().asClass().annotationsMap().containsKey(REPEATABLE)) { - for (AnnotationInstance repeatableConstraint : constraint.target().asClass().annotationsMap() - .get(REPEATABLE)) { - constraints.add(repeatableConstraint.value().asClass().name()); - } - } - } - - Set allConsideredAnnotations = new HashSet<>(); - allConsideredAnnotations.addAll(constraints); - - // Also consider elements that are marked with @Valid - allConsideredAnnotations.add(VALID); - - // Also consider elements that are marked with @ValidateOnExecution - allConsideredAnnotations.add(VALIDATE_ON_EXECUTION); - - beanValidationAnnotations.produce(new BeanValidationAnnotationsBuildItem( - VALID, - constraints, - allConsideredAnnotations)); - Set classNamesToBeValidated = new HashSet<>(); Map> methodsWithInheritedValidation = new HashMap<>(); Set detectedBuiltinConstraints = new HashSet<>(); - for (DotName consideredAnnotation : allConsideredAnnotations) { + for (DotName consideredAnnotation : beanValidationAnnotations.getAllAnnotations()) { Collection annotationInstances = indexView.getAnnotations(consideredAnnotation); if (annotationInstances.isEmpty()) { @@ -276,7 +421,8 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC // we trim the repeatable container suffix if needed String builtinConstraintCandidate = BUILT_IN_CONSTRAINT_REPEATABLE_CONTAINER_PATTERN .matcher(consideredAnnotation.toString()).replaceAll(""); - if (builtinConstraints.contains(builtinConstraintCandidate)) { + if (beanValidationAnnotations.getConstraintAnnotations() + .contains(DotName.createSimple(builtinConstraintCandidate))) { detectedBuiltinConstraints.add(builtinConstraintCandidate); } @@ -333,12 +479,6 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC } } - for (ConfigClassBuildItem configClass : configClasses) { - for (String generatedClass : configClass.getGeneratedClasses()) { - classNamesToBeValidated.add(DotName.createSimple(generatedClass)); - } - } - // JAX-RS methods are handled differently by the transformer so those need to be gathered here. // Note: The focus only on methods is basically an incomplete solution, since there could also be // class-level JAX-RS annotations but currently the transformer only looks at methods. @@ -351,7 +491,7 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC annotationsTransformers .produce(new AnnotationsTransformerBuildItem( - new MethodValidatedAnnotationsTransformer(allConsideredAnnotations, + new MethodValidatedAnnotationsTransformer(beanValidationAnnotations.getAllAnnotations(), jaxRsMethods, methodsWithInheritedValidation))); diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInjectionInValidatorTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInjectionInValidatorTest.java new file mode 100644 index 00000000000000..ea6f634b02f4a5 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInjectionInValidatorTest.java @@ -0,0 +1,75 @@ +package io.quarkus.hibernate.validator.test.config; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Inject; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.Payload; +import jakarta.validation.Validator; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.annotations.StaticInitSafe; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +class ConfigMappingInjectionInValidatorTest { + @RegisterExtension + private static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Inject + Validator validator; + + @Test + void valid() { + assertTrue(validator.validate(new Entity()).isEmpty()); + } + + @StaticInitSafe + @ConfigMapping(prefix = "valid.config") + public interface ValidConfig { + @WithDefault("true") + boolean isValid(); + } + + @Target({ TYPE, ANNOTATION_TYPE }) + @Retention(RUNTIME) + @Constraint(validatedBy = { ValidEntityValidator.class }) + @Documented + public @interface ValidEntity { + String message() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; + } + + public static class ValidEntityValidator implements ConstraintValidator { + @Inject + ValidConfig validConfig; + + @Override + public boolean isValid(Entity value, ConstraintValidatorContext context) { + return validConfig.isValid(); + } + } + + @ValidEntity + public static class Entity { + + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInvalidTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInvalidTest.java index f2c3680b8cf72f..1a7839f09ef125 100644 --- a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInvalidTest.java +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingInvalidTest.java @@ -4,11 +4,13 @@ import jakarta.inject.Inject; import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; -import org.eclipse.microprofile.config.Config; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,22 +24,54 @@ public class ConfigMappingInvalidTest { @RegisterExtension static final QuarkusUnitTest UNIT_TEST = new QuarkusUnitTest().setArchiveProducer( () -> ShrinkWrap.create(JavaArchive.class) - .addAsResource(new StringAsset("validator.server.host=localhost\n"), "application.properties")); + .addAsResource(new StringAsset("validator.server.host=localhost\n" + + "validator.hierarchy.number=1\n" + + "validator.repeatable.name=a"), "application.properties")); @Inject - Config config; + SmallRyeConfig config; @Test void invalid() { - assertThrows(ConfigValidationException.class, - () -> config.unwrap(SmallRyeConfig.class).getConfigMapping(Server.class), + assertThrows(ConfigValidationException.class, () -> config.getConfigMapping(Server.class), "validator.server.host must be less than or equal to 3"); } + @Test + @Disabled("Requires https://github.com/smallrye/smallrye-config/pull/923") + void invalidHierarchy() { + assertThrows(ConfigValidationException.class, () -> config.getConfigMapping(Child.class), + "validator.hierarchy.number must be greater than or equal to 10"); + } + + @Test + void repeatable() { + assertThrows(ConfigValidationException.class, () -> config.getConfigMapping(Repeatable.class)); + } + @Unremovable @ConfigMapping(prefix = "validator.server") public interface Server { @Max(3) String host(); } + + public interface Parent { + @Min(10) + Integer number(); + } + + @Unremovable + @ConfigMapping(prefix = "validator.hierarchy") + public interface Child extends Parent { + + } + + @Unremovable + @ConfigMapping(prefix = "validator.repeatable") + public interface Repeatable { + @Size(max = 10) + @Size(min = 2) + String name(); + } } diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java index 774e7a5e3c7e90..ff08220c4865c3 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java @@ -1,12 +1,55 @@ package io.quarkus.hibernate.validator.runtime; +import java.util.Set; + +import jakarta.validation.Validation; import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.hibernate.validator.PredefinedScopeHibernateValidatorConfiguration; +import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl; import io.smallrye.config.validator.BeanValidationConfigValidator; public class HibernateBeanValidationConfigValidator implements BeanValidationConfigValidator { + + public HibernateBeanValidationConfigValidator(Set constraints, Set> classesToBeValidated) { + PredefinedScopeHibernateValidatorConfiguration configuration = Validation + .byProvider(PredefinedScopeHibernateValidator.class) + .configure(); + + // TODO - There is no way to retrieve locales from configuration here (even manually). We need to add a way to configure the validator from SmallRye Config. + configuration + .ignoreXmlConfiguration() + .builtinConstraints(constraints) + .initializeBeanMetaData(classesToBeValidated) + .constraintValidatorFactory(new ConstraintValidatorFactoryImpl()); + + ConfigValidatorHolder.initialize(configuration.buildValidatorFactory()); + } + @Override public Validator getValidator() { - return ValidatorHolder.getValidator(); + return ConfigValidatorHolder.getValidator(); + } + + // Store in a holder, so we can easily reference it and shutdown the validator + public static class ConfigValidatorHolder { + private static ValidatorFactory validatorFactory; + private static Validator validator; + + static void initialize(ValidatorFactory validatorFactory) { + ConfigValidatorHolder.validatorFactory = validatorFactory; + ConfigValidatorHolder.validator = validatorFactory.getValidator(); + } + + static ValidatorFactory getValidatorFactory() { + return validatorFactory; + } + + static Validator getValidator() { + return validator; + } } } diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java index fd24d663fda7eb..00564dc90488dd 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java @@ -37,6 +37,19 @@ @Recorder public class HibernateValidatorRecorder { + public void shutdownConfigValidator(ShutdownContext shutdownContext) { + shutdownContext.addShutdownTask(new Runnable() { + @Override + public void run() { + ValidatorFactory validatorFactory = HibernateBeanValidationConfigValidator.ConfigValidatorHolder + .getValidatorFactory(); + if (validatorFactory != null) { + validatorFactory.close(); + } + } + }); + } + public BeanContainerListener initializeValidatorFactory(Set> classesToBeValidated, Set detectedBuiltinConstraints, Set> valueExtractorClasses, boolean hasXmlConfiguration, boolean jpaInClasspath, diff --git a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator b/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator deleted file mode 100644 index 5c35a7ee00e447..00000000000000 --- a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator +++ /dev/null @@ -1 +0,0 @@ -io.quarkus.hibernate.validator.runtime.HibernateBeanValidationConfigValidator diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/ConfigMappingStartupValidatorTest.java b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/ConfigMappingStartupValidatorTest.java index 94e82cd2f1e8cf..3abcdfc7058e22 100644 --- a/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/ConfigMappingStartupValidatorTest.java +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/ConfigMappingStartupValidatorTest.java @@ -4,7 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import jakarta.inject.Inject; import jakarta.validation.constraints.Pattern; @@ -22,6 +24,8 @@ import io.quarkus.test.junit.QuarkusTestProfile; import io.quarkus.test.junit.TestProfile; import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappingLoader; +import io.smallrye.config.ConfigMappingMetadata; import io.smallrye.config.ConfigValidationException; import io.smallrye.config.ConfigValidationException.Problem; import io.smallrye.config.ConfigValidator; @@ -34,8 +38,8 @@ public class ConfigMappingStartupValidatorTest { @ConfigMapping(prefix = "config") - public static interface ConfigWithValidation { - public static final String CONFIG_WITH_VALIDATION_VALUE_MUST_BE_D_3 = "ConfigWithValidation.value() must be \"-\\d{3}\""; + public interface ConfigWithValidation { + String CONFIG_WITH_VALIDATION_VALUE_MUST_BE_D_3 = "ConfigWithValidation.value() must be \"-\\d{3}\""; @WithDefault("invalid-value") @Pattern(regexp = "-\\d{3}", message = CONFIG_WITH_VALIDATION_VALUE_MUST_BE_D_3) @@ -57,10 +61,21 @@ public boolean disableGlobalTestResources() { @BeforeAll public static void doBefore() { + Set constraints = Set.of(Pattern.class.getName()); + Set> classesToBeValidated = new HashSet<>(); + for (ConfigMappingMetadata configMappingMetadata : ConfigMappingLoader + .getConfigMappingsMetadata(ConfigWithValidation.class)) { + try { + classesToBeValidated.add(Class.forName(configMappingMetadata.getClassName())); + } catch (ClassNotFoundException e) { + // Ignore + } + } + SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); builder.withMapping(ConfigWithValidation.class).setAddDefaultSources(true) .withValidator(new ConfigValidator() { - final ConfigValidator base = new HibernateBeanValidationConfigValidator(); + final ConfigValidator base = new HibernateBeanValidationConfigValidator(constraints, classesToBeValidated); @Override public void validateMapping(Class mappingClass, String prefix, Object mappingObject) From 8ffa353b4bb9ea27003c502073e7a2d9c15d5868 Mon Sep 17 00:00:00 2001 From: Fouad Almalki Date: Sat, 15 Apr 2023 15:00:06 +0300 Subject: [PATCH 023/333] add environmentVariables to gradle task quarkusDev --- .../io/quarkus/gradle/tasks/QuarkusDev.java | 15 ++++++++++ docs/src/main/asciidoc/gradle-tooling.adoc | 25 ++++++++++++++++ .../build.gradle | 29 +++++++++++++++++++ .../gradle.properties | 2 ++ .../settings.gradle | 19 ++++++++++++ .../acme/quarkus/sample/HelloResource.java | 18 ++++++++++++ .../AddEnvironmentVariablesDevModeTest.java | 20 +++++++++++++ 7 files changed, 128 insertions(+) create mode 100644 integration-tests/gradle/src/main/resources/add-envitonment-variables-app/build.gradle create mode 100644 integration-tests/gradle/src/main/resources/add-envitonment-variables-app/gradle.properties create mode 100644 integration-tests/gradle/src/main/resources/add-envitonment-variables-app/settings.gradle create mode 100644 integration-tests/gradle/src/main/resources/add-envitonment-variables-app/src/main/java/org/acme/quarkus/sample/HelloResource.java create mode 100644 integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/AddEnvironmentVariablesDevModeTest.java 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 dc4bf01c0e6696..afcdc38d5960e1 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 @@ -32,6 +32,7 @@ import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.CompileClasspath; @@ -82,6 +83,7 @@ public abstract class QuarkusDev extends QuarkusTask { private final CompilerOptions compilerOptions = new CompilerOptions(); private final Property workingDirectory; + private final MapProperty environmentVariables; private final Property preventNoVerify; private final Property shouldPropagateJavaCompilerArgs; @@ -114,6 +116,8 @@ public QuarkusDev( workingDirectory = objectFactory.property(File.class); workingDirectory.convention(getProject().provider(() -> QuarkusPluginExtension.getLastFile(getCompilationOutput()))); + environmentVariables = objectFactory.mapProperty(String.class, String.class); + preventNoVerify = objectFactory.property(Boolean.class); preventNoVerify.convention(false); @@ -177,6 +181,16 @@ public void setWorkingDir(String workingDir) { workingDirectory.set(getProject().file(workingDir)); } + @Input + public MapProperty getEnvironmentVariables() { + return environmentVariables; + } + + @Internal + public Map getEnvVars() { + return environmentVariables.get(); + } + @Input public Property getPreventNoVerify() { return preventNoVerify; @@ -307,6 +321,7 @@ public void startDev() { if (outputFile == null) { getProject().exec(action -> { action.commandLine(runner.args()).workingDir(getWorkingDirectory().get()); + action.environment(getEnvVars()); action.setStandardInput(System.in) .setErrorOutput(System.out) .setStandardOutput(System.out); diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index e0308c69dc3d3d..097d49343181cc 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -208,6 +208,31 @@ By default, `quarkusDev` sets the debug host to `localhost` (for security reason include::{includes}/devtools/dev-parameters.adoc[] :!dev-additional-parameters: ==== + +You also can add environment variables to the development environment: + +[role="primary asciidoc-tabs-sync-groovy"] +.Groovy DSL +**** +[source,groovy] +---- +quarkusDev { + environmentVariables = [FOO_VALUE: 'abc', BAR_VALUE: 'def'] +} +---- +**** + +[role="secondary asciidoc-tabs-sync-kotlin"] +.Kotlin DSL +**** +[source,kotlin] +---- +tasks.quarkusDev { + environmentVariables.set(mapOf("FOO_VALUE" to "abc", "BAR_VALUE" to "def")) +} +---- +**** + The plugin also exposes a `quarkusDev` configuration. Using this configuration to declare a dependency will restrict the usage of that dependency to development mode. The `quarkusDev` configuration can be used as following: diff --git a/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/build.gradle b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/build.gradle new file mode 100644 index 00000000000000..dd581f4bd0eab9 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' + id 'io.quarkus' +} + +group = 'com.quarkus.demo' +version = '1.0' + +repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + } + } + mavenCentral() +} + +quarkusDev { + environmentVariables = [FOO_VALUE: 'abc', BAR_VALUE: 'def'] +} + +dependencies { + implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + implementation 'io.quarkus:quarkus-resteasy' +} + +test { + systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/gradle.properties b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/gradle.properties new file mode 100644 index 00000000000000..8f063b7d88ba4a --- /dev/null +++ b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/gradle.properties @@ -0,0 +1,2 @@ +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformGroupId=io.quarkus \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/settings.gradle b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/settings.gradle new file mode 100644 index 00000000000000..d50360e06f19d8 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + } + } + mavenCentral() + gradlePluginPortal() + } + //noinspection GroovyAssignabilityCheck + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } +} + +rootProject.name = 'add-envitonment-variables-app' + + diff --git a/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/src/main/java/org/acme/quarkus/sample/HelloResource.java b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/src/main/java/org/acme/quarkus/sample/HelloResource.java new file mode 100644 index 00000000000000..7a3366673e03e4 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/add-envitonment-variables-app/src/main/java/org/acme/quarkus/sample/HelloResource.java @@ -0,0 +1,18 @@ +package org.acme.quarkus.sample; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +public class HelloResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + String foo = System.getenv("FOO_VALUE"); // abc + String bar = System.getenv("BAR_VALUE"); // def + return foo + bar; // abcdef + } +} diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/AddEnvironmentVariablesDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/AddEnvironmentVariablesDevModeTest.java new file mode 100644 index 00000000000000..5a034aca444977 --- /dev/null +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/AddEnvironmentVariablesDevModeTest.java @@ -0,0 +1,20 @@ +package io.quarkus.gradle.devmode; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AddEnvironmentVariablesDevModeTest extends QuarkusDevGradleTestBase { + + @Override + protected String projectDirectoryName() { + return "add-envitonment-variables-app"; + } + + @Override + protected String[] buildArguments() { + return new String[] { "clean", "quarkusDev" }; + } + + protected void testDevMode() throws Exception { + assertThat(getHttpResponse("/hello")).contains("abcdef"); + } +} \ No newline at end of file From 4e9eb210806f1dbd82bb6d0cdc13db3fe8317265 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Sun, 16 Apr 2023 05:26:46 +0300 Subject: [PATCH 024/333] Update Gradle plugins build scripts Changes: * migration to Gradle Kotlin DSL (much more streamlined IDE support) * use convention plugins in `build-logic/src/main/kotlin` to simplify actual `build.gradle.kts` build scripts * use version catalog for deps (see https://docs.gradle.org/current/userguide/platforms.html#header) * use java toolchain to build plugins (see https://docs.gradle.org/current/userguide/toolchains.html) * use `JavaPluginExtension#withSourcesJar`/`withJavadocJar` instead of older packaging them manually Signed-off-by: Konstantin Gribov --- devtools/gradle/build-logic/build.gradle.kts | 10 ++++ .../gradle/build-logic/settings.gradle.kts | 13 +++++ ....quarkus.devtools.gradle-plugin.gradle.kts | 16 +++++ ...o.quarkus.devtools.java-library.gradle.kts | 50 ++++++++++++++++ .../build-logic/src/main/kotlin/utils.kt | 4 ++ devtools/gradle/build.gradle | 58 ------------------- devtools/gradle/build.gradle.kts | 5 ++ .../gradle-application-plugin/build.gradle | 37 ------------ .../build.gradle.kts | 26 +++++++++ .../gradle-extension-plugin/build.gradle | 35 ----------- .../gradle-extension-plugin/build.gradle.kts | 24 ++++++++ devtools/gradle/gradle-model/build.gradle | 32 ---------- devtools/gradle/gradle-model/build.gradle.kts | 21 +++++++ devtools/gradle/gradle.properties | 2 - devtools/gradle/gradle/libs.versions.toml | 29 ++++++++++ devtools/gradle/settings.gradle | 16 ----- devtools/gradle/settings.gradle.kts | 25 ++++++++ 17 files changed, 223 insertions(+), 180 deletions(-) create mode 100644 devtools/gradle/build-logic/build.gradle.kts create mode 100644 devtools/gradle/build-logic/settings.gradle.kts create mode 100644 devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.gradle-plugin.gradle.kts create mode 100644 devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts create mode 100644 devtools/gradle/build-logic/src/main/kotlin/utils.kt delete mode 100644 devtools/gradle/build.gradle create mode 100644 devtools/gradle/build.gradle.kts delete mode 100644 devtools/gradle/gradle-application-plugin/build.gradle create mode 100644 devtools/gradle/gradle-application-plugin/build.gradle.kts delete mode 100644 devtools/gradle/gradle-extension-plugin/build.gradle create mode 100644 devtools/gradle/gradle-extension-plugin/build.gradle.kts delete mode 100644 devtools/gradle/gradle-model/build.gradle create mode 100644 devtools/gradle/gradle-model/build.gradle.kts create mode 100644 devtools/gradle/gradle/libs.versions.toml delete mode 100644 devtools/gradle/settings.gradle create mode 100644 devtools/gradle/settings.gradle.kts diff --git a/devtools/gradle/build-logic/build.gradle.kts b/devtools/gradle/build-logic/build.gradle.kts new file mode 100644 index 00000000000000..4dc03934dc0838 --- /dev/null +++ b/devtools/gradle/build-logic/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + `kotlin-dsl` +} + +dependencies { + implementation(plugin("com.gradle.plugin-publish", "1.2.0")) +} + +fun DependencyHandler.plugin(id: String, version: String) = + create("$id:$id.gradle.plugin:$version") diff --git a/devtools/gradle/build-logic/settings.gradle.kts b/devtools/gradle/build-logic/settings.gradle.kts new file mode 100644 index 00000000000000..db8235fe0a54b4 --- /dev/null +++ b/devtools/gradle/build-logic/settings.gradle.kts @@ -0,0 +1,13 @@ +@Suppress("UnstableApiUsage") +dependencyResolutionManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } + + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.gradle-plugin.gradle.kts b/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.gradle-plugin.gradle.kts new file mode 100644 index 00000000000000..e317c71f05da20 --- /dev/null +++ b/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.gradle-plugin.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("io.quarkus.devtools.java-library") + id("com.gradle.plugin-publish") + id("maven-publish") +} + +dependencies { + val libs = project.the().named("libs") + implementation(project(":gradle-model")) + implementation(libs.getLibrary("quarkus-devtools-common")) +} + +gradlePlugin { + website.set("https://quarkus.io/") + vcsUrl.set("https://github.com/quarkusio/quarkus") +} diff --git a/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts b/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts new file mode 100644 index 00000000000000..d2dc255e9ee13c --- /dev/null +++ b/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts @@ -0,0 +1,50 @@ +plugins { + id("java-library") + id("maven-publish") +} + +dependencies { + api(gradleApi()) + + val libs = project.the().named("libs") + implementation(platform("io.quarkus:quarkus-bom:$version")) + + implementation(libs.getLibrary("quarkus-bootstrap-core")) + implementation(libs.getLibrary("quarkus-bootstrap-gradle-resolver")) + implementation(libs.getLibrary("quarkus-core-deployment")) + + testImplementation(platform("io.quarkus:quarkus-bom:$version")) + testImplementation(platform(libs.getLibrary("junit-bom"))) + testImplementation(gradleTestKit()) + testImplementation(libs.getLibrary("junit-api")) + testImplementation(libs.getLibrary("assertj")) + + testImplementation(libs.getLibrary("quarkus-devtools-testing")) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +tasks.withType().configureEach { + options.encoding = "UTF-8" +} + +tasks.named(JavaPlugin.JAVADOC_TASK_NAME) { + options.encoding = "UTF-8" + (options as? CoreJavadocOptions)?.addStringOption("Xdoclint:-reference", "-quiet") +} + +tasks.named(JavaPlugin.TEST_TASK_NAME) { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } + + // propagate the custom local maven repo, in case it's configured + if (System.getProperties().containsKey("maven.repo.local")) { + systemProperty("maven.repo.local", System.getProperty("maven.repo.local")) + } +} diff --git a/devtools/gradle/build-logic/src/main/kotlin/utils.kt b/devtools/gradle/build-logic/src/main/kotlin/utils.kt new file mode 100644 index 00000000000000..d56cbac4e840fb --- /dev/null +++ b/devtools/gradle/build-logic/src/main/kotlin/utils.kt @@ -0,0 +1,4 @@ +import org.gradle.api.artifacts.VersionCatalog + +fun VersionCatalog.getLibrary(alias: String) = + findLibrary(alias).orElseThrow { IllegalArgumentException("library $alias not found in catalog $name") } diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle deleted file mode 100644 index 48e0f93ca87d63..00000000000000 --- a/devtools/gradle/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -plugins { - id 'com.gradle.plugin-publish' version '1.2.0' apply false -} - -subprojects { - - apply plugin: 'java-library' - apply plugin: 'maven-publish' - - if (JavaVersion.current().isJava9Compatible()) { - compileJava.options.compilerArgs.addAll(['--release', '11']) - } - compileJava { - options.encoding = 'UTF-8' - sourceCompatibility = '11' - targetCompatibility = '11' - } - - compileTestJava { - options.encoding = 'UTF-8' - } - - repositories { - mavenLocal() - mavenCentral() - } - - dependencies { - api gradleApi() - - implementation "io.quarkus:quarkus-bootstrap-core:${version}" - - testImplementation gradleTestKit() - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' - testImplementation 'org.assertj:assertj-core:3.24.2' - } - - test { - // propagate the custom local maven repo, in case it's configured - if (System.properties.containsKey('maven.repo.local')) { - systemProperty 'maven.repo.local', System.properties.get('maven.repo.local') - } - testLogging { - events "passed", "skipped", "failed" - } - useJUnitPlatform() - } - - javadoc { - options.addStringOption('encoding', 'UTF-8') - options.addStringOption("Xdoclint:-reference", "-quiet") - } -} - -wrapper { - distributionType = Wrapper.DistributionType.ALL -} - diff --git a/devtools/gradle/build.gradle.kts b/devtools/gradle/build.gradle.kts new file mode 100644 index 00000000000000..6ffe25f29b6e04 --- /dev/null +++ b/devtools/gradle/build.gradle.kts @@ -0,0 +1,5 @@ +tasks.wrapper { + // not sure if it's still required: IntelliJ works fine with `-bin` distribution + // after indexing Gradle API jars + distributionType = Wrapper.DistributionType.ALL +} diff --git a/devtools/gradle/gradle-application-plugin/build.gradle b/devtools/gradle/gradle-application-plugin/build.gradle deleted file mode 100644 index fc8b3ccfff735d..00000000000000 --- a/devtools/gradle/gradle-application-plugin/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - id 'java-gradle-plugin' - id 'com.gradle.plugin-publish' -} - -dependencies { - implementation "io.quarkus:quarkus-devtools-common:${version}" - implementation "io.quarkus:quarkus-core-deployment:${version}" - implementation "io.quarkus:quarkus-bootstrap-gradle-resolver:${version}" - implementation "io.smallrye.config:smallrye-config-source-yaml:${smallrye_config_version}" - - implementation project(":gradle-model") - - testImplementation "io.quarkus:quarkus-project-core-extension-codestarts:${version}" - testImplementation "io.quarkus:quarkus-devmode-test-utils:${version}" - testImplementation "io.quarkus:quarkus-devtools-testing:${version}" -} - -group = "io.quarkus" - -gradlePlugin { - plugins { - quarkusPlugin { - id = 'io.quarkus' - implementationClass = 'io.quarkus.gradle.QuarkusPlugin' - displayName = 'Quarkus Plugin' - description = 'Builds a Quarkus application, and provides helpers to launch dev-mode, the Quarkus CLI, building of native images' - tags.addAll(['quarkus', 'quarkusio', 'graalvm']) - } - } - vcsUrl.set("https://github.com/quarkusio/quarkus") - website.set("https://quarkus.io/") -} - -test { - systemProperty 'kotlin_version', project.kotlin_version -} diff --git a/devtools/gradle/gradle-application-plugin/build.gradle.kts b/devtools/gradle/gradle-application-plugin/build.gradle.kts new file mode 100644 index 00000000000000..b10d179997a7d9 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("io.quarkus.devtools.gradle-plugin") +} + +dependencies { + implementation(libs.smallrye.config.yaml) + + testImplementation(libs.quarkus.project.core.extension.codestarts) +} + +group = "io.quarkus" + +gradlePlugin { + plugins.create("quarkusPlugin") { + id = "io.quarkus" + implementationClass = "io.quarkus.gradle.QuarkusPlugin" + displayName = "Quarkus Plugin" + description = + "Builds a Quarkus application, and provides helpers to launch dev-mode, the Quarkus CLI, building of native images" + tags.addAll("quarkus", "quarkusio", "graalvm") + } +} + +tasks.test { + systemProperty("kotlin_version", libs.versions.kotlin.get()) +} diff --git a/devtools/gradle/gradle-extension-plugin/build.gradle b/devtools/gradle/gradle-extension-plugin/build.gradle deleted file mode 100644 index d1b76e22063446..00000000000000 --- a/devtools/gradle/gradle-extension-plugin/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -plugins { - id 'java-gradle-plugin' - id 'com.gradle.plugin-publish' -} - -dependencies { - implementation platform("io.quarkus:quarkus-bom:${version}") - implementation project(":gradle-model") - implementation "io.quarkus:quarkus-core-deployment" - implementation "io.quarkus:quarkus-devtools-common" - implementation "io.quarkus:quarkus-bootstrap-gradle-resolver" - implementation "com.fasterxml.jackson.core:jackson-databind" - implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" - testImplementation "org.assertj:assertj-core" -} - -group = "io.quarkus.extension" - -gradlePlugin { - plugins { - quarkusBootstrapPlugin { - id = 'io.quarkus.extension' - implementationClass = 'io.quarkus.extension.gradle.QuarkusExtensionPlugin' - displayName = 'Quarkus Extension Plugin' - description = 'Builds a Quarkus extension' - tags.addAll(['quarkus', 'quarkusio', 'graalvm']) - } - } - vcsUrl.set("https://github.com/quarkusio/quarkus") - website.set("https://quarkus.io/") -} - -tasks.withType(Test) { - environment 'GITHUB_REPOSITORY','some/repo' -} \ No newline at end of file diff --git a/devtools/gradle/gradle-extension-plugin/build.gradle.kts b/devtools/gradle/gradle-extension-plugin/build.gradle.kts new file mode 100644 index 00000000000000..e9ee23274ec11e --- /dev/null +++ b/devtools/gradle/gradle-extension-plugin/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("io.quarkus.devtools.gradle-plugin") +} + +dependencies { + implementation(libs.jackson.databind) + implementation(libs.jackson.dataformat.yaml) +} + +group = "io.quarkus.extension" + +gradlePlugin { + plugins.create("quarkusBootstrapPlugin") { + id = "io.quarkus.extension" + implementationClass = "io.quarkus.extension.gradle.QuarkusExtensionPlugin" + displayName = "Quarkus Extension Plugin" + description = "Builds a Quarkus extension" + tags.addAll("quarkus", "quarkusio", "graalvm") + } +} + +tasks.withType().configureEach { + environment("GITHUB_REPOSITORY", "some/repo") +} diff --git a/devtools/gradle/gradle-model/build.gradle b/devtools/gradle/gradle-model/build.gradle deleted file mode 100644 index 15e648b26cae7b..00000000000000 --- a/devtools/gradle/gradle-model/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -dependencies { - implementation "io.quarkus:quarkus-core-deployment:${version}" - implementation "io.quarkus:quarkus-bootstrap-gradle-resolver:${version}" - implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:${kotlin_version}" - testImplementation "io.quarkus:quarkus-devtools-testing:${version}" -} - -task sourcesJar(type: Jar, dependsOn: classes) { - archiveClassifier = 'sources' - from sourceSets.main.allSource -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - archiveClassifier = 'javadoc' - from javadoc.destinationDir -} - -group = 'io.quarkus' - -artifacts { - archives sourcesJar - archives javadocJar -} - -publishing { - publications { - maven(MavenPublication) { - artifactId = 'quarkus-gradle-model' - from components.java - } - } -} diff --git a/devtools/gradle/gradle-model/build.gradle.kts b/devtools/gradle/gradle-model/build.gradle.kts new file mode 100644 index 00000000000000..1811928676d470 --- /dev/null +++ b/devtools/gradle/gradle-model/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("io.quarkus.devtools.java-library") +} + +dependencies { + implementation(libs.kotlin.gradle.plugin.api) +} + +group = "io.quarkus" + +java { + withSourcesJar() + withJavadocJar() +} + +publishing { + publications.create("maven") { + artifactId = "quarkus-gradle-model" + from(components["java"]) + } +} diff --git a/devtools/gradle/gradle.properties b/devtools/gradle/gradle.properties index fbf9bb9588a10e..72a61ab94d2419 100644 --- a/devtools/gradle/gradle.properties +++ b/devtools/gradle/gradle.properties @@ -1,3 +1 @@ version = 999-SNAPSHOT -kotlin_version = 1.8.10 -smallrye_config_version = 2.13.1 diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml new file mode 100644 index 00000000000000..85df1c8967dd3c --- /dev/null +++ b/devtools/gradle/gradle/libs.versions.toml @@ -0,0 +1,29 @@ +[versions] +plugin-publish = "1.2.0" + +kotlin = "1.8.10" +smallrye-config = "2.13.1" + +junit5 = "5.9.2" +assertj = "3.24.2" + +[plugins] +plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "plugin-publish" } + +[libraries] +quarkus-bom = { module = "io.quarkus:quarkus-bom" } +quarkus-bootstrap-core = { module = "io.quarkus:quarkus-bootstrap-core" } +quarkus-bootstrap-gradle-resolver = { module = "io.quarkus:quarkus-bootstrap-gradle-resolver" } +quarkus-core-deployment = { module = "io.quarkus:quarkus-core-deployment" } +quarkus-devtools-common = { module = "io.quarkus:quarkus-devtools-common" } +quarkus-devtools-testing = { module = "io.quarkus:quarkus-devtools-testing" } +quarkus-project-core-extension-codestarts = { module = "io.quarkus:quarkus-project-core-extension-codestarts" } + +kotlin-gradle-plugin-api = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin" } +smallrye-config-yaml = { module = "io.smallrye.config:smallrye-config-source-yaml", version.ref = "smallrye-config" } +jackson-databind = {module="com.fasterxml.jackson.core:jackson-databind"} +jackson-dataformat-yaml = {module="com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"} + +junit-bom = { module = "org.junit:junit-bom", version.ref = "junit5" } +junit-api = { module = "org.junit.jupiter:junit-jupiter-api" } +assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } diff --git a/devtools/gradle/settings.gradle b/devtools/gradle/settings.gradle deleted file mode 100644 index a06ece264efa79..00000000000000 --- a/devtools/gradle/settings.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id "com.gradle.enterprise" version "3.13" -} - -gradleEnterprise { - buildScan { - // plugin configuration - //See also: https://docs.gradle.com/enterprise/gradle-plugin/ - termsOfServiceUrl = 'https://gradle.com/terms-of-service'; - termsOfServiceAgree = 'yes' - publishOnFailure() - } -} - -rootProject.name = 'quarkus-gradle-plugins' -include 'gradle-application-plugin', 'gradle-extension-plugin', 'gradle-model' diff --git a/devtools/gradle/settings.gradle.kts b/devtools/gradle/settings.gradle.kts new file mode 100644 index 00000000000000..16b2d96281d92e --- /dev/null +++ b/devtools/gradle/settings.gradle.kts @@ -0,0 +1,25 @@ +plugins { + id("com.gradle.enterprise") version "3.13" +} + +gradleEnterprise { + buildScan { + // plugin configuration + //See also: https://docs.gradle.com/enterprise/gradle-plugin/ + termsOfServiceUrl = "https://gradle.com/terms-of-service" + termsOfServiceAgree = "yes" + publishOnFailure() + } +} + +rootProject.name = "quarkus-gradle-plugins" +includeBuild("build-logic") +include("gradle-application-plugin", "gradle-extension-plugin", "gradle-model") + +@Suppress("UnstableApiUsage") +dependencyResolutionManagement { + repositories { + mavenLocal() + mavenCentral() + } +} From 78145855baf2b472254df413951e26b8161a3025 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Sun, 16 Apr 2023 05:28:13 +0300 Subject: [PATCH 025/333] Update Gradle wrapper Updated with `(cd devtools/gradle ; ./gradlew wrapper ; ./gradlew wrapper)` Signed-off-by: Konstantin Gribov --- .../gradle/gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 62076 bytes devtools/gradle/gradlew | 276 +++++++++++------- devtools/gradle/gradlew.bat | 15 +- 3 files changed, 177 insertions(+), 114 deletions(-) diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.jar b/devtools/gradle/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 13895 zcmZ8|Wmp``)-~=Hdu)0n3Y-8OvyK$p9^s9MM|Aj$miotNhy-{udLczZyd9uWtD)X_{|!LhIEF9y8(e*Z zW>^w$u&x|i9OjL=#6Nl~*ERulzX>8C-}o;iSMRYdfCU5d`~U{V4>HCg0HG4Xg2uP;fn!>S9+>LbuWbc0bETMQfo9~h}yI*TSv;Oikl~t-+xqI-`P$Rj@yi{mr2zC~s1snMT3!OPBdJ%IDnPXq+pl*Z>=+?qo${lkCSKmwTlVjfb3thU6B8yFjr!tphOs*G6 zwL`RyVAUXj4p=9&@PpWK)m+REuvHaq838TEhY^7W+JAp$ zZ^y;8`Z*@VqJ{sFFj?<|7SKS@G`$Yi)gx%nOi@Lr zCv0IJlFz0bP(eDIW(uWNq?;8zEAb+uGgnkLk;y!4XhA6=Eoa<`+|;6mOq>z`%ir@z$4)Mkd3 zF=hFo zyd{*bRQ4YUe^bU*Y`__)Uhu5NIjVJ~a}{lHp-_7wI?#EB11XcqmdY>pk`JJ) zW9Rt!tK=N>fZ!UDomwMnb`0EOvTjcNl=yW@$c!OAg*=l()GjZwSyJ+o^;Zi#I5*uP z$6qeih8&g8E(pNSneK>93A(8*%gvwv!0V|SqGcj55Y7`=N*@pJx_ig3uVuf-G~LJbm`7nxNcZ>Jgqy(LTHu_C2e>STp^Pm{}f&^)XU}vzuU`UV&>e& zqsXNXSs;Wri|?NhCq0vXC5$>9Cag$adyWz^x@NCiy2${9Dc)Y;J8k1Z933W$3$H}g zCQFU1XwzGm_WUheXvnDisH_%BdzMgNwk2^mHcQu*x>U%iN*B^8U(eVz1~(%`kV1Vb z=9T0xmN?bQMyrrd?u}jer}zV&sCK6zSm!zV8A8dP6THF=4*V{_K*E*K<)I(Q^(eV!m!vu##-2g|G z{RB;{gJB_X-w{ANq?ft_n!@=O8_gj6FxW&zO$7L3@NjWt@R{NxMbpHLk6;=2$0P5P=kKc1_85inX z#s$&s0zhV1cz>nRb#|D#N8Z-=Tphm)sGH>9cz3K3I)6XpimJW0(6$GtLzN(YPu9%R zdFXG9|30AZME4r@joC0IdvBBe08mF@+5Dd97p$h=n|pi80Cn2n{ev!S$llPGLqHva zZ3*OmW%!Qj>C$F!Ffafl7#I_1(gz!aa)b{ebU*=yH%^kr=~N?|2&2Df2o9X=2B?U!#R#+Cj45=f@=EcQx+9J z=X3~A=zbX29Fqn23m3dm}0Voj^Q9BjI=MiG+NZ)YCYn@r^qv(xE3=)&i z=(ML301=rNTptvUt2tnsPb1~G*DWFWoZfv)wV|uNW%?!)jju`jN(K-0$JYi!ofNup z9K%_ucHwutbZsl~vDQ!Jtj8uI6WA6K--@?8+_=t>g|kgUeC=w`IP9m&*fuoO3#A;t z&3@=3;J0>yjM89?h5MG$S`wW+=vyYOWQGhIP`^vScM8^JL{mGan5uTJPvAg$0z}8; z zhMi+S${H#^wF;eU-0UHJDo$QwXDjm{ns>^ltubKXtd>6Bq-=ByF%bHu>2&e&uZj2X zgWIq(l^;Ab7#I@h%#j1AtBIkB`GO*y!i;1K+_SZ-p}4jmP7#%E-=>{ zK(3*ObyAgDLnbBLObTWJWNO7<60SK6*!dD~_7JOWTB*}(*X)ox0{lq5ac$ABkcL~0 z9qCHT8^`QIe_4-BW&mIe*&0VT6w|oJ9hnOO&oZUe!rP+gStQ)h5ZPhBprHZI;So+g5}&;adp<|7#r@DG!wXmtwdwy=7i>a`x1D4 z_N$0`Q)>zTVUU%@RzlG=4Nk1hE=_klWj|6aj`KJ@S`y^%bifkdX`s!A#|mpM-x;SF zg;bju5cA0?a}%hk=3AL^#2B>5X(TSne6PDWY5gRVvn6nKl;vg?SIbv^Uz=+4aPUft z-$}QR)+_U?eX*p)V0%#0@S46_6c($OJL^bPj0Ij}up8}In#GQa&Cp<#%ZPjx(^97{ z8AfEgrNRTg-l9WJrNJzHx1EkI<|n(P3VIwFlTvMxfe=V&NL)4MubdHqZF)&Eq4`+% z7z;>s(sjUsebUfFF;~)_%@3BDl8i085o$H!*yBv%Z27d~)|jfg4DhJ&nMb((B#4hOfeBhL)g+r)f%2be?s2ox zT3j0k+Va^9`gqO)FoUV@F|((*vGxN>?5IlvC!BzW-8cyCy_)Fl8W+eg<&Lz^s>dJx zkly@2Xzzi9Uf%|1pF_Nz-3SgOx*+ShK(x=XUlP?;EfoDqAkkwyR*yjIcD#7-@=|Um z{T+V}q`6)wnSO#*N#Hp8QT7^>6R+H^_o4LBc}$aD^@(1!+Y54YF3@A|Cupsfz@Wt8 z!KwmSb9}3l)u^Y+V6W6(bL3hk;XTY4FNy3hKhID#Ep#xLM88?`xT=lw3xsgN;gKK@ zqpElV*j#e;{w`OPYcb1_szKUtRLygjq2ldhGJ$8ksyH(hF%^w`&FH|zlDK`DfuZ_g zs}!{hMk^~48&b=jWqG2*^m8?ERreHIw8dgR`Ugj*t4Uo`^U*56MmU<^ zNxcuRh+Kc2>W~lzD8S6}Xho3s9f}{o4@tIc)G;lKXi(HJhZV{qSH1-xj>P2$NHEK2 z)TjOy%>(9Ot_zPO)^tp@AsSNd+`R?}_2Vd>=eT{G&TfITkeW@p{F+FTJf(n87##z& z!%w+6-!NJ*?9Z(hbZv^BG$Y1`BOo~*k7jaZ)9%@;H6F+W!Q%IV4qSM85; z0%xWZi_wc=CCc>2rd3Rk3C79_rJH1uG?yFIm4f6Fdmts<41T*;3ek&p z3(NaDK3iIDa)MaUD{_;~fMV6obrT6_K$c+eeRBJ7jd)c%0jldoJX`EWz8M$b1s|DS z)cr6)em!+P%GjM6uQb6CQ!FvUb%_>qbKn=gHl=@K-Z*6_VaD=;!?P9pr$Z?6NrB%a zb_G4M-UkkhI>H@+kP;eS4p->q_f+&(R^7hyRsS9Xl94vA^AYlM%tdNdHQz zFQu?Rau!C@&&Dn;i5iEhn3`y>{O-m^_*h+Jp6C?D+5yn9Vq5XVQoUe#BP3}lqvHa} z@x~UctaNE9PwnRg6+15NJ5k(PC0dETm#QxXY6&uTqupm)GVrsvKC9o)&*mLo9?$Ot z!SFjh+!mr{kYE5A#urFIBv?<(6-HtqfprK#3H4dylz5j`Uc)Hz@1}A9OXe=4gf3_- z$P|^SpeQ89xlL`pftC^4tO3N)JXTqmkbruGAsraU5Y$fyMd~L3r3t8-SfkX{n4<`@ zhBKAeBP_1Rd8q`<3^dio2W9^9iYW?#m-!IKDO7ge{vC%1Y>dWLslyLNrm-!*YU3Dy ze|qm9gwdCJKZlwcvaoV%S_%X-k_?QIf2zuAG&32WtJ6NDr0i+<{w;CG_St&I_7HtR zTiR;!)_1iw&#FKwAGFuBze6(_%DLu?>|K(H5bf{br_f5|#qa zNOuJQhSU1PGQ+dltC{ik3sA?PcKcDJg;_^-LCcLGo+|3VsWx0vMNOpKz3*U1wGG0{Z@O=3gt1Ay|67ZJC zGe%Q2bP}rYtE^Lc+ybPES@Snxwlh7Ydq$c{H?d&8e>!Dvt=dFxeS0fvt=u3$KHuU; zKHr9fCbGGQBeJ~@{wdgJi6Ah40fcT>yGRWEe)%=j!AaG~XDaHNdzsU6*ZJ2XC5>lv z=IT$K4yEi0xt7i<^=rn-$1nOKKRQZ$7df4uU#`?ddlH+Oo~+H_Zq!-}6VK;|?PGiI zhbt$ffNJ|--Bn6(L{pZ#!&ykjgBXEs%hmxg3vB~;GMKcAfeq~#2~f9vw7{>?pTu{T zcxLiHNCP}pJ_fYl3^gBy_}h~U`lx1^?)q|U1cti6s?Nt*RvSgF6WD8U%3uk zwC7lEPg``Bjt5YXNFE!^nq zJC-z}n^zNvd{jVhiv9aKNd}lH0$n97EBjb`Fh+7~amqAtrK{@Sn3QZO3BBiUIo^n$ zsiS{+L+8B0e&`mFnEqM!LCLnzlclx?UwZ(L6!FZ$b53#xA2caP^zn&!GVtipn{W`U zvN9yG-?@6)3`HYt>E;wO*N_UGd``TDMJ+e<*WUe$SGeaBU)dJHbvUp$J?}caKfP>U znZQtJY@$~+#6FOn9R6m86Sq3iiaaWa3kiz1k>ntIk2*6R+6gchFxKLcBi9EMRVQrl zP~vO=WAFX7o6BB76*mwH?R^-5HX?KAu`a^Eplkmc zSXpmBvQ4t(kVfyQIR#|Wi7PYcy+x;(5j|LOp3()IiR>2j9**}<*nO2NiED?Z;)iGh&PH4nB*kN{VVt!lYX*(jAlnZkabB{Fa7)iF?pBFk(T+)xyg(Y5TUd;DX&MX&_}`_=Z_KcQ9;Ok=&YEqPyVul9sRG%P!*byO8nRS# zGwOm?IyLaeqMf=7AGF{L7v%GKmeM+;#U;vPs0=0R1WAo2JIq8N`PGDe}Q zt6VP!Fqln^U#5ZJFp?b?d*Q}Ynd3Q)jTU;{RwiqDncXA=DXTWhkWhiR{XF9aobJH{ zEYYt-`Hdwp@ZQ5$_i&f`=DA1D>lgJ>_PkLE6#)L#3R1Giq@XA zCLtGAgOI35<3Y-&55pCx#&@_R?w|x@%3$Q-X|@=Zhuo`C@cOG0@M*&sW@uXQJz-M; z=ZcUIw+bXwCV+k?WF;Ugyrm6gy8KjZmaobl;Omt^`!m*(!@&}j)uCT=+}RbLo7WiC zM*7VJG5hnkugII&>R-Jyx<}$pNBtEizA`Gn{GbTy^WPi*o!^5_gH8ME&+{<}nBbSA*p<6A z{c--0SNgk{iH@g2s&K3L#wl5fR-H5$YrMAEA$gwfPC&GdtAb=bUk$?Md6^mdF&^vj z+iAp=tz8ZK>*?)QgEVBG?CnAb`($wf9*1w->8@)hg(hpH^%IFjGqTs7<*jz0J-*C! zs)=j2cA@=KgS0+*LX^Qe*))69yFm;(i`r6`?_p2Dfi!AQh43;ix#Kv8_*W|IsGg;f zJ=0%L||IPz~u^1P?ZkuO7VD7>GEfT=K*2JP!?hLF1f0rSkXpoIojW`}iLv zt$qt5Kd$Ty5UwS~N|w!IW4-TDG6g9!ecEoE+JUM(=T{d4yASY8>tlDG_XdEUinvXN zl>XB_*;iM^53IG90-1uxg#z{ov9M-y`(|4~g#J?dVQ&7tJ+a=N9npjr(_lb@G$v24 zPeA4UfgSFXLSe$Ghn!^hh)2|+YuV|~a}U+Y9iy?b*TKn*`y{ADmlq%d|HzJn0mW<0 z5McIquX})(09`s?@%4OLy)I^TdiKP=%}XfT`s{oX5eauP0FS#ZH3$bT&E#E)1%_v48Kc&JbnK@KR+fCJ+WWg`;cXecj9ij8zP$MV%S9InmL z#D$p6%KIKx&U;|#5fPg~KlH~fC7Sh-(Ut}5+tSSriumK>DDF&sl2pa_A|~tu_*8aY z(*Ud4=(+k5;ke&7V(y`$@j|FGqk0(WA5Wc(N${j@=7U}Xs^XNgK(<|>qug3-b1T3( z0=#Hgj}+TLlDhVm<>&!j$jvWXm6SLkMW&2k+;_u9Tq#<8uKtToJ3Q^==VQ0eV{+r6 zQn5p9xfHk@%P_FbqYM3DFnxUSXF^sk#Ms{)T4quYP`fK;T+Tj&gRl6sm|74UbHHrF z7h!QzEST^cpRO6L8_~zXNp!niGl&79$k_8RSj0W{xMrR)D4`>~tNrK~*s0gkO-PC^ zu^*~aOBQF>qG>`%KGd+7W{nGqd5lc0%E_*&rn?MObfYvgPvJ%vawv{il#Km=$-hF* z1V^<{OA_t~X|u>{5ljynGhf844dJ#q31&xuibhPgP;6z{C2qw67U617_1*$=(_{mu z@T$|cK0GIz9sS4`1VcT=#Rqfsfiwbly-A61ih$VWK@T{K(t%VCA4=VJ4(eT` zLP`DnbAKO!X02C>qoh6kk2SEE|nQ8^J~0S)XyHMI1`BA+8Q-{{y-|Sc=j6N9xVnV z3^giq-U}tR!`_$ty{geQQ}xVo!CwzlXx}-}k2&VU3u7n@(1G0xP$36j1GKVJtLydS zm|^pz&9wE!Q>OWGMLY+Y?=$lIM$IKdF`8Pw)uhzhmFGtIyWl(qh0C@9BbzwDR>rEa z2gc62w3u1cW+De8tCw(3SQ8EK+t9l|ef|)GLRlRJz>SleVh^o zSq>XS(iJr>IQL-5^9LMn-MBxnO*FN{K2{7JVUpW5nZ{sz&_Z(dXDW?G7lmn%1nU|B zqC_R`=83Y=g^uel37AnfplTx)W_%O1pY@^^#~MgJg`0^G07b7RHOA>7K6Vzom_M3= zbD)3(BXXoqR6UFGHM9a3uK)SxX-0%jvKG23)#s6{vbq>#o$1tZMI5hU1c`fGME7#Ij+u%*rdsnO7yaltUc zz)OZMW*a=_Q|k2CFQ+lR%Md1Kd~``A8LX7vMtOupY7HV^E*;7o5$|Yq;EZjl%s-BLWa)nM| zOY1bfH5&%ed5t0h_`z*>GNiXhoMBw9+W7 z4U!O;)Tz3n;x64wHcYoivoslIkj9IN05|H7X~GWEx-k619Z-KjWv%8@$1wbIvAFfI z0=AQoH{3yl1z|`pSg$(!>x0)nU|wT@4i`lCchm_nrU@Y;XR$D^5wA!Ftl}*9OwXFZ zai&Zh_YNnlz=LEccY_eUXOEY1;q&Pd;dLtf$RffP4%P#4ZyIjV&0;_13^ zIVGMUzx+5jLyq55_Qz0jPBx~-{DfuUW)hKduk1gv0et-e(ZN8;IIdhtV$3N9Bg((Q zw5eHG)FFs=ewUwfdHfvHb$&&i=h{#epIdWr+=YE9)%453DlIOHLFX;%dv2LDNMrMZ zEWU|CvEYY*(2SE$Y{jAd$QU-wd*Hbe5yO+Lu6Ux|(Y>L}E_jNPR+TX@Ch(#orbP8g zv+Z(oKz1gylHHGKB*FbdpSh7VBM2KVmx2oj>?q8|s72`}5s)jT=s4;lbRw$cKh+N{ zVTxW`s~QW~rRB;e|7pxFoJ_Vm^eVjcddUh0Xp(NhCBZ@Uya;(x_wkvyH*^ds{2_H? zs*PV?33(>MyJC_<)JC=|9II5@I`QnNGgZr z5AfQVuy5}nzXlGQGV~eESn9UcL_U$gw(QjDVEW4b-o=BQGBT*a$1Fk+4bm2n^6m6w z_hn7X46IDL7iQZ8s+_(8yX!fXqM9htq_Ts}08b%snTZMmP}{6(anfizqhpR1cR61k z=sfzRN*!0HP{Z76PDg%PUY)rjwhuy71^5D3f^bR;(fQe>3U#zrWwe0OSYjHZ-eSJV zuKnE7`~*u%-HShx%*b9ZPU~(Rg=`lQI$;iBY#2k^6{Ef6e9D&EK^irorXEpE!h=>^ zVxH#pyrndMgk)Ff-ke*RFsPY@B3AM_;Kj`PIJU@EH^QsIUo1wdl_wfqd48O^9?06@ zt*>img{+gG%WiGU+&V)`jeJUPSDDLhd#nVrUr~dURh(&O#gMnA0dEg-#?fg0Wnp#P z;4QjL{Fv?Unq!!)POdN%ZI&vU*Ww};bqd3@5fb_<7mIa_w@U?X&ed5f1FCQ@57aR@ z)TUphLPht{?j%;+T}Sfla?uiG26R^?7=x!#CUXw+$_TQx_%vLhgg8LVJz@{QVxH;M zGcV^6&Z%`yWalhb>$VS`{^Ex`w@cldtZ8t!!exC zu+Msuk)M-ylAjAz8{yA&TjgR`O%H1H0T&$<*+K{2-<~=1E0~C+w@CzUg>GyIegmx$ z$vp-I6CygcS8Jm9rR{Wt@W?<)IdIk##3DUE741Dg@lQ~Lskm-7=|2%)&XCF_8|780 z9d-AgO*4e1uf}M3*FGo&%&eG;OB^Vm_x8i73V3P?d^qdJMvO&{H(jgc?n6UYZ>-FU zeO%|qJ%xvB;o+$e+CHm+Ot1UgzOrX7_G!pZrt%?TaOs9ZPg>i>-gg^Vuu6p>LEd99 zGlCZbE5(oNfEP{~x>KfOZv6XWA8zfk0@R+{;r7WV?(wWFRaGkg&mR3j$wJa7CBWz= znwfnWiE^@dC=n6jrAY4vvH*;b5{E#wK8AoUW`vT3W+8gyt9<*hPl1ID>F3bkLniI?`*u@J2zcd_cAH2?L5O|qzu1jQs$J^g9=beD zYoEgyA^AIv!P%D3;3T_C#zm7j6=+ACjtf5->)lXATb2p>g%qD7L1EbTMh(z$4oMY) zSZft;+pfN?a7x#%4}(P3Q)Gvt1F^8eu9}_PDW&}_2hhqjF#&SGUnz^`=V(U{;B;`G zt7FmRinElmq%KVXaBZL$+hD> zLe`*wO^B_i5W9q8#>l8J4;5{XbZg#@Z9|D|{gN8}jF1XBNzpi*9R3+-F)w8EbJ~In zEdim4jC?)`IzcZ1_`5oBWd#yPJNc%ajkte>^q1KY$#LzK)`jz_7$%1`N1_tdhr^wG zp92GvW>iDG)!1`I3*Y3;C)Jz7**nV;DaO_d19A_8qX%OCf-KY-GEZ#Nv;2CZQ*ht5 zY`vXc7yAb|?h#Z_dEKDC)Wp}g7hJDlI>P+ctKoq`U4!4az+ECGUSGmfHRpW&m_%7? z(o7gajY+w(Le-L(_Al|yQIvl1gk&lX-5BMZn=+~n-N}$`J#2x5x&B1EG{drVp+i;- zucW)%=6bqw%wNB|=k!-_k($v{gQB1ZX`dn0tu@(Z7b0$g5k88nHYIEE zT{wBh?|8X1yS1ITl!hS_>>{cobd%i3<#)=amBnHn>p;m6f%!T!BSP{_9DL_Wmv{PtyL9hoTep$i_uAr>^@7u^a($-HJh2k0xNsYVmt|v+kCWusAE%8~f zgZeq1{C!DL z7|_)gsX-J$DBwOYs|TpK6>I&l2*#dm_B%7y(JCJ?jaOVZJg!;eleEd~bT^pJkrk>q zB4)r!XRL!mow*tX6z6JA){(LgKapsISwxE@P|Hy&;*5I17ktf2EQSu$>0G&bDc^|D zoB?VpoqIQzg72DO!zOL#jXEsFWVZoyX*Q+>cyNC5+bi$(-R z2PXnAH)~j-X7q#KV*r7K0Tj#Pt=_Ix!xQizqfxG}vfg*swPul)E%ElLW)2B0BOb4U z$5{w|1BT44k;f7uS&T@0UH_mBvgr?Q_m;tun8!5sqbDu3_a@H76e`xzggnje$~Vo7 za$jN9vO%&+?c(NFBWd(HH(c*Tf3txzhrnp4X1859WXnbk!aVPy#xl`hJYOb;9$6q{ zkbx6NHJ;r$;+CoL5@BT|)P$#Nd4mLhJ?! z#V8L2#1$FDnc_k5#=YeMy9&SHkG_wJOT1g%-w$u1eta|QD44f{Y&WqiWW218tS?qy z$ZDkAwNCgrzLY?-u2WO8%SB`AO_vLdwg{s)2>YT(Vp}$u)h6yDPl(o)wFGQ6GTv9!92`>rC_Xgn9)BKfMk>B0lFK$_ux zk^my^G@g^?|Ds?LnEwzyJ7qzahke+uzE$SE-IhBwTL zCnKg33>Lk_tsV;Q?3Nd07IG)>PA43Q@@bD_XViZuJnF+-SR9eSm-b^YbLCU7PG6GQ zJKkO|*b;^O^%Ehg6e-0+bze&Un{k(1?Aom@b7Sm z?b{}WJ!Zfj23oRMKPiLEh^qy6lZ(sff1?M#aP;~C;P0@AuUam$iHH$i(Zc-_8++)) zGiB*fRHaTE_*K_lAl+<$IklN{WiruTjZ?Ir>rocinb-6%~rZb)Z@l>WsZ%cVnF`u(k z3MC-R0(^u8vlUE{9TX~VYef_B+y~v-T`n!_ zJXHL4N_pJy{bQGCGEJ2vO`^5M=(MU>=QoaiN4n$ZmlEhRRC09~b|CV#QExkR{!cxv z-Ih(Yq);JB({7Iv5SqD14A&CD>{9d#mQfp_-1nX*824hiHi&jI!rbzk3^mafyBi2I zXwJzh@J~^n^Qq+Rev`}V%T)Pds`2QDUxGv4pkJOaJP+l=87o}7L-RV1V*p70%Q?kQJ!b+v(*=vXQsHF z#w&NkJNb4_Kvu6hrx0e1Q_pLru87EM%Rez`mTlk~vCAr;IKZqQ$#>gK{ZQNJ$F@r9 z17m<_yD6oKG?O@e`O;WsIhdWwE)Z7*SyABxHvKJ!x|y(wVq*Eg`D2Q%Q#&zSm8c_X zY`zJhB88q%6!2%9%}+RQMhWH=sbw#8{a(embAwu zeRHhkOtBY=U&ubKu7vS#2DPzJ+WbaUn%Eu`p1cjDEU*&qFGKE(o%RZ13w1x?o_-#{ zj3y3uOaJI8nlJ`Rt11>dUer4~gzlg1qwk_n+`w_Q&I230F}#e<84l6$Ub}ga5BLCy z$uT-aXsHnb5x(Q2(qiSxMHMrLS5E#p#t6L)COeA@Vy#t82W3I7zxNN*jGG$^^A3V~ zTr=^dD(liTi!S&uFU(~grGKHPJ3#7Wm91!jh!*X-6-6}Q?cA`2ld(6Q{A_nw+16`p zBq**{Pk_!LEyI8)FurdbBN-IqyhFR52Y9f)rE-#p}V=M?A%c$M#J3kjR;+GEA#vBv7ig$61YKjN2FsuXxl6YE;g-oLfc3d7ixb z(~0wjUXzRlz7@}MhgnS+FRey=b`F|l<3w;qodOa{(-yU^k{7Owq0>0sq7~my3O9?# z;MqUiGm}Q%_f`tMUWXlWG>uF0_?>-d_6ru!DNoiMD&X~fg!7a0H9Z%=3kwQs-Q1{g zxIsDbEXG9ly4o5M4LODy_vvf8k1Dey9QW4T^up55&l zkpg05cG;FhOyo7R#xy!3{&xPzXTpzSZpRkB&$uR(?99to5LDHD?ak+~^R*OGg2wFv zUjX`1J0_eHXV^8UJXLSFxSNPlDSRKCJ@A^Jrtp08!98KQXBT1L%avWTv-8l?va+Jq zHqd)|JwByFcmK%afGyJ=rb@ELtB7tehaH#)iRz5v6?C;mDxZj)`upc|y>)S)VveGb zj?RG?$-D;ms{Mi9UTajprUthRTIksl=OfjZ8iD{zhh{YOLQV$~PKQE~HHn!A-`+on zR*Vi4Qpbff5whUZ9dr@0UMy^6)_zH48Tiz-RM+T2vk9}rr*_Wy-CfoxGjcedo-{zF zI=^!G@*UT_@;VTiU+I>Ht{NTo^Dj&T`?{QK>&9s}PXt=TxQbmKUDW->h6Eh)@|}uY zfxqy8(^9cw%+k#m9NNz`x+UB*DrrBVuFm%-eo5kp!74OI^qtOcOgmD z8KADRYxrHr>DeRsuJG&}MumPmOimcRYf)HcNZ@n+9Z>VwI;H|{kuzD-~H{S8;hQ?c2 zjtv0GZ}PmMOMCz*ca!f8t!=)0eIWsWjJ71-P|23{TZz8yg7Kf_uYY%rfKs-#-mI6~ zWDtv=K%3NLAnu*Falh$e$sp$0L0w!lpwgZ9QTM+QD_m~`Hwd`>zEy>8mki>B7c|Ao z1M1j$C*t3TL;k-)g!W*N|5no|$$~>*LSlkyga9DKJp_ntp?@6S+sqXOyh(8W{uKnw zfCBb--`KW2G6-skzsABWLHJMO%+dg)|G1h+znMw@zb^du$snNhKu5aNu>aTVhA9Aa zypI5ZZuUl#f&d5a@?81@G6)V!kn(}ZTjkqZ1;HA0Zp8~i*?9jK@7DzF5Cwb{M0EJJ zdFQYCg$>j{ouh%B3M1Qs3=ZGV(U(Iq2#NQ~M^NV>2IYUw?*FKE|8LZ9$ASPj2hfxc z)|-fz^uOHyRf8gcfie7#JF3$^?wBCp5zhlK2f^T{`>T=fi_P#-dNmI zGKjp)zxq`<#rm&d{*P?xe});I^_TmbiV9SEit=9}|1ST-{Qv(9yx`vu!D0;he=gX+ z0@?prp8cP``iuSvME>_G8=t*R-p;@1^t1OXT=hnT^!!D1c2WH6hj~s0Vcqu+jSSK~ ze?K{$!~Z?8YDWJup9~X#I?msx!{h`2w0@2N(KYpMNVp(=<47*ZAV}x_uET;%E(l>n J*WbtZ{{Z#P!zlm& delta 13442 zcmY*=Wk4Lu)-CStgS)!~5AMM=xVr|o3>Mr6cNpB=LvVsyAXspBcgQ1o=R5aaest|x zYp|Ud;3g1aLn46!*8mAJI&Z-nf(`=#0paw?iVYg# zKUs^o|DOcGK$5&gPV0aMK}b!cw=e}1HdMgiC8Pg8*>1^32Z5FfsER!G3mZ%qKjJOpfesiQ2!1wa9roW6I&DK_t$shg|m=c2cE{QdM|NtSH0rXoXzvmNP+5U2LV{^QbB?sv0VKm95!eQeL4~+?=ho^^MZI zi4QY0fsKBbqrOh39Z!#mM!z2}i6F-BHKbV_Q&qzRsaF`l1Vjpm1sC-ZseEjRhHlco zfXoyCv0NC5K}!1s)zB(Gd8sKQIBYyB)bFK(2G2GM&K4S`>_HR&4tr1?iRab0FsEbp z*Jv*zm^-fRK+ctLcyDjn-afw<1S1jM(4q5ykfHQzL_}qIFL}{AIQ>4(4ufTO5LOPw z_jW{#M|)nyUycekv0yq3ALu*Gjx4MO>bHe*!#3>nE^vCCDgcN>sA^k$Zux742g7MRGS5YWh9J!2T zS<0JF@`%w;58G&U(_V6*RvcGc?)SP#I!b=^l;;8|2L56hb1X6;bd2imS_1e~0c%T; z1T8HGf8HR3ELFmM^n?Su6+Q7D+$t^=tIK-pWi`W;i!lHwI+jG7m{1RRjBU0~dzp zhN*kX9bAON4=>l-DWvYo*J$Q4Xp~|yYTaabShU@ns@lubZE3xU%6MYv&e|3AuK8?k zu?#J5JQ%%TJ7Bb$Gs;&*)*UAk%Oo-5q=+2(Jm zIuppiu)ZJ9p`Q{Ox6P5{rbDkZk#-Qv`%KHjq9XiNOUl8kb7aZj*E~>vv^dbHH4oOd zczWr1LJT!^o_(O*2>j}6lOtE3Z)Pht?L5pyzPpntJ|r!%j z5uggS6oZWkpVt^698p3fEKA&|+deWq)ldqZGKG?a|~=1V2xdW$8-mayFlC zJWmagu;BBJC#|ZHrUXfE&`4P20AGgWC5=H0HjYm~^E~OwgAnMps?;#CY=ahb7%?H$ ziejQ`%0Proz9+myGwpEQf^)-=KkUK?uyDVM9dcP_xwRPl?asXN_w$2*H zua=Dr(GFqiFLl870&u+1P>>n@QI(3gk(rj0%e8Ar$G7fdFyGel0{sZrPuEX12l`k< z5>lA+*xaiLY{Vo_72dq>E!s&D_ z0I)&YzOCXkxi;^DvcHbfU{x!;>3?+f!px_0&rPIW~iPmIG@n7rmiC;XiLC?f3vTJUz`Gg=p9 zK8)mv-V6dl|9;(R_$VaJ&lBtE0aw!=g-iJ(;|-J>nsF(42in0{Gp)Wy}WNr3llis^vYk0y2t{zC9G7SQW8GEvz>ZPi09E9wH*yE=+9`RdARy$??) z&b{^h_aIn=A*FNBQ7ATjvh&tjsQ~1FV3r;lW1~f8kh24Aagu#Jxb89ZAs>t(Qw(FD zS|S=1m#oMS;Dwi>0@KkG0*-OHaJb4?~;#3j^WrKgCx}3YozM}uF#0{&QFMled>Mo$+hUe%lY}nvK|5GwA1fTy@ z(^KJxKj6OT*`H=XLgP=vBF+Dn0wO;EGz7>+V7(zo`X~r*4Zb>n+<&CFW^ zx;O-Yo^0{nqPJTC5S<;>8>L{^1C9Ql@|#RETigaBa*_pJOL-@W8p+w%^}Gv*)l3j& zWma|3USri z5Z(cKy3rMvzZlR?nR7E6wO%( zDf&3(AqN7_lQ~96t?KD<`i5K_pH$aIxYeiWm}ICd!1&&$NJHxywzKXt0v0W~ZuFwG z5rq7KRa$-&A|tYU(+b&T6VxMx2Qmg$O$VM!XY^ciTE+)P^vMMLl^U-ySP1P83$*2u zNcQ@)+ok4pN7x{9Z?XBZPr*Vr7wr91_FvBH=xc%RZ4TH$W+0R#VWB0Ua`8O;-2Pnqo5QG!{#(=RmvtM({fuA>4ai&IW$2`P<|D!v-qs^RSsZ z2+y{qc6(Io-Ywwf<$c?(7ay7Q&wZ)JAdk<#iTYCy`PaXy(4aeKd-6d}u}-UT9jad< zPB+QbuZWqQGTG)@?W;;TDUqxD9Q+ao``pz(B`&cPTFR3|P6fz8&WRjU<4 zKLyJI>Cm{uI!saN=y6~Pp0Yiw`YLo6*z$^aOS8b)G@I&C3g&BsS$8cSG8QK(iy>kZ`195!*f-ndgPIM}p9?J=GYwFDqRYmdSymmgW9=>uiSN z{#DAsx#ke6UQ;6!o#~HR_BN1VnmUn=c$;LY0ajlu+#0J~E8a8UlvxiJ7^)K-FrJE% z<2gebNA1Z==jc$B(7~TXXM6&Q)3pToSPkWWSOl$HC)oA zgNe5(5xkR+BQco*Qiy6ns0vv|LP>(bx@_3vrzwIU;zwexl)cvpL>(yu=LHEOokp5L zRA9~H_ysBBuJrkjur_&)92IMj*o{ClU=^%$`6*Q~>ISJTt7*aljn)-ljW+BK3w>s| zLN#{_x{$hhj7jvX2)Uy)P$0MUVAnPRgU&7jijQ%_?AODC$j+(yrkEJnuiw`IZ7!R2 zPB4GAo_x+e`MWBlrj}-+i-p zjlo(;u36|+c@du3o(ChHTb!CNG1uvA!k!ACwEt{gFz)!#yl79^=yNgIS(ucgbSZVj zR+{Nqx!hUAVk>-}*j$=WTI$Wgh61lQum5C;c&WKWY;gwydc@?bv+*)FqXm13fAnj~ z7*E%gV-~u|mTx|mAw-ZO`Bi*+jS3ZWr4V0~ zh0jG$(j(1RVT&D>u$wVNqIc}P&MlcPYg z_5|^fraxyhG$cMGT+&0SEe)_*oGW>KQZ~0~Rq(Ly?T1~r;_P(>cUwlKd0k}|K>BjD zPqf(ox&pVUNt_0FAu<5Ry?hfTydm-bPTF3CYZH!1pu(4}QAR&!8!uXdc*_CBC>{%1 zA#ZnKhO=T2`m_g!lt@+#fsRc8DFky1Glal5Y`)UPr+ffyzIo=U{^j>S8)Iva%|F%A zGycyWb;bAUPc@wa68+gwA19vu!9Z~EZ_QRl-&-LDp`8Ih-Pu$4|EZ)baFvDzZ+qHA zEC>in&_*!{DEABjn62&YhoepMyX%-^)Evr&KA*^%h@n}5{G)gq78)|*fHeX)qcQ9U*FEo?pAZ2&Lq&Gb-n;6#E_Xu)r30J;4{Oxf#|W(TISTm37EaLAz)5( zb1#?ZZ;q%NG(z8!JPil?M!oqa`W!eDy}m>{b|!``@2#VCMt(D7+2Uyh$(<&;@EQ{J z9;IF1P;>@bd{rIHJhxo+R-ifU(Mvyf==AfYG4+z6+4Q1Ar=nOHUA`Ok!e3Kj@w~@yTV|fh zG~45!>b!@cwCpXeD#8WQ?o1;`s8Gotuz$`fbvPoAP1e|d71`QPX&ZV+oBm-u;`HE@ zym&N?*)l!sMsiRqUCH=ki3ME&qFxMUJEEzrkRkAmSMOkwUCrLg(Ig%_Sr!ztKfZ&I&V|;hkBz1&x)60kft|N;0kXv~YbhB+EPM4N&!QS#}gP3tLBgQpm6pCr<>GQPu|KzFkk@ zOl|mn?>(D2)rZDbhsv1rnmK?{HP{lsAt^U^B+7vBxyOSavbz-KuGLmVO-nU=o z6S)#sswKHb>egmHw;{EM^SRV1M`pAk%gw4o7vPVDDKws)dfEG=5Opk4ayvRjWd%MK zXYcoEj?$jD=(Zg5!X+}wY2~0gxnC&q#zc-9wV0VW_PZP2tztcR_L@_n9AKCBu2fRHnbjeyv<*yJx~og`}k@A0HvO@R|K|$hBMLQ=WrVx>{$Ar3jVpsHmuC z$t3qeB>3$4EYSl>!zj&+H1r&FyDogkkYpysdb~}}mQ$u9=gVLTQ=Ns$4fWH&Gy=E_ z%CR%}(Hu1zm@)A~It;A3Re$W4q#uP;pyBCK6ta|7RTit)0mWh==&(r2UnTNDxk6om zmC>MJQS((G-uhP&ZPN^6Ry(Rrvz$XAhg$K8((*`87J)?Ujsv1THp9U~zMz*LJ2W|s(*ZTJ+2yv_eH*%dgVNuT(K!EpdvA^glL-!ujzY3Y z`KD{RAk{+dBc8b1NkgVVuh7c{#ta>ikwf9R&>BXBG@;6@!IJ8s!{^!TOSnoiXhJKq z?$^tc4t>w-N4X8((semr5<}q8VoD}!Pl|ZIk^JZ=leGyf(d(I2BU2>tl34u@7+jql z4N!&y&O_{Zbr!2bT8oPEH#c3eTM8Y6ab=2t-SM_`QpwW~PL!U-RtbW$9TA_Y9`}KQ zIm#;}*G*)&@z!0tS3P?A^WhYQLr zSy4ZZ5rI9~P9E!9?O~2mtyH;!ESE4k4@kzyhIRzCqRn~`#JT5k1Y*8$8zo4k?H~CF z=kwf&U*-m^wM5Lnx-bI|b%lcR0g5_8HsTc`$CD9QTdkZjx~{mG+?Fmpm=>yMB=5rp z!d|Ru`@?G2Kpu)ttD7#&4(`giOjCpi@DuC0ftdE2HAgVQY!X#HSTvYwSZIlvIXwJQ z8|!>2H#uIGlyv;@QWAKhAIV;3HzHTWzLYdyz@Rn3$xF(}6y`f2O2*-W=5m1`Ts3JXDuiYr z6d`uOh7w_AtN~-(cK;qFotu@Cr2}!C4)Mmfbmo~F$bUPd9bZU7p8bTd6>_dmBH53< z4^|H}aUq*qgxnNnJ?$CS$bK(GbLfnWmY8&GM)SB4&z#XOi3IpYi84+{|@ngymx$~Rj(n;X6$p3B%0|6q}h`vw| z5P-LTue1EUBRM<61|}yNC}WG^gs$1N7_|QquUfm;ERxkj(nHF?7$A@fr^X(L0Yd+JlyIbivAQ_WnVN+;*y|^d-o0gj@Sj0@Ll9H0=1@hE$Hta zR2PzZH0j!kKBea;ePh?Jrz9Ko7nOq28iGI}i($3?7&Jc!m;GLB*io;%#<2JUVUyNS z!x!dd5#uN<(@nza%(Q+QY+5y16l%qlK@t)s6jyvV^GzU}5{h^k#n=pC00#k<0GqHun4N7jH*p5NKxwY-`-poyrq98zAIn(Pqelhp@wBZS z;VPUpIZzh2>BSRb$Z?b~p?EPDjb#@KnB}){l5^=Naz&X^lrUaq`pipVbPx&kM1xpN z6F(xQqnZQL23bVMsk6$`?ca%u_*|N#<8zPrmThWVf6KSa&6A2d5O?dgv*@;Cgjp*B zq9km)rsQ-BmlK{>#^X~h*KOtJG(cw&oGPG2kQwhrr;VYA)J|^_Tgrrk@v%jYPrQtt zNfNI58EA5j9B%W{vgy!n`D;ueZJM60hba*peuxnK?;^EQuvlBbfq($AfL4p?fFBY4 zH0I_+=o&hQ&ljK|L&sGS&1sHDVe%tu)bbFl9j zT><}db*{&yjtx=~fNtE&hISi_2$bbgHKcne3!$?U8jyO9f`8uLE93M`HT*Vz6ZRT1~`1F?D!-$WNc;<&((Ib08Ag&yg|t zgjctZts}}?Z4*NkMIsVgJ|ZmJJcPXWHXI8k&Q;t;h5YLKm8n%R?^nsGhnP=8*y={8CBq{b z{Z1z2l0k`Rey6&pI09&?tw5cO;>4>RN@eM;5S9L+n!_|Sv1%ql{6v*EAj?yZ53f0e zGuz;q!pFarb_lP-92?X@yK2iBQ;9w_7OK&>_`#l?oq;sGg&;vunv(hKK&)jBGjxwu z@Kdut>cI;O;%x00?ndE2=bbq|pIxuF6kh^vxsjCt#~RjYlIH>zABUiYp4!%AA4{6OoRsk@aiB5-scca{ zgAc*xCz9H^EL)%*w$84D!Nm3-fZNkzve)G0*kYJ`?d zIpjut2dLm)=AZ34RwGb!v*GfMJf3||p%&~r!JRCSvmq2}EZT|TU?LW<#WEpSedEKH z9rtUHv@iE7LQ_c-f8H1-Znqi5p#pMe90Z!{VAf*dI)stltyRxJvofFk(yti0 zx|9WUkxLZkVJ0Wam1udF5}C2ce5Qug{)O+Ie*AF8Rv1#EQjKet91DYB#y(b#(fqxD z=vSK6#ca?)n&qt?EibeHleq-0r6&V>JLM+Sw|sprhxy8nA5LOrEOzx@et+=rHfShJ zXBp4>%&;4QGXd`*jU>amD8M9P-G!n1X*1*#@TeB03U;X2eat>Nze&YfGYg@L?*?Yu(P`DMIR42wH#Yo+>sAW0hA$p6f!s92m}jI%+zHV@~WpCT;m8=%^DqO zW|QW@yFWsIEu5wBkt~^=L1}fQ&MWCTUWZ%^n+FxEYE&eo_{k&hvMGy1Ca`awgh#=pynJdeU{rREf6`K z((@f%xEN&nCFyJP#M;K$;j{2-z>T|#ZvC_xM`?+X1vDf{lyKwxeBPPRdLkF-l{ z&(J5~U}ZMBvu8z(iVsZBPqjeE3+mAUt{@d`Hbpx#TlcruF$Zq(v+_Gz*1q%Cg0J$b zMWqv)I_|9_JwTh7s6NVxU@S6fZ5rP*(b;?P6W#M|Q{E%HF!*3aq8ZM8My=ByJRL_H zIB|FJLP+-G0rGRa%}pH--cJA`MaG=)el2nma18yxjp$ePRo^pqHhNFtN}b#Yu-G|j zWV6RBb9UZ16LPOPM<0hNk_U1n)~-O>v$k)+5iV1a3$HQSx&#Nahs319%u@A(zX5fD zSVdp$R9X)pb`6ayC_94ho$fEO{b`m?`*5v73IQ%*^kBH6Af!-`iXg>&@Ti`J!j!CN zqZ=tqJ5I;-t+5^@=@Nk)boU~N=edVvmmizr$_7cy*AqEy`naa4JCM)h0g`Batz z0j|PMD9#>RO=h(8sRzt1$QxCWuK5yEEk0YzBLc*B8CA_|tF=SP-u)Du$}6+$f{C~* zYylAlW#yhgHyzX7HR9N!Egb}*7{*O&+yw|Xt1d<%7LsW`dD@@74_EH5Kn7D(jhyKR ztLMrI5&Z5r*J_k>D73H^;gT!1`&99L?U`qv0JX&t)xEWFsTEV@i260l6x2!x_s>cx ziZADsDqDN*uO#2{u1torx59SQ8WH8~Hp^ryB8iiR!+Snt6CWS5B?UWNNYc|k>`BD{ zYp%%pIdp~ixk4jVw^H3+fmGirFLK>JfB9W`WprPYwrcV-Rp8qQaQ1=cGYL(V8K7uZ z?>ThBDUxb!^P3g3P@%`n16g9n@3O0J_ZHc|Sx$3=765keIKkMTW?fE`?l(j>Q(D}8 zQeP{s1fLD^F80G9W}~+%!&E+771NZeI!*9j#63ozC6Cq{T4Y>PkO61fyoOnrTT}-v zSoG#e@#Eu}MUm9d2MyH=&hpcJ%DzrGwM2r8sOqYyKfE#eabL&ktLQo`!@2;cd(xWh zT21{``ca`~=^|5c0}5Ee+#QZCT2T+zi`WXMPq1hKjYA9vn+#WnXU(^~L0GU&@Ke$; zuTt~8$=y3*MW{$X4^_dI9c3Z@s!?)NF4{|P7ITA@HNmcI8oHsVU7EylK>KEm78ma) zzv=g=vvQ9L2@^f9$dhf5kDAN))XgGt=_S~1uW`j{fa{a>hB?roaklqoO^aeS$|15X zLS2;v%Q5}uW{+H!rYDB1Wv=w3f7W!H_)^wjm%UP9D}{n?@+r64IwvOlE1ZG(sx8 zxP0lDg_&q3k5(_$>3AH4sMfaF!*3Qd9t0-HH}GiCxS9Ovett?pgkD5~Jr9ZE_b~^# z@@px>rOE}(h6WKV{1nvaZ8{*FHdl4yLh$n<_Wajh@-}ws^C?X0{-QP*|;bR&Co=D@zEYi&qyMo2H@C8da2rC z<@+vZn_uzIsT&C$g9%}5R|&KL7ArBuumo$#kTltOM#2?LO==v=9-(-pJiebc&}?(k z9t6WY7a?z(Lk{pcnht7Ix`EcCdu?XDw`B0#G12gftNye$S~LKY0hNgAlLarMO=Ehx z`1I;djAMh-67)+g@uy&|bh}bWe0Q0?Z&vUVv>>J8Yz=WqQlzPp1Fn8I%+*V4eBAE? zusO)vcoH|M(>vwgf~qA&;OuG&DyBc9Ipspa@;(A>ioPZpEy=tV2bq8mrVVHArq5^U z{R@**&ZwMh2Hq3aX}jDDEk$fg2@(l1*)Wd>qPW^Hj)T>0-Wvp`t7X#q2X@I8=19_N zDN}0Z_+Yi^6TDyldcxyD$l_tj=Vm5u7>$nZ z^<)jSSGVaVI!{W~yjC+okMRu{T;rFWkeYJgpw||gr{RuJ0;^l6C%Pt&voP(cJ#rer zN0`58?^on)hG`iEC+jch$#)#US-(T{S(W8AnPcEicN_$zI`%m7daOnY-xs&sY;}FC)Yyrd6u9s{NWom+mGt2+hV(rC8#Pz zcYNK#5?|CF-@ia`@=hIGOQ^U6KdAxRLAODx1`Awqja1}EbJiu&TRiP=4n-ZXe~43c z857Upg}*5HqFOb64SYa2*QwA4-&&6!-w3^fVC^IMs^&E{tKt%1$$rk>oVValmdxEY zLUgBo@R_j#n``I0Hm_N^>3Px-#P}GMsK!)hE+bh_!N*{{;r?U6WR%UQgCtYjOyUR-fm)Fz1#Q`O$cqA*CQrT4pC-M84+$g04 z$Z<%t#eKQ1(`*GDHvBjAim5>_l;j6PjDe`&FV`43)CWJzn`-jIG)QszRz7u0{hPy{df+b|8lfD)Sq!8;aufj=wu-HojGV53sOYStR| zGb+>GH29hTC&2uply=Fl<31%9N5lD|+wU&~m|sS}yTg)=aW`r=gpT{*9mUnB(&AywS|~%d z(l3)6kI6A#-P*IiYE$@9UHv#IPWEqXFN>S7PP}_G)SXp8r7*v0s=X0dm|B*wdiTXI z%-Tw)^LTL`-G^?m#~g;q8=p<}t0%rr&}x*;zg#GJ zqU~g9JQLJctDdT0VDZ!>q!Jll75s@26bpqw@MqXZQkB~or|urqc7dE6bz>lXRA86} zI~Y#-(bq8WD@NIc=f~QgiIbi%e*OTmtrBVQ4&m3lXp zi(BY@`7@P!13s^Uy1twfSI%{+sfIyBlBT*yeZ*xxTff{{`@IEPz)uB7e%>0oxT9DF z{qRQoI=@wt;QEmY<7?hp-x%rXBZOvN6``+)be&QS=UoA-6L5NnTCWL)q29gC% zd%M(1&m*zE0vYWt86O)s+tNJw+Ez=TVqSaIS78%`9xBw@;k+=;J~Owq#|dm-qw}sa zizvtY1~d<2nvST4eRX z7Oz!)7EL6Pf&bdPq*f2rwwoWet_^TNJx{~JT5%O_>T33*I#laoFmX?+L~9sEtGS?Htoj->OE7d51ez z?s43UVib0q_tavOp?pr3+FrX6LM<_U{S62Ck2kQp;*Z-evTy5;o6m7T=FNEkGQ0pZ zOpe{Y`4d2$Z{gas%pZ>e-5li~=l&mqpV1n{TNJn^_D_FdjrgAkY5mRm_cupko#`!d zTGxI%CLjYq>+8IK832f5L-?PZkPW)GsB**b?TEZ-{dRQQ{1YqS0zk)`f3hm@03eAi zfw$;_7ywG$5_*ePNC2RdE#6J#qRuhOJS80 zkhqHkRlo__pr-<{?fw~q>Mj*j9uH_^mjRT!`)3dvd;sLP*9HFm6b2T7)^|nUP>MY& zs3yU`X-<3iZ@{TA0F<|f1XVBm7i4{p06&7VUY%a#`ck*E~Nf~Py5twAo&3m6qDQ=Knco|gZo$P_6ASrfhhFp|AoH4 zLCa=u5G6>({6AM9XaxWX9wI^gwgkx>iocx^-3Ea2pFz!9gK7@{Ox?vH6;ZM6|9@@6 z>XV7Ny#<@Qn~go&|Bd8rsxbinr-Q(NI1!t-1!W!)ft-&1yndlz2LQz#Awi;pGLG12 z|MR{7b$UX+Jq?0}fMEMq4gpaZIPD0^@56nw4B~(koe)6e$8i58`yXrJ|Hyti|05&( zcjQ6GR8V3bf8o^=1W=X-!oQS)=iA~rMuMXD{FerL(*8@Y_yRzBCrD6DzW>q~et>`J zDIfs!^^GnA{zK!ujr2GX075xMf*MHtS3?fM`&Y990)Xt^=qAu#I{K9MP1A5n1=X4H z7eLSa&xNC%Q9%V{|Al4GaQ|!g|KsZUpW)l){7wIwgUTg9ZNmCL9O;d!f1Zy^)lttY-EmuCD*Ls0=TtpgKnWo-FO+&mW7kxx<=g>fwml$x0zy4h1{{yI$%}4+M diff --git a/devtools/gradle/gradlew b/devtools/gradle/gradlew index 4f906e0c811fc9..aeb74cbb43e393 100755 --- a/devtools/gradle/gradlew +++ b/devtools/gradle/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,98 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +137,109 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/devtools/gradle/gradlew.bat b/devtools/gradle/gradlew.bat index ac1b06f93825db..6689b85beecde6 100644 --- a/devtools/gradle/gradlew.bat +++ b/devtools/gradle/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 9a75983441c0c7c71b9bdf0f3a7bff319fb904f0 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Sun, 16 Apr 2023 05:37:13 +0300 Subject: [PATCH 026/333] Fix dependencies on Gradle plugin marker artifacts in integration tests Only `pom.xml` is published at `io.quarkus:io.quarkus.gradle.plugin` GA coordinates. Same for `io.quarkus.extension`. See https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers Signed-off-by: Konstantin Gribov --- integration-tests/gradle/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-tests/gradle/pom.xml b/integration-tests/gradle/pom.xml index c85a73c94a0458..6feb49c42c27a0 100644 --- a/integration-tests/gradle/pom.xml +++ b/integration-tests/gradle/pom.xml @@ -27,11 +27,13 @@ io.quarkus io.quarkus.gradle.plugin ${project.version} + pom io.quarkus.extension io.quarkus.extension.gradle.plugin ${project.version} + pom org.assertj From 1b0b5f5b2ac2e94a049cd39606dd4d004d03c227 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Sun, 16 Apr 2023 05:42:11 +0300 Subject: [PATCH 027/333] Avoid accessing Logger via project instance from Gradle execution phase Accessing project from task actions is prohibited when configuration cache is used. If Quarkus plugin is to be compatible with CC eventually it should be avoided. Signed-off-by: Konstantin Gribov --- .../main/java/io/quarkus/gradle/tasks/ImagePush.java | 8 ++++---- .../main/java/io/quarkus/gradle/tasks/ImageTask.java | 12 ++++++------ .../io/quarkus/gradle/tasks/QuarkusBuildTask.java | 2 +- .../java/io/quarkus/gradle/tasks/QuarkusDev.java | 2 +- .../java/io/quarkus/gradle/tasks/QuarkusInfo.java | 5 ++--- .../quarkus/gradle/tasks/QuarkusListPlatforms.java | 8 ++++---- .../io/quarkus/gradle/tasks/QuarkusPlatformTask.java | 2 +- .../java/io/quarkus/gradle/tasks/QuarkusUpdate.java | 3 +-- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java index caae7919b3445c..4fa72ecee5a1c8 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java @@ -33,12 +33,12 @@ public void checkRequiredExtensions() { .collect(Collectors.toList()); if (containerImageExtensions.isEmpty()) { - getProject().getLogger().warn("Task: {} requires a container image extension.", getName()); - getProject().getLogger().warn("Available container image exntesions: [{}]", + getLogger().warn("Task: {} requires a container image extension.", getName()); + getLogger().warn("Available container image exntesions: [{}]", extensions.stream().collect(Collectors.joining(", "))); - getProject().getLogger().warn("To add an extension to the project, you can run one of the commands below:"); + getLogger().warn("To add an extension to the project, you can run one of the commands below:"); extensions.forEach(e -> { - getProject().getLogger().warn("\tgradle addExtension --extensions={}", e); + getLogger().warn("\tgradle addExtension --extensions={}", e); }); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java index 7ecf267341e66e..089c63db4caea4 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java @@ -69,17 +69,17 @@ public void checkRequiredExtensions() { List availableBuidlers = availableBuilders(); Optional missingBuilder = Optional.of(builder()).filter(Predicate.not(availableBuidlers::contains)); missingBuilder.map(builder -> QUARKUS_CONTAINER_IMAGE_PREFIX + builder.name()).ifPresent(missingDependency -> { - getProject().getLogger().warn("Task: {} requires extensions: {}", getName(), missingDependency); - getProject().getLogger().warn("To add the extensions to the project you can run the following command:"); - getProject().getLogger().warn("\tgradle addExtension --extensions={}", missingDependency); + getLogger().warn("Task: {} requires extensions: {}", getName(), missingDependency); + getLogger().warn("To add the extensions to the project you can run the following command:"); + getLogger().warn("\tgradle addExtension --extensions={}", missingDependency); abort("Aborting."); }); if (!missingBuilder.isPresent() && availableBuidlers.isEmpty()) { - getProject().getLogger().warn("Task: {} requires on of extensions: {}", getName(), + getLogger().warn("Task: {} requires on of extensions: {}", getName(), Arrays.stream(Builder.values()).map(Builder::name).collect(Collectors.joining(", ", "[", "]"))); - getProject().getLogger().warn("To add the extensions to the project you can run the following command:"); - getProject().getLogger().warn("\tgradle addExtension --extensions="); + getLogger().warn("To add the extensions to the project you can run the following command:"); + getLogger().warn("\tgradle addExtension --extensions="); abort("Aborting."); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java index a3637e4243f43c..fa954cbed2a590 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -253,7 +253,7 @@ void generateBuild() { } void abort(String message, Object... args) { - getProject().getLogger().warn(message, args); + getLogger().warn(message, args); getProject().getTasks().stream() .filter(t -> t != this) .filter(t -> !t.getState().getExecuted()).forEach(t -> { 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 afcdc38d5960e1..f9f42451bd761d 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 @@ -446,7 +446,7 @@ private QuarkusDevModeLauncher newLauncher() throws Exception { File file = p.toFile(); if (file.exists() && configuredParentFirst.contains(artifact.getKey()) && filesIncludedInClasspath.add(file)) { - getProject().getLogger().debug("Adding dependency {}", file); + getLogger().debug("Adding dependency {}", file); builder.classpathEntry(artifact.getKey(), file); } }); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java index 053c5aca16b146..16d1c5062b199e 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java @@ -30,8 +30,7 @@ public QuarkusInfo() { @TaskAction public void logInfo() { - - getProject().getLogger().warn(getName() + " is experimental, its options and output might change in future versions"); + getLogger().warn(getName() + " is experimental, its options and output might change in future versions"); final QuarkusProject quarkusProject = getQuarkusProject(false); final QuarkusCommandOutcome outcome; @@ -44,7 +43,7 @@ public void logInfo() { throw new GradleException("Failed to collect Quarkus project information", e); } if (outcome.getValue(ProjectInfoCommandHandler.RECOMMENDATIONS_AVAILABLE, false)) { - this.getProject().getLogger().warn( + getLogger().warn( "Non-recommended Quarkus platform BOM and/or extension versions were found. For more details, please, execute 'gradle quarkusUpdate --rectify'"); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java index f5ee805f1e3415..c985939dbcdef1 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java @@ -28,9 +28,9 @@ public void setInstalled(boolean installed) { @TaskAction public void listExtensions() { - getProject().getLogger().info(""); + getLogger().info(""); if (installed) { - getProject().getLogger().info("Imported Quarkus platforms:"); + getLogger().info("Imported Quarkus platforms:"); importedPlatforms().forEach(coords -> { final StringBuilder buf = new StringBuilder(); buf.append(coords.getGroupId()).append(":") @@ -40,13 +40,13 @@ public void listExtensions() { messageWriter().info(buf.toString()); }); } else { - getProject().getLogger().info("Available Quarkus platforms:"); + getLogger().info("Available Quarkus platforms:"); try { new ListPlatforms(getQuarkusProject(installed)).execute(); } catch (Exception e) { throw new GradleException("Unable to list platforms", e); } } - getProject().getLogger().info(""); + getLogger().info(""); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java index 9c612cb4e1780d..5bc34c46318bd1 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java @@ -140,7 +140,7 @@ protected QuarkusProject getQuarkusProject(boolean limitExtensionsToImportedPlat } protected GradleMessageWriter messageWriter() { - return new GradleMessageWriter(getProject().getLogger()); + return new GradleMessageWriter(getLogger()); } protected static URL toURL(String url) { diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java index 20013bd73b3fe8..c47654b99d7018 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java @@ -115,8 +115,7 @@ public QuarkusUpdate() { @TaskAction public void logUpdates() { - - getProject().getLogger().warn(getName() + " is experimental, its options and output might change in future versions"); + getLogger().warn(getName() + " is experimental, its options and output might change in future versions"); final QuarkusProject quarkusProject = getQuarkusProject(false); final ExtensionCatalog targetCatalog; try { From cd0c04cac892bce9251567faeb7331c9a5bbe3dd Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Sun, 16 Apr 2023 06:05:10 +0300 Subject: [PATCH 028/333] Add bcpkix-jdk18on alongside quarkus-kubernetes-client Fixes `integration-tests/gradle` when `.kube/config` has ECDSA keys/certs Signed-off-by: Konstantin Gribov --- .../resources/basic-java-library-module/library/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/gradle/src/main/resources/basic-java-library-module/library/build.gradle b/integration-tests/gradle/src/main/resources/basic-java-library-module/library/build.gradle index a13ebe4b69f88f..f2164ce02a07f8 100644 --- a/integration-tests/gradle/src/main/resources/basic-java-library-module/library/build.gradle +++ b/integration-tests/gradle/src/main/resources/basic-java-library-module/library/build.gradle @@ -14,6 +14,7 @@ repositories { dependencies { api enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") api 'io.quarkus:quarkus-kubernetes-client' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.72' } compileJava { From 0014b6411ffa2ca813c4956a2564b195e5a54622 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Sun, 16 Apr 2023 08:41:08 +0300 Subject: [PATCH 029/333] Allow overwriting resource files in TestResourcesInBuildStepsTest Signed-off-by: Konstantin Gribov --- .../main/java/org/acme/ext/deployment/AcmeExtProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration-tests/gradle/src/main/resources/test-resources-in-build-steps/deployment/src/main/java/org/acme/ext/deployment/AcmeExtProcessor.java b/integration-tests/gradle/src/main/resources/test-resources-in-build-steps/deployment/src/main/java/org/acme/ext/deployment/AcmeExtProcessor.java index 18b2bc4bfdc297..44cee1ad30eabc 100644 --- a/integration-tests/gradle/src/main/resources/test-resources-in-build-steps/deployment/src/main/java/org/acme/ext/deployment/AcmeExtProcessor.java +++ b/integration-tests/gradle/src/main/resources/test-resources-in-build-steps/deployment/src/main/java/org/acme/ext/deployment/AcmeExtProcessor.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; class AcmeExtProcessor { @@ -23,9 +24,9 @@ FeatureBuildItem feature(ApplicationArchivesBuildItem appArchivesBuildItem, final Path output = p.getParent().getParent().getParent().resolve(launchModeBuildItem.getLaunchMode() + "-" + p.getFileName()); try { - Files.copy(p, output); + Files.copy(p, output, StandardCopyOption.REPLACE_EXISTING); } catch(IOException e) { - throw new IllegalStateException("Failed to copy " + p + " to " + output); + throw new IllegalStateException("Failed to copy " + p + " to " + output, e); } return new FeatureBuildItem(FEATURE); } From 3505e5761b375301c09860956bda72273996c244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crocquesel?= <88554524+scrocquesel@users.noreply.github.com> Date: Sun, 16 Apr 2023 12:24:28 +0200 Subject: [PATCH 030/333] Replace remaining references to bcX-jdk150 --- docs/src/main/asciidoc/kubernetes-client.adoc | 2 +- docs/src/main/asciidoc/security-customization.adoc | 4 ++-- docs/src/main/asciidoc/stork-kubernetes.adoc | 2 +- .../client/runtime/graal/CertUtilsSubstitutions.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/kubernetes-client.adoc b/docs/src/main/asciidoc/kubernetes-client.adoc index afd2d0653debeb..3e8074bc4d908f 100644 --- a/docs/src/main/asciidoc/kubernetes-client.adoc +++ b/docs/src/main/asciidoc/kubernetes-client.adoc @@ -340,7 +340,7 @@ Please note that if you would like to use Elliptic Curve keys with Kubernetes Cl [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -implementation("org.bouncycastle:bcpkix-jdk15on") +implementation("org.bouncycastle:bcpkix-jdk18on") ---- Note that internally an `org.bouncycastle.jce.provider.BouncyCastleProvider` provider will be registered if it has not already been registered. diff --git a/docs/src/main/asciidoc/security-customization.adoc b/docs/src/main/asciidoc/security-customization.adoc index 5483addf7a19f1..4bf085eaacf8ab 100644 --- a/docs/src/main/asciidoc/security-customization.adoc +++ b/docs/src/main/asciidoc/security-customization.adoc @@ -424,7 +424,7 @@ and add the BouncyCastle provider dependency: [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -implementation("org.bouncycastle:bcprov-jdk15on") +implementation("org.bouncycastle:bcprov-jdk18on") ---- [[bouncy-castle-jsse]] @@ -459,7 +459,7 @@ and add the BouncyCastle TLS dependency: [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -implementation("org.bouncycastle:bctls-jdk15on") +implementation("org.bouncycastle:bctls-jdk18on") ---- [[bouncy-castle-fips]] diff --git a/docs/src/main/asciidoc/stork-kubernetes.adoc b/docs/src/main/asciidoc/stork-kubernetes.adoc index 0572cc59557260..ba587d054a9e16 100644 --- a/docs/src/main/asciidoc/stork-kubernetes.adoc +++ b/docs/src/main/asciidoc/stork-kubernetes.adoc @@ -432,7 +432,7 @@ kubectl apply -f target/kubernetes/kubernetes.yml -n=development [NOTE] ==== -Please note that if you use Elliptic Curve keys with Stork and are getting exceptions like `java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider`, then adding a BouncyCastle PKIX dependency (`org.bouncycastle:bcpkix-jdk15on`) is required. +Please note that if you use Elliptic Curve keys with Stork and are getting exceptions like `java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider`, then adding a BouncyCastle PKIX dependency (`org.bouncycastle:bcpkix-jdk18on`) is required. Note that internally an `org.bouncycastle.jce.provider.BouncyCastleProvider` provider will be registered if it has not already been registered. diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/graal/CertUtilsSubstitutions.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/graal/CertUtilsSubstitutions.java index 0c9402259b0871..dd884d8257ac5f 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/graal/CertUtilsSubstitutions.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/graal/CertUtilsSubstitutions.java @@ -13,6 +13,6 @@ public final class CertUtilsSubstitutions { @Substitute static PrivateKey handleECKey(InputStream keyInputStream) throws IOException { throw new RuntimeException( - "EC Keys are not supported when using the native binary, please add the org.bouncycastle:bcpkix-jdk15on dependency"); + "EC Keys are not supported when using the native binary, please add the org.bouncycastle:bcpkix-jdk18on dependency"); } } From e184046bda9ecddf1d20dd9fe42fb9be7ca5d812 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Mon, 17 Apr 2023 06:50:54 +1000 Subject: [PATCH 031/333] Dev UI make sure to work on custom http root Signed-off-by: Phillip Kruger --- bom/application/pom.xml | 2 +- .../dev-ui/qwc-kubernetes-manifest.js | 19 +++---- .../deployment/BuildTimeContentProcessor.java | 8 ++- .../devui/deployment/DevUIProcessor.java | 16 +++--- .../dev-ui-templates/build-time/index.html | 2 +- .../resources/dev-ui/qui/qui-code-block.js | 50 +++++++++++++++++++ .../resources/dev-ui/qwc/qwc-dev-services.js | 8 ++- .../resources/dev-ui/qwc/qwc-external-page.js | 33 ++++++------ .../quarkus/devui/runtime/DevUIRecorder.java | 8 +-- .../quarkus/devui/runtime/MvnpmHandler.java | 6 ++- 10 files changed, 105 insertions(+), 47 deletions(-) create mode 100644 extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-code-block.js diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 4a9dc0075cb59b..a6a5c8374d208f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -220,7 +220,7 @@ 0.8.9 1.0.0 - 1.0.7 + 1.0.8 24.0.2 2.7.2 3.3.1 diff --git a/extensions/kubernetes/vanilla/deployment/src/main/resources/dev-ui/qwc-kubernetes-manifest.js b/extensions/kubernetes/vanilla/deployment/src/main/resources/dev-ui/qwc-kubernetes-manifest.js index ae3c64d3954e3b..6c33ed70fb676e 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/resources/dev-ui/qwc-kubernetes-manifest.js +++ b/extensions/kubernetes/vanilla/deployment/src/main/resources/dev-ui/qwc-kubernetes-manifest.js @@ -1,16 +1,12 @@ import { LitElement, html, css} from 'lit'; import { JsonRpc } from 'jsonrpc'; -import { until } from 'lit/directives/until.js'; -import { observeState } from 'lit-element-state'; -import { themeState } from 'theme-state'; -import '@vanillawc/wc-codemirror'; -import '@vanillawc/wc-codemirror/mode/yaml/yaml.js'; +import 'qui-code-block'; import '@vaadin/icon'; import '@vaadin/tabs'; import '@vaadin/tabsheet'; import '@vaadin/progress-bar'; -export class QwcKubernetesManifest extends observeState(LitElement) { +export class QwcKubernetesManifest extends LitElement { jsonRpc = new JsonRpc(this); @@ -101,13 +97,10 @@ export class QwcKubernetesManifest extends observeState(LitElement) { let yaml = this._manifests.get(key); return html`
- - - - + +
`; } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java index 8c86ff8afb119e..78074c4e63ed48 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java @@ -69,6 +69,8 @@ InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBu internalImportMapBuildItem.add("qui/", contextRoot + "qui/"); internalImportMapBuildItem.add("qui-badge", contextRoot + "qui/qui-badge.js"); internalImportMapBuildItem.add("qui-alert", contextRoot + "qui/qui-alert.js"); + internalImportMapBuildItem.add("qui-code-block", contextRoot + "qui/qui-code-block.js"); + // Echarts internalImportMapBuildItem.add("echarts/", contextRoot + "echarts/"); internalImportMapBuildItem.add("echarts-gauge-grade", contextRoot + "echarts/echarts-gauge-grade.js"); @@ -231,13 +233,15 @@ QuteTemplateBuildItem createIndexHtmlTemplate( Map importMap = importMapBuildItem.getImportMap(); aggregator.addMappings(importMap); } - String importmap = aggregator.aggregateAsJson(); + String importmap = aggregator.aggregateAsJson(nonApplicationRootPathBuildItem.getNonApplicationRootPath()); aggregator.reset(); String themeVars = themeVarsBuildItem.getTemplateValue(); - String contextRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath() + DEV_UI + SLASH; + String nonApplicationRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath(); + String contextRoot = nonApplicationRoot + DEV_UI + SLASH; Map data = Map.of( + "nonApplicationRoot", nonApplicationRoot, "contextRoot", contextRoot, "importmap", importmap, "themeVars", themeVars); diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java index 53edd36dc8eff0..848f1969df0f83 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java @@ -193,15 +193,17 @@ void registerDevUiHandlers( } // Static mvnpm jars - routeProducer.produce(RouteBuildItem.builder() - .route("/_static" + SLASH_ALL) - .handler(recorder.mvnpmHandler(mvnpmBuildItem.getMvnpmJars())) - .build()); + String contextRoot = nonApplicationRootPathBuildItem.getNonApplicationRootPath(); + routeProducer.produce( + nonApplicationRootPathBuildItem.routeBuilder() + .route("_static" + SLASH_ALL) + .handler(recorder.mvnpmHandler(contextRoot, mvnpmBuildItem.getMvnpmJars())) + .build()); // Redirect /q/dev -> /q/dev-ui - routeProducer.produce(RouteBuildItem.builder() - .route("/q/dev") - .handler(recorder.redirect()) + routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() + .route("dev") + .handler(recorder.redirect(contextRoot)) .build()); } diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui-templates/build-time/index.html b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui-templates/build-time/index.html index 1121d8880ab795..d4535e3ddd9319 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui-templates/build-time/index.html +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui-templates/build-time/index.html @@ -5,7 +5,7 @@ - + + `; + } + } + +} + +customElements.define('qui-code-block', QuiCodeBlock); diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js index 647d99349d61be..f54e9d81a5ab02 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js @@ -2,6 +2,7 @@ import {LitElement, html, css} from 'lit'; import {devServices} from 'devui-data'; import '@vaadin/vertical-layout'; import '@vaadin/icon'; +import 'qui-code-block'; /** * This component shows the Dev Services Page @@ -87,7 +88,7 @@ export class QwcDevServices extends LitElement { You do not have any Dev Services running. Read more about Dev Services

- ` + `; } } @@ -121,7 +122,10 @@ export class QwcDevServices extends LitElement { let properties = ''.concat(...list); return html`Config:
-
${properties.trim()}
+ +
`; } } diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js index c7cfd0ab7adc33..a6765e54b04639 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js @@ -1,18 +1,13 @@ import { LitElement, html, css} from 'lit'; -import { until } from 'lit/directives/until.js'; import { RouterController } from 'router-controller'; -import { observeState } from 'lit-element-state'; -import { themeState } from 'theme-state'; -import '@vanillawc/wc-codemirror'; -import '@vanillawc/wc-codemirror/mode/yaml/yaml.js'; -import '@vanillawc/wc-codemirror/mode/properties/properties.js'; -import '@vanillawc/wc-codemirror/mode/javascript/javascript.js'; import '@vaadin/icon'; +import 'qui-code-block'; +import '@vaadin/progress-bar'; /** * This component loads an external page */ -export class QwcExternalPage extends observeState(LitElement) { +export class QwcExternalPage extends LitElement { routerController = new RouterController(this); static styles = css` @@ -52,7 +47,15 @@ export class QwcExternalPage extends observeState(LitElement) { } render() { - return html`${until(this._loadExternal(), html`Loading with ${themeState.theme.name} theme`)}`; + if(this._mode){ + return this._loadExternal(); + }else { + return html` +
+
Loading content...
+ +
`; + } } _autoDetectMimeType(){ @@ -89,17 +92,17 @@ export class QwcExternalPage extends observeState(LitElement) { height='100%'>
`; } else { + let currentPath = window.location.pathname; + currentPath = currentPath.substring(0, currentPath.indexOf('/dev')); return html`
Download - - - + +
`; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/DevUIRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/DevUIRecorder.java index 6702ee7c3ce43c..4b8eca0d506887 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/DevUIRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/DevUIRecorder.java @@ -90,16 +90,16 @@ public Handler vaadinRouterHandler(String basePath) { return new VaadinRouterHandler(basePath); } - public Handler mvnpmHandler(Set mvnpmJarFiles) { - return new MvnpmHandler(mvnpmJarFiles); + public Handler mvnpmHandler(String root, Set mvnpmJarFiles) { + return new MvnpmHandler(root, mvnpmJarFiles); } - public Handler redirect() { + public Handler redirect(String contextRoot) { return new Handler() { @Override public void handle(RoutingContext rc) { // 308 because we also want to redirect other HTTP Methods (and not only GET). - rc.response().putHeader("Location", "/q/dev-ui").setStatusCode(308).end(); + rc.response().putHeader("Location", contextRoot + "dev-ui").setStatusCode(308).end(); } }; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/MvnpmHandler.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/MvnpmHandler.java index 9c8f99e8f5a1ef..5d68e31d9c382c 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/MvnpmHandler.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/MvnpmHandler.java @@ -18,15 +18,17 @@ public class MvnpmHandler implements Handler { private final URLClassLoader mvnpmLoader; + private final String root; - public MvnpmHandler(Set mvnpmJars) { + public MvnpmHandler(String root, Set mvnpmJars) { + this.root = root; this.mvnpmLoader = new URLClassLoader(mvnpmJars.toArray(new URL[] {})); } @Override public void handle(RoutingContext event) { + String fullPath = event.normalizedPath().replaceFirst(root, SLASH); // Find the "filename" and see if it has a file extension - String fullPath = event.normalizedPath(); String parts[] = fullPath.split(SLASH); String fileName = parts[parts.length - 1]; From 4a6a33009330f59d94708d7de11d313429d637e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 22:03:51 +0000 Subject: [PATCH 032/333] Bump testcontainers.version from 1.17.6 to 1.18.0 Bumps `testcontainers.version` from 1.17.6 to 1.18.0. Updates `testcontainers-bom` from 1.17.6 to 1.18.0 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) Updates `testcontainers` from 1.17.6 to 1.18.0 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers-bom dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.testcontainers:testcontainers dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 4 ++-- .../quarkus/mongodb/deployment/DevServicesMongoProcessor.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 4a9dc0075cb59b..857a61b33a52d0 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -204,8 +204,8 @@ 1.11.1 2.4.2.Final 0.1.15.Final - 1.17.6 - 3.2.13 + 1.18.0 + 3.3.0 1.3.0 2.7 2.4 diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java index 7f288183ffdc46..b751aa3a96c7fc 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java @@ -272,7 +272,7 @@ private QuarkusMongoDBContainer(DockerImageName dockerImageName, Integer fixedEx } @Override - protected void configure() { + public void configure() { super.configure(); if (useSharedNetwork) { From 52e4b44f8d17dd10ff16fc22150e877730fef176 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Mon, 17 Apr 2023 10:04:07 +1000 Subject: [PATCH 033/333] Dev UI: allow status to be a list Signed-off-by: Phillip Kruger --- .../devui/deployment/DevUIProcessor.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java index 53edd36dc8eff0..446dad820c3a3a 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Scanner; +import java.util.stream.Collectors; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -373,7 +374,7 @@ void getAllExtensions(List cardPageBuildItems, List inactiveExtensions = new ArrayList<>(); List sectionMenuExtensions = new ArrayList<>(); List footerTabExtensions = new ArrayList<>(); - ClassPathUtils.consumeAsPaths(YAML_FILE, p -> { + ClassPathUtils.consumeAsPaths(YAML_FILE, (Path p) -> { try { Extension extension = new Extension(); final String extensionYaml; @@ -410,7 +411,7 @@ void getAllExtensions(List cardPageBuildItems, } extension.setCategories((List) metaData.getOrDefault(CATEGORIES, null)); - extension.setStatus((String) metaData.getOrDefault(STATUS, null)); + extension.setStatus(collectionToString(metaData, STATUS)); extension.setBuiltWith((String) metaData.getOrDefault(BUILT_WITH, null)); extension.setConfigFilter((List) metaData.getOrDefault(CONFIG, null)); extension.setExtensionDependencies((List) metaData.getOrDefault(EXTENSION_DEPENDENCIES, null)); @@ -506,6 +507,21 @@ void getAllExtensions(List cardPageBuildItems, } } + private String collectionToString(Map metaData, String key) { + Object value = metaData.getOrDefault(key, null); + if (value == null) { + return null; + } else if (String.class.isAssignableFrom(value.getClass())) { + return (String) value; + } else if (List.class.isAssignableFrom(value.getClass())) { + List values = (List) value; + return (String) values.stream() + .map(n -> String.valueOf(n)) + .collect(Collectors.joining(", ")); + } + return String.valueOf(value); + } + private void produceResources(String artifactId, BuildProducer webJarBuildProducer, BuildProducer devUIWebJarProducer) { From 6942710abead4275efdb532b48265fa55b4810e7 Mon Sep 17 00:00:00 2001 From: "Pavel.Vervenko" Date: Mon, 3 Apr 2023 18:09:07 +0200 Subject: [PATCH 034/333] #24192 put AwsRequestId to logger MDC --- .../amazon/lambda/runtime/AbstractLambdaPollLoop.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java index f0e0750d70440c..c967b6064c5e3b 100644 --- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java +++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java @@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.jboss.logging.Logger; +import org.jboss.logging.MDC; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -32,6 +33,7 @@ public abstract class AbstractLambdaPollLoop { private final ObjectReader clientCtxReader; private final LaunchMode launchMode; private static final String LAMBDA_TRACE_HEADER_PROP = "com.amazonaws.xray.traceHeader"; + private static final String MDC_AWS_REQUEST_ID_KEY = "AWSRequestId"; public AbstractLambdaPollLoop(ObjectMapper objectMapper, ObjectReader cognitoIdReader, ObjectReader clientCtxReader, LaunchMode launchMode) { @@ -91,6 +93,9 @@ public void run() { } try { String requestId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID); + if (requestId != null) { + MDC.put(MDC_AWS_REQUEST_ID_KEY, requestId); + } if (requestConnection.getResponseCode() != 200) { // connection should be closed by finally clause continue; @@ -164,6 +169,7 @@ public void run() { } return; } finally { + MDC.remove(MDC_AWS_REQUEST_ID_KEY); try { requestConnection.getInputStream().close(); } catch (IOException ignored) { From 37eacbdd67490357182453d06fdf5b92a37fc541 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 7 Apr 2023 16:18:05 +0200 Subject: [PATCH 035/333] Switch Hibernate Validator extension to @ConfigMapping --- .../HibernateValidatorBuildTimeConfig.java | 35 ++++++++++--------- .../runtime/HibernateValidatorRecorder.java | 12 +++---- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorBuildTimeConfig.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorBuildTimeConfig.java index 9c65d18486ebd9..950c2fda895a9e 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorBuildTimeConfig.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorBuildTimeConfig.java @@ -4,36 +4,39 @@ import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; +import io.quarkus.runtime.annotations.ConfigDocDefault; import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +@ConfigMapping(prefix = "quarkus.hibernate-validator") @ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) -public class HibernateValidatorBuildTimeConfig { +public interface HibernateValidatorBuildTimeConfig { /** * Enable the fail fast mode. When fail fast is enabled the validation * will stop on the first constraint violation detected. */ - @ConfigItem(defaultValue = "false") - public boolean failFast; + @WithDefault("false") + boolean failFast(); /** * Method validation. */ @ConfigDocSection - public HibernateValidatorMethodBuildTimeConfig methodValidation; + HibernateValidatorMethodBuildTimeConfig methodValidation(); /** * Expression Language. */ @ConfigDocSection - public HibernateValidatorExpressionLanguageBuildTimeConfig expressionLanguage; + HibernateValidatorExpressionLanguageBuildTimeConfig expressionLanguage(); @ConfigGroup - public static class HibernateValidatorMethodBuildTimeConfig { + public interface HibernateValidatorMethodBuildTimeConfig { /** * Define whether overriding methods that override constraints should throw a {@code ConstraintDefinitionException}. @@ -47,8 +50,8 @@ public static class HibernateValidatorMethodBuildTimeConfig { * This would pose a strengthening of preconditions to be fulfilled by the caller." * */ - @ConfigItem(defaultValue = "false") - public boolean allowOverridingParameterConstraints; + @WithDefault("false") + boolean allowOverridingParameterConstraints(); /** * Define whether parallel methods that define constraints should throw a {@code ConstraintDefinitionException}. The @@ -63,8 +66,8 @@ public static class HibernateValidatorMethodBuildTimeConfig { * This again is to avoid an unexpected strengthening of preconditions to be fulfilled by the caller." * */ - @ConfigItem(defaultValue = "false") - public boolean allowParameterConstraintsOnParallelMethods; + @WithDefault("false") + boolean allowParameterConstraintsOnParallelMethods(); /** * Define whether more than one constraint on a return value may be marked for cascading validation are allowed. @@ -79,12 +82,12 @@ public static class HibernateValidatorMethodBuildTimeConfig { * overridden method of the super type or interface." * */ - @ConfigItem(defaultValue = "false") - public boolean allowMultipleCascadedValidationOnReturnValues; + @WithDefault("false") + boolean allowMultipleCascadedValidationOnReturnValues(); } @ConfigGroup - public static class HibernateValidatorExpressionLanguageBuildTimeConfig { + public interface HibernateValidatorExpressionLanguageBuildTimeConfig { /** * Configure the Expression Language feature level for constraints, allowing the selection of @@ -97,7 +100,7 @@ public static class HibernateValidatorExpressionLanguageBuildTimeConfig { * created programmatically in validator implementations. * The feature level for those can only be configured directly in the validator implementation. */ - @ConfigItem(defaultValueDocumentation = "bean-properties") - public Optional constraintExpressionFeatureLevel; + @ConfigDocDefault("bean-properties") + Optional constraintExpressionFeatureLevel(); } } diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java index 00564dc90488dd..5888318baae135 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java @@ -82,9 +82,9 @@ public void created(BeanContainer container) { .defaultLocale(localesBuildTimeConfig.defaultLocale) .beanMetaDataClassNormalizer(new ArcProxyBeanMetaDataClassNormalizer()); - if (hibernateValidatorBuildTimeConfig.expressionLanguage.constraintExpressionFeatureLevel.isPresent()) { + if (hibernateValidatorBuildTimeConfig.expressionLanguage().constraintExpressionFeatureLevel().isPresent()) { configuration.constraintExpressionLanguageFeatureLevel( - hibernateValidatorBuildTimeConfig.expressionLanguage.constraintExpressionFeatureLevel.get()); + hibernateValidatorBuildTimeConfig.expressionLanguage().constraintExpressionFeatureLevel().get()); } InstanceHandle configuredConstraintValidatorFactory = Arc.container() @@ -127,13 +127,13 @@ public void created(BeanContainer container) { // Hibernate Validator-specific configuration - configuration.failFast(hibernateValidatorBuildTimeConfig.failFast); + configuration.failFast(hibernateValidatorBuildTimeConfig.failFast()); configuration.allowOverridingMethodAlterParameterConstraint( - hibernateValidatorBuildTimeConfig.methodValidation.allowOverridingParameterConstraints); + hibernateValidatorBuildTimeConfig.methodValidation().allowOverridingParameterConstraints()); configuration.allowParallelMethodsDefineParameterConstraints( - hibernateValidatorBuildTimeConfig.methodValidation.allowParameterConstraintsOnParallelMethods); + hibernateValidatorBuildTimeConfig.methodValidation().allowParameterConstraintsOnParallelMethods()); configuration.allowMultipleCascadedValidationOnReturnValues( - hibernateValidatorBuildTimeConfig.methodValidation.allowMultipleCascadedValidationOnReturnValues); + hibernateValidatorBuildTimeConfig.methodValidation().allowMultipleCascadedValidationOnReturnValues()); InstanceHandle configuredScriptEvaluatorFactory = Arc.container() .instance(ScriptEvaluatorFactory.class); From 56a75ab1b761d128fbd1c19d887f81c2776ce206 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 11 Apr 2023 09:07:34 +0100 Subject: [PATCH 036/333] Upgrade Oracle JDBC driver to 23.2.0.0 --- bom/application/pom.xml | 2 +- .../deployment/OracleMetadataOverrides.java | 29 ++++++++++++------- .../jpaoracle/LdapUrlTestEndpoint.java | 2 +- .../it/jpa/oracle/JPAFunctionalityTest.java | 5 +++- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 857a61b33a52d0..98732de3a28584 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -132,7 +132,7 @@ 8.0.30 12.2.0.jre11 1.6.7 - 21.5.0.0 + 23.2.0.0 10.14.2.0 11.5.8.0 1.2.6 diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java index 5a2f4ddf89536f..e614e0b9ccfe8a 100644 --- a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java +++ b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java @@ -7,6 +7,7 @@ import io.quarkus.deployment.builditem.RemovedResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; @@ -48,7 +49,7 @@ public final class OracleMetadataOverrides { void build(BuildProducer reflectiveClass) { //This is to match the Oracle metadata (which we excluded so that we can apply fixes): reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.internal.ACProxyable") - .methods().build()); + .constructors().methods().build()); reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.driver.T4CDriverExtension") .build()); reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.driver.T2CDriverExtension") @@ -65,21 +66,24 @@ void build(BuildProducer reflectiveClass) { .build()); reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.net.ano.SupervisorService") .build()); - reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.driver.Message11") - .build()); + //This is listed in the original metadata, but it doesn't actually exist: + // reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.driver.Message11") + // .build()); reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.sql.TypeDescriptor") - .fields().build()); + .constructors().fields().build()); reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.sql.TypeDescriptorFactory") - .build()); + .constructors().build()); reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.sql.AnyDataFactory") - .build()); + .constructors().build()); reflectiveClass .produce(ReflectiveClassBuildItem.builder("com.sun.rowset.providers.RIOptimisticProvider") .build()); - reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.logging.annotations.Supports") - .methods().build()); - reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.logging.annotations.Feature") - .methods().build()); + //This is listed in the original metadata, but it doesn't actually exist: + // reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.logging.annotations.Supports") + // .constructors().methods().build()); + //This is listed in the original metadata, but it doesn't actually exist: + // reflectiveClass.produce(ReflectiveClassBuildItem.builder("oracle.jdbc.logging.annotations.Feature") + // .constructors().methods().build()); } @BuildStep @@ -155,4 +159,9 @@ RemovedResourceBuildItem enhancedCharsetSubstitutions() { Collections.singleton("oracle/nativeimage/CharacterSetFeature.class")); } + @BuildStep + NativeImageResourceBundleBuildItem additionalResourceBundles() { + return new NativeImageResourceBundleBuildItem("oracle.net.mesg.NetErrorMessages"); + } + } diff --git a/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/LdapUrlTestEndpoint.java b/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/LdapUrlTestEndpoint.java index 3d3b174e338187..bf1bc072350b97 100644 --- a/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/LdapUrlTestEndpoint.java +++ b/integration-tests/jpa-oracle/src/main/java/io/quarkus/example/jpaoracle/LdapUrlTestEndpoint.java @@ -30,7 +30,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO resp.getWriter().write(output); } catch (Exception e) { - resp.getWriter().write("An error occurred while attempting ldap operations"); + e.printStackTrace(resp.getWriter()); } } diff --git a/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java b/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java index 3f2daa87ceac00..d69986b1c40386 100644 --- a/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java +++ b/integration-tests/jpa-oracle/src/test/java/io/quarkus/it/jpa/oracle/JPAFunctionalityTest.java @@ -10,7 +10,10 @@ /** * Test connecting Hibernate ORM to Oracle database. * Can quickly start a matching database with: - * docker run -it --rm=true --name ORCLCDB -p 1521:1521 store/oracle/database-enterprise:12.2.0.1-slim + * + *
+ * docker run --rm=true --name=HibernateTestingOracle -p 1521:1521 -e ORACLE_PASSWORD=hibernate_orm_test docker.io/gvenzl/oracle-free:23-slim-faststart
+ * 
*/ @QuarkusTest public class JPAFunctionalityTest { From 506e3e218e36e63293288b87b989f7e8b01f4806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Mon, 17 Apr 2023 11:06:59 +0200 Subject: [PATCH 037/333] Qute: Allow to specify default bundle file without language tag --- docs/src/main/asciidoc/qute-reference.adoc | 3 + .../deployment/MessageBundleProcessor.java | 25 ++-- .../DefaultFileBundleLocaleMergeTest.java | 111 ++++++++++++++++++ .../DefaultFileDefaultBundleNameTest.java | 51 ++++++++ .../i18n/DefaultFileDuplicateFoundTest.java | 52 ++++++++ 5 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileBundleLocaleMergeTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDefaultBundleNameTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDuplicateFoundTest.java diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 791aea44d13e4a..94cb42d973fcd3 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -2596,6 +2596,9 @@ public interface GermanAppMessages extends AppMessages { Message bundle files must be encoded in _UTF-8_. The file name consists of the relevant bundle name (e.g. `msg`) and underscore followed by a language tag (IETF; e.g. `en-US`). +The language tag may be omitted, in which case the language tag of the default bundle locale is used. +For example, if bundle `msg` has default locale `en`, then `msg.properties` is going to be treated as `msg_en.properties`. +If both `msg.properties` and `msg_en.properties` are detected, an exception is thrown and build fails. The file format is very simple: each line represents either a key/value pair with the equals sign used as a separator or a comment (line starts with `#`). Blank lines are ignored. Keys are _mapped to method names_ from the corresponding message bundle interface. diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 96f61624605349..0cdcec03d9aa7f 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -206,13 +206,24 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv for (Path messageFile : messageFiles) { String fileName = messageFile.getFileName().toString(); if (fileName.startsWith(name)) { - // msg_en.txt -> en - // msg_Views_Index_cs.properties -> cs - // msg_Views_Index_cs-CZ.properties -> cs-CZ - // msg_Views_Index_cs_CZ.properties -> cs_CZ - String locale = fileName.substring(name.length() + 1, fileName.indexOf('.')); - // Support resource bundle naming convention - locale = locale.replace('_', '-'); + final String locale; + int postfixIdx = fileName.indexOf('.'); + if (postfixIdx == name.length()) { + + // msg.txt -> use bundle default locale + locale = defaultLocale; + } else { + + locale = fileName + // msg_en.txt -> en + // msg_Views_Index_cs.properties -> cs + // msg_Views_Index_cs-CZ.properties -> cs-CZ + // msg_Views_Index_cs_CZ.properties -> cs_CZ + .substring(name.length() + 1, postfixIdx) + // Support resource bundle naming convention + .replace('_', '-'); + } + ClassInfo localizedInterface = localeToInterface.get(locale); if (defaultLocale.equals(locale) || localizedInterface != null) { // both file and interface exist for one locale, therefore we need to merge them diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileBundleLocaleMergeTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileBundleLocaleMergeTest.java new file mode 100644 index 00000000000000..88f3f216093874 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileBundleLocaleMergeTest.java @@ -0,0 +1,111 @@ +package io.quarkus.qute.deployment.i18n; + +import static io.quarkus.qute.i18n.MessageBundle.DEFAULT_NAME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.i18n.Localized; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultFileBundleLocaleMergeTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Messages.class, EnMessages.class, DeMessages.class) + .addAsResource(new StringAsset("hello_world=Hi!"), "messages/msg_en.properties") + .addAsResource(new StringAsset("farewell=Abschied\ngoodbye=Freilos"), "messages/msg_de.properties") + .addAsResource(new StringAsset("goodbye=Mej se!\nfarewell=Sbohem!\nhello_world=Zdravim svete!"), + "messages/msg.properties")) + .overrideConfigKey("quarkus.default-locale", "cs"); + + @Localized("en") + Messages enMessages; + + @Localized("de") + Messages deMessages; + + @Inject + Messages csMessages; + + /** + * Default message template method is not overridden and message was set with {@link Message#value()}. + */ + @Test + void testDefaultFromAnnotationIsUsedAsFallback() { + assertEquals("Nazdar!", enMessages.hello()); + } + + /** + * Default message template method is not overridden and message was set in default 'msg.properties' file. + */ + @Test + void testDefaultFromFileIsUsedAsFallback() { + assertEquals("Mej se!", enMessages.goodbye()); + } + + /** + * Localized message template method is provided without {@link Message#value()} + */ + @Test + void testDefaultIsUsedAsFallback2() { + assertEquals("Greetings!", enMessages.greetings()); + } + + @Test + void testLocalizedFileIsMerged() { + assertEquals("Freilos", deMessages.goodbye()); + } + + /** + * Default message set with {@link Message#value()} has priority over message from 'msg.properties'. + */ + @Test + void testDefaultInterfaceHasPriority() { + assertEquals("Ahoj svete!", csMessages.hello_world()); + } + + @Test + void testBothDefaultAndLocalizedFromFile() { + assertEquals("Abschied", deMessages.farewell()); + } + + @MessageBundle(DEFAULT_NAME) + public interface Messages { + + @Message("Ahoj svete!") + String hello_world(); + + @Message("Nazdar!") + String hello(); + + String goodbye(); + + @Message("Greetings!") + String greetings(); + + @Message + String farewell(); + } + + @Localized("en") + public interface EnMessages extends Messages { + + @Message + String greetings(); + + } + + @Localized("de") + public interface DeMessages extends Messages { + + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDefaultBundleNameTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDefaultBundleNameTest.java new file mode 100644 index 00000000000000..2dfd1cfb501a37 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDefaultBundleNameTest.java @@ -0,0 +1,51 @@ +package io.quarkus.qute.deployment.i18n; + +import static io.quarkus.qute.i18n.MessageBundle.DEFAULT_NAME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.i18n.Localized; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultFileDefaultBundleNameTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Messages.class) + .addAsResource(new StringAsset("goodbye=Mej se!\nfarewell=Sbohem!"), "messages/msg.properties")) + .overrideConfigKey("quarkus.default-locale", "cs"); + + @Inject + Messages csMessages1; + + @Localized("cs") + Messages csMessages2; + + @Test + void unannotatedMessageMethod() { + assertEquals("Mej se!", csMessages1.goodbye()); + } + + @Test + void annotatedMessageMethod() { + assertEquals("Sbohem!", csMessages2.farewell()); + } + + @MessageBundle(DEFAULT_NAME) + public interface Messages { + + String goodbye(); + + @Message + String farewell(); + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDuplicateFoundTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDuplicateFoundTest.java new file mode 100644 index 00000000000000..6a5509e8b2b2b5 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/DefaultFileDuplicateFoundTest.java @@ -0,0 +1,52 @@ +package io.quarkus.qute.deployment.i18n; + +import static io.quarkus.qute.i18n.MessageBundle.DEFAULT_NAME; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.deployment.MessageBundleException; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultFileDuplicateFoundTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addAsResource(new StringAsset("hi=Nazdar!"), "messages/msg.properties") + .addAsResource(new StringAsset("hi=Ahoj!"), "messages/msg_cs.properties")) + .overrideConfigKey("quarkus.default-locale", "cs") + .assertException(t -> { + Throwable e = t; + MessageBundleException mbe = null; + while (e != null) { + if (e instanceof MessageBundleException) { + mbe = (MessageBundleException) e; + break; + } + e = e.getCause(); + } + assertNotNull(mbe); + assertTrue(mbe.getMessage().contains("localized file already exists for locale [cs]"), mbe.getMessage()); + assertTrue(mbe.getMessage().contains("msg_cs.properties"), mbe.getMessage()); + assertTrue(mbe.getMessage().contains("msg.properties"), mbe.getMessage()); + }); + + @Test + public void testValidation() { + fail(); + } + + @MessageBundle(DEFAULT_NAME) + public interface Messages { + @Message + String hi(); + } + +} From f7fb2411d467dfbc1d2afae4f4472f9702146f27 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 17 Apr 2023 12:36:59 +0200 Subject: [PATCH 038/333] Logging with Panache: fix LocalVariablesSorter usage The `LocalVariablesSorter` utility from ASM is prone to misuse [1], which is exactly what happened in the Logging with Panache bytecode transformation. As a result, the transformed bytecode didn't obey the JVM stack discipline, leading to verification errors or possibly incorrect log messages. With this commit, instructions that refer to the newly allocated local variables are emitted directly through the original `MethodVisitor`, bypassing the `LocalVariablesSorter`. Therefore, local variable slot numbers in these instructions are not transformed and will not refer to wrong local variables. [1] https://stackoverflow.com/questions/50140365/asm-strange-localvar-index-using-newlocal-from-localvariablesorter --- .../deployment/logging/LoggingWithPanacheProcessor.java | 4 ++-- .../src/test/java/io/quarkus/logging/LoggingBean.java | 8 ++++++++ .../java/io/quarkus/logging/LoggingWithPanacheTest.java | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingWithPanacheProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingWithPanacheProcessor.java index 897a6ae01de30a..3aa4d051272c98 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingWithPanacheProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingWithPanacheProcessor.java @@ -137,7 +137,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri locals = new int[numArgs]; for (int i = numArgs - 1; i >= 0; i--) { locals[i] = newLocal(argTypes[i]); - super.visitVarInsn(argTypes[i].getOpcode(Opcodes.ISTORE), locals[i]); + visitor.visitVarInsn(argTypes[i].getOpcode(Opcodes.ISTORE), locals[i]); } } @@ -162,7 +162,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String descri // stack: [logger arg1 arg2 arg3] locals: {l1 = arg4} // stack: [logger arg1 arg2 arg3 arg4] locals: {} for (int i = 0; i < numArgs; i++) { - super.visitVarInsn(argTypes[i].getOpcode(Opcodes.ILOAD), locals[i]); + visitor.visitVarInsn(argTypes[i].getOpcode(Opcodes.ILOAD), locals[i]); } } diff --git a/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingBean.java b/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingBean.java index a3bed8f67238f0..13d7133aafbebd 100644 --- a/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingBean.java +++ b/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingBean.java @@ -40,4 +40,12 @@ public void doSomething() { Log.error("Hello Error", error); } + // https://github.com/quarkusio/quarkus/issues/32663 + public void reproduceStackDisciplineIssue() { + String result; + String now = "now"; + + Log.infov("{0} {1}", "number", 42); + Log.info("string " + now); + } } diff --git a/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingWithPanacheTest.java b/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingWithPanacheTest.java index 075b1dec002aa7..7e6ee43c4f3144 100644 --- a/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingWithPanacheTest.java +++ b/integration-tests/logging-panache/src/test/java/io/quarkus/logging/LoggingWithPanacheTest.java @@ -40,7 +40,9 @@ public class LoggingWithPanacheTest { "[ERROR] four: foo | bar | baz | quux", "[WARN] foo | bar | baz | quux: io.quarkus.logging.NoStackTraceTestException", "[ERROR] Hello Error: io.quarkus.logging.NoStackTraceTestException", - "[INFO] Hi!"); + "[INFO] Hi!", + "[INFO] number 42", + "[INFO] string now"); }); @Inject @@ -50,5 +52,7 @@ public class LoggingWithPanacheTest { public void test() { bean.doSomething(); new LoggingEntity().something(); + + bean.reproduceStackDisciplineIssue(); } } From c9422dc7d5d5c7da6b6e61c48fcce67b0f6d56fc Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 17 Apr 2023 15:01:33 +0200 Subject: [PATCH 039/333] ArC - improve the AdditionalBeanBuildItem javadoc - make it clear that it should not be used for generated classes - resolves #13464 --- .../io/quarkus/arc/deployment/AdditionalBeanBuildItem.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java index 23afa839228d5b..a2dcde480edd2e 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java @@ -21,6 +21,11 @@ * An additional bean may have the scope defaulted via {@link #defaultScope} and {@link Builder#setDefaultScope(DotName)}. The * default scope is only used if there is no scope declared on the bean class. The default scope should be used in cases where a * bean class source is not controlled by the extension and the scope annotation cannot be declared directly on the class. + * + *

Generated Classes

+ * + * This build item should never be produced for a generated class - {@link GeneratedBeanBuildItem} and + * {@link GeneratedBeanGizmoAdaptor} should be used instead. */ public final class AdditionalBeanBuildItem extends MultiBuildItem { From d270e1f34dba18558025d9256d5620dadd2503e5 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 17 Apr 2023 16:28:32 +0200 Subject: [PATCH 040/333] Support decoding of messages GZIP-compressed in REST Client Reactive Fix https://github.com/quarkusio/quarkus/issues/26981 --- .../main/asciidoc/rest-client-reactive.adoc | 3 + .../test/ClientUsingGzipCompressionTest.java | 72 +++++++++++++++++++ .../client-using-gzip-application.properties | 3 + .../runtime/RestClientBuilderImpl.java | 7 ++ .../client/impl/ClientBuilderImpl.java | 12 ++++ .../ClientGZIPDecodingInterceptor.java | 41 +++++++++++ 6 files changed, 138 insertions(+) create mode 100644 extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/ClientUsingGzipCompressionTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/resources/client-using-gzip-application.properties create mode 100644 independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/interceptors/ClientGZIPDecodingInterceptor.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index b0f6dded8e37e2..c25f0f7dffdffa 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -1125,6 +1125,9 @@ The code uses the following pieces: As previously mentioned, the body parameter needs to be properly crafted by the application code to conform to the service's requirements. +=== Receiving compressed messages +REST Client Reactive also supports receiving compressed messages using GZIP. You can enable the HTTP compression support by adding the property `quarkus.http.enable-compression=true`. +When this feature is enabled and a server returns a response that includes the header `Content-Encoding: gzip`, REST Client Reactive will automatically decode the content and proceed with the message handling. == Proxy support REST Client Reactive supports sending requests through a proxy. diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/ClientUsingGzipCompressionTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/ClientUsingGzipCompressionTest.java new file mode 100644 index 00000000000000..e40abad5570705 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/ClientUsingGzipCompressionTest.java @@ -0,0 +1,72 @@ +package io.quarkus.rest.client.reactive.jackson.test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.http.Compressed; + +public class ClientUsingGzipCompressionTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(MyResource.class, Message.class, MyClient.class)) + .withConfigurationResource("client-using-gzip-application.properties"); + + @RestClient + MyClient client; + + @Test + public void testClientSupportCompressedMessagesWithGzip() { + Message actual = client.receiveCompressed(); + Assertions.assertEquals(1, actual.id); + } + + @Test + public void testClientStillWorksWhenMessageIsUncompressed() { + Message actual = client.receiveUncompressed(); + Assertions.assertEquals(1, actual.id); + } + + @Path("/client") + @RegisterRestClient(configKey = "my-client") + public interface MyClient { + + // This header is used to reproduce the issue: it will force the server to produce the payload with gzip compression + @ClientHeaderParam(name = "Accept-Encoding", value = "gzip") + @GET + @Path("/message") + Message receiveCompressed(); + + @GET + @Path("/message") + Message receiveUncompressed(); + + } + + @Path("/client") + public static class MyResource { + + @Compressed + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/message") + public String receive() { + return "{\"id\": 1}"; + } + + } + + public static class Message { + public int id; + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/resources/client-using-gzip-application.properties b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/resources/client-using-gzip-application.properties new file mode 100644 index 00000000000000..98de043b07b819 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/resources/client-using-gzip-application.properties @@ -0,0 +1,3 @@ +quarkus.http.enable-compression=true + +quarkus.rest-client.my-client.url=http://localhost:${quarkus.http.test-port:8081} \ No newline at end of file diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java index 105a88f0a3e2c6..014bfc5f6e2942 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java @@ -47,6 +47,7 @@ public class RestClientBuilderImpl implements RestClientBuilder { private static final String DEFAULT_MAPPER_DISABLED = "microprofile.rest.client.disable.default.mapper"; private static final String TLS_TRUST_ALL = "quarkus.tls.trust-all"; + private static final String ENABLE_COMPRESSION = "quarkus.http.enable-compression"; private final ClientBuilderImpl clientBuilder = (ClientBuilderImpl) new ClientBuilderImpl() .withConfig(new ConfigurationImpl(RuntimeType.CLIENT)); @@ -341,6 +342,12 @@ public T build(Class aClass) throws IllegalStateException, RestClientDefi clientBuilder.http2((Boolean) getConfiguration().getProperty(QuarkusRestClientProperties.HTTP2)); } + Boolean enableCompression = ConfigProvider.getConfig() + .getOptionalValue(ENABLE_COMPRESSION, Boolean.class).orElse(false); + if (enableCompression) { + clientBuilder.enableCompression(); + } + if (proxyHost != null) { configureProxy(proxyHost, proxyPort, proxyUser, proxyPassword, nonProxyHosts); } else if (restClientsConfig.proxyAddress.isPresent()) { diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index 025bf3e507ed45..6fd323050231ef 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -29,6 +29,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.client.api.ClientLogger; import org.jboss.resteasy.reactive.client.api.LoggingScope; +import org.jboss.resteasy.reactive.client.interceptors.ClientGZIPDecodingInterceptor; import org.jboss.resteasy.reactive.client.logging.DefaultClientLogger; import org.jboss.resteasy.reactive.client.spi.ClientContextResolver; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; @@ -75,6 +76,8 @@ public class ClientBuilderImpl extends ClientBuilder { private ClientLogger clientLogger = new DefaultClientLogger(); private String userAgent = "Resteasy Reactive Client"; + private boolean enableCompression; + public ClientBuilderImpl() { configuration = new ConfigurationImpl(RuntimeType.CLIENT); } @@ -188,6 +191,11 @@ public ClientBuilder clientLogger(ClientLogger clientLogger) { return this; } + public ClientBuilder enableCompression() { + this.enableCompression = true; + return this; + } + @Override public ClientImpl build() { HttpClientOptions options = Optional.ofNullable(configuration.getFromContext(HttpClientOptions.class)) @@ -273,6 +281,10 @@ public ClientImpl build() { } } + if (enableCompression) { + configuration.register(ClientGZIPDecodingInterceptor.class); + } + clientLogger.setBodySize(loggingBodySize); return new ClientImpl(options, diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/interceptors/ClientGZIPDecodingInterceptor.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/interceptors/ClientGZIPDecodingInterceptor.java new file mode 100644 index 00000000000000..fdc64e2c88c1bb --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/interceptors/ClientGZIPDecodingInterceptor.java @@ -0,0 +1,41 @@ +package org.jboss.resteasy.reactive.client.interceptors; + +import static jakarta.ws.rs.core.HttpHeaders.CONTENT_ENCODING; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.ext.ReaderInterceptor; +import jakarta.ws.rs.ext.ReaderInterceptorContext; + +/** + * Implementation based on {@see org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor}. + */ +public class ClientGZIPDecodingInterceptor implements ReaderInterceptor { + + private static final String GZIP = "gzip"; + + @Override + public Object aroundReadFrom(ReaderInterceptorContext context) + throws IOException, WebApplicationException { + Object encoding = context.getHeaders().getFirst(CONTENT_ENCODING); + if (encoding != null && encoding.toString().equalsIgnoreCase(GZIP)) { + InputStream old = context.getInputStream(); + GZIPInputStream is = new GZIPInputStream(old); + context.setInputStream(is); + + Object response; + try { + response = context.proceed(); + } finally { + context.setInputStream(old); + } + + return response; + } else { + return context.proceed(); + } + } +} From bbd53ba056d22270ae5fd716dd385856f870c63e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 17 Apr 2023 16:47:00 +0200 Subject: [PATCH 041/333] Make mailer beans default beans Fixes #32685 --- .../java/io/quarkus/mailer/deployment/MailerProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java index 95fb5a051f07b5..4d5dc582cf76b0 100644 --- a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java +++ b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java @@ -151,6 +151,7 @@ private void generateMailerBeansForName(String name, .scope(Singleton.class) .qualifiers(qualifier) .unremovable() + .defaultBean() .setRuntimeInit() .addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class))) .createWith(recorder.mailClientFunction(name, mailersRuntimeConfig)) @@ -159,6 +160,7 @@ private void generateMailerBeansForName(String name, .scope(Singleton.class) .qualifiers(qualifier) .unremovable() + .defaultBean() .setRuntimeInit() .addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class))) .createWith(recorder.reactiveMailClientFunction(name, mailersRuntimeConfig)) @@ -167,6 +169,7 @@ private void generateMailerBeansForName(String name, .scope(Singleton.class) .qualifiers(qualifier) .unremovable() + .defaultBean() .setRuntimeInit() .addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class))) .createWith(recorder.mailerFunction(name, mailersRuntimeConfig)) @@ -175,6 +178,7 @@ private void generateMailerBeansForName(String name, .scope(Singleton.class) .qualifiers(qualifier) .unremovable() + .defaultBean() .setRuntimeInit() .addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class))) .createWith(recorder.reactiveMailerFunction(name, mailersRuntimeConfig)) @@ -183,6 +187,7 @@ private void generateMailerBeansForName(String name, .scope(Singleton.class) .qualifiers(qualifier) .unremovable() + .defaultBean() .setRuntimeInit() .addInjectionPoint(ClassType.create(DotName.createSimple(Mailers.class))) .createWith(recorder.mockMailboxFunction(name, mailersRuntimeConfig)) From 5ab553a92cb6a3f4b89ee69781a329c81f033ed3 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 17 Apr 2023 16:44:09 +0100 Subject: [PATCH 042/333] Allow ConfigMappings with default visibility --- .../RunTimeConfigurationGenerator.java | 2 -- .../steps/ConfigGenerationBuildStep.java | 15 +++++++-------- .../runtime/configuration/ConfigUtils.java | 9 --------- .../configuration/MappingsConfigBuilder.java | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index 111b6171fdba6b..c032fce642b7b0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -173,8 +173,6 @@ public final class RunTimeConfigurationGenerator { static final MethodDescriptor CU_ADD_SOURCE_FACTORY_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceFactoryProvider", void.class, SmallRyeConfigBuilder.class, ConfigSourceFactoryProvider.class); - static final MethodDescriptor CU_WITH_MAPPING = MethodDescriptor.ofMethod(ConfigUtils.class, "addMapping", - void.class, SmallRyeConfigBuilder.class, String.class, String.class); static final MethodDescriptor RCS_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSource.class, String.class); static final MethodDescriptor RCSP_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceProvider.class, String.class); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index 29d490c2cc6584..48bd549b6edd66 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -75,6 +75,7 @@ import io.quarkus.runtime.configuration.ConfigRecorder; import io.quarkus.runtime.configuration.DefaultsConfigSource; import io.quarkus.runtime.configuration.DisableableConfigSource; +import io.quarkus.runtime.configuration.MappingsConfigBuilder; import io.quarkus.runtime.configuration.QuarkusConfigValue; import io.quarkus.runtime.configuration.RuntimeOverrideConfigSource; import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; @@ -91,10 +92,6 @@ public class ConfigGenerationBuildStep { SmallRyeConfigBuilder.class, "withSources", SmallRyeConfigBuilder.class, ConfigSource[].class); - private static final MethodDescriptor WITH_MAPPING = MethodDescriptor.ofMethod( - SmallRyeConfigBuilder.class, "withMapping", - SmallRyeConfigBuilder.class, Class.class, String.class); - @BuildStep void staticInitSources( BuildProducer staticInitConfigSourceProviderBuildItem, @@ -553,23 +550,25 @@ private static void generateMappingsConfigBuilder( .classOutput(new GeneratedClassGizmoAdaptor(generatedClass, true)) .className(className) .interfaces(ConfigBuilder.class) + .superClass(MappingsConfigBuilder.class) .setFinal(true) .build()) { MethodCreator method = classCreator.getMethodCreator(CONFIG_BUILDER); ResultHandle configBuilder = method.getMethodParam(0); + MethodDescriptor addMapping = MethodDescriptor.ofMethod(MappingsConfigBuilder.class, "addMapping", void.class, + SmallRyeConfigBuilder.class, String.class, String.class); + for (ConfigClassWithPrefix mapping : mappings) { - method.invokeVirtualMethod(WITH_MAPPING, configBuilder, - method.loadClass(mapping.getKlass()), + method.invokeStaticMethod(addMapping, configBuilder, method.load(mapping.getKlass().getName()), method.load(mapping.getPrefix())); } method.returnValue(configBuilder); } - reflectiveClass - .produce(ReflectiveClassBuildItem.builder(className).build()); + reflectiveClass.produce(ReflectiveClassBuildItem.builder(className).build()); } private static Set discoverService( diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index 7169fceea750ec..649a612ed84335 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -244,15 +244,6 @@ public static void addSourceFactoryProvider(SmallRyeConfigBuilder builder, Confi builder.withSources(provider.getConfigSourceFactory(Thread.currentThread().getContextClassLoader())); } - public static void addMapping(SmallRyeConfigBuilder builder, String mappingClass, String prefix) { - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - try { - builder.withMapping(contextClassLoader.loadClass(mappingClass), prefix); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - public static List getProfiles() { return ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getProfiles(); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java new file mode 100644 index 00000000000000..24d1b607074bea --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java @@ -0,0 +1,18 @@ +package io.quarkus.runtime.configuration; + +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * To support mappings that are not public + */ +public abstract class MappingsConfigBuilder implements ConfigBuilder { + protected static void addMapping(SmallRyeConfigBuilder builder, String mappingClass, String prefix) { + // TODO - Ideally should use the classloader passed to Config, but the method is not public. Requires a change in SmallRye Config. + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + builder.withMapping(contextClassLoader.loadClass(mappingClass), prefix); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} From 9850c2e1d779020ee08f45be501e18caa2595638 Mon Sep 17 00:00:00 2001 From: Melloware Date: Sun, 16 Apr 2023 19:45:39 -0400 Subject: [PATCH 043/333] Undertow file servlet listing --- .../test/ResourceManagerTestCase.java | 59 +++++++++++++++++++ .../runtime/KnownPathResourceManager.java | 56 +++++++++++++----- 2 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java new file mode 100644 index 00000000000000..5bd77e6640383d --- /dev/null +++ b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java @@ -0,0 +1,59 @@ +package io.quarkus.undertow.test; + +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ResourceManagerTestCase { + + private static final String CONTEXT_PATH = "/foo"; + public static final String META_INF_RESOURCES = "META-INF/resources/"; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(ContextPathServlet.class) + .addAsResource(new StringAsset("index.html"), "META-INF/resources/index.html") + .addAsResource(new StringAsset("foo/foo.html"), "META-INF/resources/foo/foo.html") + .addAsResource(new StringAsset("foo/bar/bar.html"), "META-INF/resources/foo/bar/bar.html")); + + @Test + public void testServlet() { + RestAssured.when().get("/").then() + .statusCode(200) + .body(is("[foo, index.html]")); + RestAssured.when().get("/foo").then() + .statusCode(200) + .body(is("[foo/bar, foo/foo.html]")); + RestAssured.when().get("/foo/bar").then() + .statusCode(200) + .body(is("[foo/bar/bar.html]")); + } + + @WebServlet(urlPatterns = "/*") + public static class ContextPathServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + var paths = req.getServletContext().getResourcePaths(req.getPathInfo() == null ? "/" : req.getPathInfo()); + resp.getWriter().write(String.valueOf(new TreeSet<>( + paths.stream().map(s -> s.substring(s.lastIndexOf(META_INF_RESOURCES) + META_INF_RESOURCES.length())) + .collect(Collectors.toSet())))); + } + } +} \ No newline at end of file diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java index eae7915b6a41a2..fe426c649bf774 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java @@ -3,15 +3,17 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.NavigableSet; import java.util.Set; +import java.util.SortedSet; import java.util.TreeSet; import io.undertow.httpcore.OutputChannel; @@ -23,8 +25,10 @@ public class KnownPathResourceManager implements ResourceManager { + public static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"); + private final NavigableSet files; - private final Set directories; + private final NavigableSet directories; private final ResourceManager underlying; public KnownPathResourceManager(Set files, Set directories, ResourceManager underlying) { @@ -51,7 +55,7 @@ public KnownPathResourceManager(Set files, Set directories, Reso tmp.add(i); } tmp.add(""); - this.directories = Collections.unmodifiableSet(tmp); + this.directories = new TreeSet<>(tmp); } @Override @@ -78,7 +82,11 @@ private class DirectoryResource implements Resource { private final String path; private DirectoryResource(String path) { - this.path = path; + this.path = evaluatePath(path); + } + + private String evaluatePath(String path) { + return IS_WINDOWS ? path.replaceAll("\\\\", "/") : path; } @Override @@ -118,18 +126,38 @@ public boolean isDirectory() { @Override public List list() { List ret = new ArrayList<>(); - String slashPath = path + "/"; - for (String i : files.headSet(path)) { - if (i.startsWith(slashPath)) { - try { - ret.add(underlying.getResource(i)); - } catch (IOException e) { - throw new RuntimeException(e); + String slashPath = path.isEmpty() ? path : path + "/"; + if (IS_WINDOWS) { + slashPath = slashPath.replaceAll("/", "\\\\"); // correct Windows paths + } + SortedSet fileSet = files.tailSet(slashPath); + SortedSet dirSet = directories.tailSet(slashPath); + + for (var s : List.of(fileSet, dirSet)) { + for (String i : s) { + if (i.equals(slashPath)) { + continue; + } + if (i.startsWith(slashPath)) { + i = evaluatePath(i); + if (!i.substring(slashPath.length()).contains("/")) { + try { + Resource resource = underlying.getResource(i); + if (resource == null) { + throw new RuntimeException("Unable to get listed resource " + i + " from directory " + path + + " for path " + slashPath + " from underlying manager " + underlying); + } + ret.add(resource); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } else { + break; } - } else { - break; } } + return ret; } @@ -184,4 +212,4 @@ public URL getUrl() { return null; } } -} +} \ No newline at end of file From 6019c4fa82b1e61a10965673a9b5c11af656df26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crocquesel?= <88554524+scrocquesel@users.noreply.github.com> Date: Mon, 17 Apr 2023 21:56:24 +0200 Subject: [PATCH 044/333] Include MariaDB deprecated.properties --- .../quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java b/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java index e652d6338740fb..d5443003105e43 100644 --- a/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java +++ b/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java @@ -68,6 +68,9 @@ void addNativeImageResources(BuildProducer resourc // driver.properties is not added because it only provides optional descriptions for // org.mariadb.jdbc.Driver.getPropertyInfo(), which is probably not even called. resources.produce(new NativeImageResourceBuildItem("mariadb.properties")); + + // necessary when jdbcUrl contains useSsl=true + resources.produce(new NativeImageResourceBuildItem("deprecated.properties")); } @BuildStep From 7e6f4781b9371177f8947e26a2c35b225153def1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 22:22:20 +0000 Subject: [PATCH 045/333] Bump yasson from 3.0.2 to 3.0.3 Bumps [yasson](https://github.com/eclipse-ee4j/yasson) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/eclipse-ee4j/yasson/releases) - [Commits](https://github.com/eclipse-ee4j/yasson/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: org.eclipse:yasson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 466491a1be17e4..0db80fcfaa6370 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -166,7 +166,7 @@ 4.2.0 1.1.1 9.16.3 - 3.0.2 + 3.0.3 4.20.0 2.0 6.0.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index c2b4abbf781f49..4edd0724deab77 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -69,7 +69,7 @@ 2.14.2 2.1.0 3.0.2 - 3.0.2 + 3.0.3 3.0.0 4.2.0 3.2.0 From d480ef8d53f1cf45a605846a36d0e5bb6f406842 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 18 Apr 2023 13:09:28 +1000 Subject: [PATCH 046/333] Dev UI Move info to menu item and format page for known items Signed-off-by: Phillip Kruger --- ...Processor.java => InfoDevUIProcessor.java} | 24 +-- .../src/main/resources/dev-ui/qwc-info.js | 149 ++++++++++++++++++ .../deployment/BuildTimeContentProcessor.java | 40 ++++- .../src/main/resources/dev-ui/qui/qui-card.js | 92 +++++++++++ 4 files changed, 288 insertions(+), 17 deletions(-) rename extensions/info/deployment/src/main/java/io/quarkus/info/deployment/{InfoDevUiProcessor.java => InfoDevUIProcessor.java} (62%) create mode 100644 extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js create mode 100644 extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-card.js diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUiProcessor.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUIProcessor.java similarity index 62% rename from extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUiProcessor.java rename to extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUIProcessor.java index a8c4e6362386fa..8c1e87051ea773 100644 --- a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUiProcessor.java +++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUIProcessor.java @@ -2,34 +2,36 @@ import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.ExecutionTime; -import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.LaunchModeBuildItem; -import io.quarkus.devui.spi.page.CardPageBuildItem; +import io.quarkus.devui.spi.page.MenuPageBuildItem; import io.quarkus.devui.spi.page.Page; -import io.quarkus.info.runtime.InfoRecorder; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; /** * This processor is responsible for the dev ui widget. */ -public class InfoDevUiProcessor { +public class InfoDevUIProcessor { @BuildStep(onlyIf = IsDevelopment.class) - @Record(ExecutionTime.STATIC_INIT) - CardPageBuildItem create(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + MenuPageBuildItem create(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, InfoBuildTimeConfig config, ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig, - LaunchModeBuildItem launchModeBuildItem, - InfoRecorder unused) { - CardPageBuildItem pageBuildItem = new CardPageBuildItem(); + LaunchModeBuildItem launchModeBuildItem) { + MenuPageBuildItem pageBuildItem = new MenuPageBuildItem(); var path = nonApplicationRootPathBuildItem.resolveManagementPath(config.path(), managementInterfaceBuildTimeConfig, launchModeBuildItem); - pageBuildItem.addPage(Page.externalPageBuilder("App Information") + + pageBuildItem.addBuildTimeData("infoUrl", path); + + pageBuildItem.addPage(Page.webComponentPageBuilder() + .title("Information") .icon("font-awesome-solid:circle-info") + .componentLink("qwc-info.js")); + pageBuildItem.addPage(Page.externalPageBuilder("Raw") .url(path) + .icon("font-awesome-solid:circle-info") .isJsonContent()); return pageBuildItem; diff --git a/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js new file mode 100644 index 00000000000000..3cd1f9e32c2d2a --- /dev/null +++ b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js @@ -0,0 +1,149 @@ +import { LitElement, html, css} from 'lit'; +import { columnBodyRenderer } from '@vaadin/grid/lit.js'; +import { infoUrl } from 'build-time-data'; +import '@vaadin/progress-bar'; +import 'qui-card'; +import '@vaadin/icon'; + +/** + * This component shows the Info Screen + */ +export class QwcInfo extends LitElement { + + static styles = css` + :host { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(450px, 1fr)); + gap: 1em; + padding: 10px; + } + qui-card { + display: flex; + } + .cardContent { + display: flex; + align-items: center; + padding: 10px; + gap: 10px; + } + vaadin-icon { + font-size: xx-large; + } + `; + + static properties = { + _infoUrl: {state: false}, + _info: {state: true}, + }; + + constructor() { + super(); + this._infoUrl = infoUrl; + this._info = null; + } + + async connectedCallback() { + super.connectedCallback(); + await this.load(); + } + + async load() { + const response = await fetch(this._infoUrl) + const data = await response.json(); + this._info = data; + } + + render() { + if (this._info) { + return html` + ${this._renderOsInfo(this._info)} + ${this._renderJavaInfo(this._info)} + ${this._renderGitInfo(this._info)} + ${this._renderBuildInfo(this._info)} + `; + }else{ + return html` +
+
Fetching infomation...
+ +
+ `; + } + } + + _renderOsInfo(info){ + if(info.os){ + let os = info.os; + return html` +
+ ${this._renderOsIcon(os.name)} + + + + +
Name${os.name}
Version${os.version}
Arch${os.arch}
+
+
`; + } + } + + _renderJavaInfo(info){ + if(info.java){ + let java = info.java; + return html` +
+ + + +
Version${java.version}
+
+
`; + } + } + + _renderOsIcon(osname){ + + if(osname){ + if(osname.toLowerCase().startsWith("linux")){ + return html``; + }else if(osname.toLowerCase().startsWith("mac") || osname.toLowerCase().startsWith("darwin")){ + return html``; + }else if(osname.toLowerCase().startsWith("win")){ + return html``; + } + } + } + + _renderGitInfo(info){ + if(info.git){ + let git = info.git; + return html` +
+ + + + + +
Branch${git.branch}
Commit${git.commit.id}
Time${git.commit.time}
+
+
`; + } + } + + _renderBuildInfo(info){ + if(info.build){ + let build = info.build; + return html` +
+ + + + + +
Group${build.group}
Artifact${build.artifact}
Version${build.version}
Time${build.time}
+
+
`; + } + } +} +customElements.define('qwc-info', QwcInfo); \ No newline at end of file diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java index 8c86ff8afb119e..d7c38fc12410f8 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java @@ -32,7 +32,10 @@ import io.quarkus.devui.spi.DevUIContent; import io.quarkus.devui.spi.buildtime.QuteTemplateBuildItem; import io.quarkus.devui.spi.buildtime.StaticContentBuildItem; +import io.quarkus.devui.spi.page.AbstractPageBuildItem; import io.quarkus.devui.spi.page.CardPageBuildItem; +import io.quarkus.devui.spi.page.FooterPageBuildItem; +import io.quarkus.devui.spi.page.MenuPageBuildItem; import io.quarkus.devui.spi.page.Page; import io.quarkus.devui.spi.page.PageBuilder; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; @@ -67,6 +70,8 @@ InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBu internalImportMapBuildItem.add("qwc-server-log", contextRoot + "qwc/qwc-server-log.js"); // Quarkus UI internalImportMapBuildItem.add("qui/", contextRoot + "qui/"); + internalImportMapBuildItem.add("qui-card", contextRoot + "qui/qui-card.js"); + internalImportMapBuildItem.add("qui-badge", contextRoot + "qui/qui-badge.js"); internalImportMapBuildItem.add("qui-alert", contextRoot + "qui/qui-alert.js"); // Echarts @@ -101,13 +106,31 @@ InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBu * @param buildTimeConstProducer */ @BuildStep(onlyIf = IsDevelopment.class) - void mapPageBuildTimeData(List pageBuildItems, + void mapPageBuildTimeData(List cards, + List menus, + List footers, CurateOutcomeBuildItem curateOutcomeBuildItem, BuildProducer buildTimeConstProducer) { - for (CardPageBuildItem pageBuildItem : pageBuildItems) { - String extensionPathName = pageBuildItem.getExtensionPathName(curateOutcomeBuildItem); - Map buildTimeData = getBuildTimeData(curateOutcomeBuildItem, pageBuildItem); + for (CardPageBuildItem card : cards) { + String extensionPathName = card.getExtensionPathName(curateOutcomeBuildItem); + Map buildTimeData = getBuildTimeDataForCard(curateOutcomeBuildItem, card); + if (!buildTimeData.isEmpty()) { + buildTimeConstProducer.produce( + new BuildTimeConstBuildItem(extensionPathName, buildTimeData)); + } + } + for (MenuPageBuildItem menu : menus) { + String extensionPathName = menu.getExtensionPathName(curateOutcomeBuildItem); + Map buildTimeData = getBuildTimeDataForPage(menu); + if (!buildTimeData.isEmpty()) { + buildTimeConstProducer.produce( + new BuildTimeConstBuildItem(extensionPathName, buildTimeData)); + } + } + for (FooterPageBuildItem footer : footers) { + String extensionPathName = footer.getExtensionPathName(curateOutcomeBuildItem); + Map buildTimeData = getBuildTimeDataForPage(footer); if (!buildTimeData.isEmpty()) { buildTimeConstProducer.produce( new BuildTimeConstBuildItem(extensionPathName, buildTimeData)); @@ -115,12 +138,17 @@ void mapPageBuildTimeData(List pageBuildItems, } } - private Map getBuildTimeData(CurateOutcomeBuildItem curateOutcomeBuildItem, - CardPageBuildItem pageBuildItem) { + private Map getBuildTimeDataForPage(AbstractPageBuildItem pageBuildItem) { Map m = new HashMap<>(); if (pageBuildItem.hasBuildTimeData()) { m.putAll(pageBuildItem.getBuildTimeData()); } + return m; + } + + private Map getBuildTimeDataForCard(CurateOutcomeBuildItem curateOutcomeBuildItem, + CardPageBuildItem pageBuildItem) { + Map m = getBuildTimeDataForPage(pageBuildItem); if (pageBuildItem.getOptionalCard().isPresent()) { // Make the pages available for the custom card diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-card.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-card.js new file mode 100644 index 00000000000000..2ea714978cdc1b --- /dev/null +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-card.js @@ -0,0 +1,92 @@ +import { LitElement, html, css} from 'lit'; + +/** + * Card UI Component + */ +export class QuiCard extends LitElement { + + static styles = css` + .card { + display: flex; + flex-direction: column; + justify-content: space-between; + border: 1px solid var(--lumo-contrast-10pct); + border-radius: 4px; + filter: brightness(90%); + + } + + .card-header { + font-size: var(--lumo-font-size-l); + line-height: 1; + height: 25px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px; + background-color: var(--lumo-contrast-5pct); + border-bottom: 1px solid var(--lumo-contrast-10pct); + } + + .card-footer { + height: 20px; + padding: 10px 10px; + color: var(--lumo-contrast-50pct); + display: flex; + flex-direction: row; + justify-content: space-between; + visibility:hidden; + }`; + + static properties = { + title: {type: String}, + width: {state: true}, + _hasFooter: {state: true}, + }; + + constructor(){ + super(); + this.width = "100%"; + this._hasFooter = false; + } + + connectedCallback() { + super.connectedCallback() + } + + render() { + return html`
+ ${this._headerTemplate()} + + ${this._footerTemplate()} +
`; + } + + firstUpdated(){ + const footerSlot = this.shadowRoot.querySelector("#footer"); + if (footerSlot && footerSlot.assignedNodes().length>0){ + console.log('No content is available') + this._hasFooter = true; + } + } + + _headerTemplate() { + return html`
+
${this.title}
+
+ `; + } + + _footerTemplate() { + if(this._hasFooter){ + return html` + + `; + } + } + +} +customElements.define('qui-card', QuiCard); \ No newline at end of file From e60c3ac92e6b486a3cb918bd97e9f710bbce16fd Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 18 Apr 2023 08:39:55 +0200 Subject: [PATCH 047/333] Ensure the ServiceAccount/Role/ClusterRole resourcs are created in order Fix https://github.com/quarkusio/quarkus/issues/32640 Partially reverts https://github.com/quarkusio/quarkus/pull/32208 --- .../AddClusterRoleResourceDecorator.java | 6 - .../deployment/AddRoleResourceDecorator.java | 6 - .../AddServiceAccountResourceDecorator.java | 6 - .../deployment/KubernetesProcessor.java | 3 +- .../deployment/QuarkusFileWriter.java | 105 ++++++++++++++++++ .../KubernetesWithRbacFullTest.java | 6 + 6 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java index d13091514834bf..2074e4fd122f10 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Map; -import io.dekorate.kubernetes.decorator.Decorator; import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; @@ -46,9 +45,4 @@ public void visit(KubernetesListBuilder list) { .endMetadata() .withRules(rules)); } - - @Override - public Class[] before() { - return new Class[] { AddRoleBindingResourceDecorator.class }; - } } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java index 632b3bca7bcefc..752efe7fd2b035 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Map; -import io.dekorate.kubernetes.decorator.Decorator; import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; @@ -49,9 +48,4 @@ public void visit(KubernetesListBuilder list) { .endMetadata() .withRules(rules)); } - - @Override - public Class[] before() { - return new Class[] { AddRoleBindingResourceDecorator.class }; - } } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java index f6f90801b3608b..b8fb1f0eb8dc48 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java @@ -5,7 +5,6 @@ import java.util.HashMap; import java.util.Map; -import io.dekorate.kubernetes.decorator.Decorator; import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; @@ -44,9 +43,4 @@ public void visit(KubernetesListBuilder list) { .endMetadata() .endServiceAccountItem(); } - - @Override - public Class[] before() { - return new Class[] { AddRoleBindingResourceDecorator.class }; - } } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java index cce484f5534c97..fa4d58dad98251 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java @@ -30,7 +30,6 @@ import io.dekorate.kubernetes.decorator.Decorator; import io.dekorate.logger.NoopLogger; import io.dekorate.processor.SimpleFileReader; -import io.dekorate.processor.SimpleFileWriter; import io.dekorate.project.Project; import io.dekorate.utils.Maps; import io.dekorate.utils.Strings; @@ -146,7 +145,7 @@ public void build(ApplicationInfoBuildItem applicationInfo, .map(DeploymentTargetEntry::getName) .collect(Collectors.toSet())); final Map generatedResourcesMap; - final SessionWriter sessionWriter = new SimpleFileWriter(project, false); + final SessionWriter sessionWriter = new QuarkusFileWriter(project); final SessionReader sessionReader = new SimpleFileReader( project.getRoot().resolve("src").resolve("main").resolve("kubernetes"), targets); sessionWriter.setProject(project); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java new file mode 100644 index 00000000000000..a837f359522dcc --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java @@ -0,0 +1,105 @@ +package io.quarkus.kubernetes.deployment; + +import static io.quarkus.kubernetes.deployment.Constants.CLUSTER_ROLE; +import static io.quarkus.kubernetes.deployment.Constants.CLUSTER_ROLE_BINDING; +import static io.quarkus.kubernetes.deployment.Constants.CRONJOB; +import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; +import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG; +import static io.quarkus.kubernetes.deployment.Constants.INGRESS; +import static io.quarkus.kubernetes.deployment.Constants.JOB; +import static io.quarkus.kubernetes.deployment.Constants.ROLE; +import static io.quarkus.kubernetes.deployment.Constants.ROLE_BINDING; +import static io.quarkus.kubernetes.deployment.Constants.ROUTE; +import static io.quarkus.kubernetes.deployment.Constants.SERVICE_ACCOUNT; +import static io.quarkus.kubernetes.deployment.Constants.STATEFULSET; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import io.dekorate.processor.SimpleFileWriter; +import io.dekorate.project.Project; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.KubernetesListBuilder; + +public class QuarkusFileWriter extends SimpleFileWriter { + + private static final List RESOURCE_KIND_ORDER = List.of("Namespace", + "NetworkPolicy", + "ResourceQuota", + "LimitRange", + "PodSecurityPolicy", + "PodDisruptionBudget", + SERVICE_ACCOUNT, + "Secret", + "SecretList", + "ConfigMap", + "StorageClass", + "PersistentVolume", + "PersistentVolumeClaim", + "CustomResourceDefinition", + CLUSTER_ROLE, + "ClusterRoleList", + CLUSTER_ROLE_BINDING, + "ClusterRoleBindingList", + ROLE, + "RoleList", + ROLE_BINDING, + "RoleBindingList", + "Service", + "ImageStream", + "BuildConfig", + "DaemonSet", + "Pod", + "ReplicationController", + "ReplicaSet", + DEPLOYMENT, + "HorizontalPodAutoscaler", + STATEFULSET, + DEPLOYMENT_CONFIG, + JOB, + CRONJOB, + INGRESS, + ROUTE, + "APIService"); + + public QuarkusFileWriter(Project project) { + super(project, false); + } + + @Override + public Map write(String group, KubernetesList list) { + // sort resources in list by: ServiceAccount, Role, ClusterRole, the rest... + return super.write(group, new KubernetesListBuilder().addAllToItems(sort(list.getItems())).build()); + } + + private List sort(List items) { + // Resources that we need the order. + Map> groups = new HashMap<>(); + // List of resources with unknown order: we preserve the order of creation in this case + List rest = new LinkedList<>(); + for (HasMetadata item : items) { + String kind = item.getKind(); + if (RESOURCE_KIND_ORDER.contains(kind)) { + groups.computeIfAbsent(kind, k -> new LinkedList<>()) + .add(item); + } else { + rest.add(item); + } + } + + List sorted = new LinkedList<>(); + // we first add the resources with order + for (String kind : RESOURCE_KIND_ORDER) { + List group = groups.get(kind); + if (group != null) { + sorted.addAll(group); + } + } + + sorted.addAll(rest); + return sorted; + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithRbacFullTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithRbacFullTest.java index 41bdd6c3e594f0..83be5e5411d865 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithRbacFullTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithRbacFullTest.java @@ -53,6 +53,12 @@ public void assertGeneratedResources() throws IOException { assertTrue(lastIndexOfRoleRefKind < firstIndexOfRoleBinding, "RoleBinding resource is created before " + "the Role/ClusterRole/ServiceAccount resource!"); + // ensure service account resource is generated before the Deployment resource: + int lastIndexOfServiceAccount = lastIndexOfKind(kubernetesFileContent, "ServiceAccount"); + int firstIndexOfDeployment = kubernetesFileContent.indexOf("kind: Deployment"); + assertTrue(lastIndexOfServiceAccount < firstIndexOfDeployment, "ServiceAccount resource is created after " + + "the Deployment resource!"); + List kubernetesList = DeserializationUtil.deserializeAsList(kubernetesFile); Deployment deployment = getDeploymentByName(kubernetesList, APP_NAME); From 9bbfe72549539f5378929b6bc85e639d12c5da6e Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 18 Apr 2023 10:07:44 +0200 Subject: [PATCH 048/333] Add service registars and loaders to the list of beans to keep --- .../stork/deployment/SmallRyeStorkProcessor.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java b/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java index 39254abe27a818..a1e2f4ab5750a1 100644 --- a/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java +++ b/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java @@ -26,8 +26,10 @@ import io.quarkus.vertx.deployment.VertxBuildItem; import io.smallrye.stork.spi.LoadBalancerProvider; import io.smallrye.stork.spi.ServiceDiscoveryProvider; +import io.smallrye.stork.spi.ServiceRegistrarProvider; import io.smallrye.stork.spi.internal.LoadBalancerLoader; import io.smallrye.stork.spi.internal.ServiceDiscoveryLoader; +import io.smallrye.stork.spi.internal.ServiceRegistrarLoader; public class SmallRyeStorkProcessor { @@ -35,10 +37,11 @@ public class SmallRyeStorkProcessor { private static final Logger LOGGER = Logger.getLogger(SmallRyeStorkProcessor.class.getName()); @BuildStep - void registerServiceProviders(BuildProducer services, Capabilities capabilities) { + void registerServiceProviders(BuildProducer services) { services.produce(new ServiceProviderBuildItem(io.smallrye.stork.spi.config.ConfigProvider.class.getName(), StorkConfigProvider.class.getName())); - for (Class providerClass : asList(LoadBalancerLoader.class, ServiceDiscoveryLoader.class)) { + for (Class providerClass : asList(LoadBalancerLoader.class, ServiceDiscoveryLoader.class, + ServiceRegistrarLoader.class)) { services.produce(ServiceProviderBuildItem.allProvidersFromClassPath(providerClass.getName())); } } @@ -47,11 +50,15 @@ void registerServiceProviders(BuildProducer services, UnremovableBeanBuildItem unremoveableBeans() { return UnremovableBeanBuildItem.beanTypes( DotName.createSimple(ServiceDiscoveryProvider.class), - DotName.createSimple(LoadBalancerProvider.class)); + DotName.createSimple(ServiceDiscoveryLoader.class), + DotName.createSimple(LoadBalancerProvider.class), + DotName.createSimple(LoadBalancerLoader.class), + DotName.createSimple(ServiceRegistrarProvider.class), + DotName.createSimple(ServiceRegistrarLoader.class)); } /** - * This build step is the fix for https://github.com/quarkusio/quarkus/issues/24444. + * This build step is the fix for #24444. * Because Stork itself cannot depend on Quarkus, and we do not want to have extensions for all the service * discovery and load-balancer providers, we work around the issue by detecting when the kubernetes service * discovery is used and if the kubernetes extension is used. From d1b5543da0b47ea8ec7b1e4df353d0efb96ed170 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 17 Apr 2023 15:06:50 +0100 Subject: [PATCH 049/333] Reinitialize BouncyCastle DRBG URLSeededEntropySourceProvider at runtime --- .../io/quarkus/security/deployment/SecurityProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index a240bf2cabc32f..1b9a6f21932e7e 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -214,6 +214,11 @@ private static void prepareBouncyCastleProvider(CurateOutcomeBuildItem curateOut .produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.jcajce.provider.drbg.DRBG$Default")); runtimeReInitialized .produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV")); + // URLSeededEntropySourceProvider.seedStream may contain a reference to a 'FileInputStream' which includes + // references to FileDescriptors which aren't allowed in the image heap + runtimeReInitialized + .produce(new RuntimeReinitializedClassBuildItem( + "org.bouncycastle.jcajce.provider.drbg.DRBG$URLSeededEntropySourceProvider")); } else { reflection.produce(ReflectiveClassBuildItem.builder("org.bouncycastle.crypto.general.AES") .methods().fields().build()); From afa5e428933afed2e39fd303a3e88e5ee80c5354 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 18 Apr 2023 12:19:59 +0300 Subject: [PATCH 050/333] Avoid using ClientWebApplicationException as the cause of ClientWebApplicationException --- .../reactive/client/impl/RestClientRequestContext.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java index 1d0e1380b3cfce..59e89940f428cd 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java @@ -179,7 +179,9 @@ protected Throwable unwrapException(Throwable t) { + invokedMethod.getDeclaringClass().getName() + "#" + invokedMethod.getName() + "'"; } - return new ClientWebApplicationException(message, webApplicationException, + return new ClientWebApplicationException(message, + webApplicationException instanceof ClientWebApplicationException ? webApplicationException.getCause() + : webApplicationException, webApplicationException.getResponse()); } return res; From e542cc94e57915313d7246df43ebefb024ec3d04 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 18 Apr 2023 12:23:54 +0300 Subject: [PATCH 051/333] Throw better exception when REST Client receives invalid JSON Fixes: #32710 --- .../test/InvalidJsonFromServerTest.java | 61 +++++++++++++++++++ .../ClientJacksonMessageBodyReader.java | 9 ++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java new file mode 100644 index 00000000000000..7e9ce98150490e --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java @@ -0,0 +1,61 @@ +package io.quarkus.rest.client.reactive.jackson.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.ClientWebApplicationException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that when the server responds with data that is not valid JSON, we return an internal server error + */ +public class InvalidJsonFromServerTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(JsonObject.class, JsonClient.class, InvalidJsonEndpoint.class)); + + @RestClient + JsonClient client; + + @Test + public void test() { + assertThatThrownBy(() -> client.get()) + .isInstanceOf(ClientWebApplicationException.class) + .hasMessageContaining("HTTP 200") + .cause() + .hasMessageContaining("was expecting double-quote to start field name"); + } + + @Path("/invalid-json") + @RegisterRestClient(baseUri = "http://localhost:8081") + public interface JsonClient { + + @Produces(MediaType.APPLICATION_JSON) + @GET + JsonObject get(); + } + + static class JsonObject { + public String name; + } + + @Path("/invalid-json") + @Produces(MediaType.APPLICATION_JSON) + public static class InvalidJsonEndpoint { + + @GET + public String get() { + return "{name: test}"; + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java index 19d3d79d72b648..5939422b47b3ac 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java @@ -14,11 +14,13 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; +import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.ClientWebApplicationException; import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -26,6 +28,8 @@ public class ClientJacksonMessageBodyReader extends JacksonBasicMessageBodyReader implements ClientRestHandler { + private static final Logger log = Logger.getLogger(ClientJacksonMessageBodyReader.class); + private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>(); private RestClientRequestContext context; @@ -39,8 +43,11 @@ public Object readFrom(Class type, Type genericType, Annotation[] annota MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream); + } catch (JsonParseException e) { + log.debug("Server returned invalid json data", e); + throw new ClientWebApplicationException(e, Response.Status.OK); } catch (StreamReadException | DatabindException e) { - throw new ClientWebApplicationException(e, Response.Status.BAD_REQUEST); + throw new ClientWebApplicationException(e, Response.Status.BAD_REQUEST); // TODO: we need to check if this actually makes sense... } } From 869c248b96faa3e7a246439cc55cd4f5306e33e5 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 18 Apr 2023 10:51:07 +0300 Subject: [PATCH 052/333] Add rule to mark Bearer token issues as security issues Done because https://github.com/quarkusio/quarkus/issues/32701 was left as `needs-triage` --- .github/quarkus-github-bot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/quarkus-github-bot.yml b/.github/quarkus-github-bot.yml index d8f4ebc89cda51..84ab3ceb5f5abb 100644 --- a/.github/quarkus-github-bot.yml +++ b/.github/quarkus-github-bot.yml @@ -226,6 +226,10 @@ triage: - extensions/security/ - extensions/elytron - integration-tests/elytron + - id: bearer-token + labels: [area/security, area/oidc] + title: "bearer" + notify: [sberyozkin, pedroigor] - id: metrics labels: [area/metrics, area/smallrye] title: "metrics" From 3e87a015d922cdddd8b5d2a9c921455a6692c31d Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 18 Apr 2023 11:09:10 +0100 Subject: [PATCH 053/333] Get element type without annotations --- .../generate_doc/ConfigDocItemFinder.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java index 26035efd27f36b..0e5c0643f18eaf 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java @@ -159,8 +159,6 @@ private List recursivelyFindConfigItems(Element element, String r String name = null; String defaultValue = NO_DEFAULT; String defaultValueDoc = EMPTY; - final TypeMirror typeMirror = unwrapTypeMirror(enclosedElement.asType()); - String type = typeMirror.toString(); List acceptedValues = null; final TypeElement clazz = (TypeElement) element; final String fieldName = enclosedElement.getSimpleName().toString(); @@ -250,6 +248,9 @@ private List recursivelyFindConfigItems(Element element, String r defaultValue = EMPTY; } + TypeMirror typeMirror = unwrapTypeMirror(enclosedElement.asType()); + String type = getType(typeMirror); + if (isConfigGroup(type)) { List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, type, configSection, withinAMap, generateSeparateConfigGroupDocsFiles); @@ -387,6 +388,15 @@ private TypeMirror unwrapTypeMirror(TypeMirror typeMirror) { return typeMirror; } + private String getType(TypeMirror typeMirror) { + if (typeMirror instanceof DeclaredType) { + DeclaredType declaredType = (DeclaredType) typeMirror; + TypeElement typeElement = (TypeElement) declaredType.asElement(); + return typeElement.getQualifiedName().toString(); + } + return typeMirror.toString(); + } + private boolean isConfigGroup(String type) { if (type.startsWith("java.") || PRIMITIVE_TYPES.contains(type)) { return false; From 00a4504803c5bbf698a39e000e2fc4d296737572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 18 Apr 2023 12:18:14 +0200 Subject: [PATCH 054/333] Fix OpenTelemetryJdbcInstrumentationTest flaky test --- .../src/main/resources/application.properties | 4 +++ .../OpenTelemetryJdbcInstrumentationTest.java | 29 ++++++++++--------- ...sOpenTelemetryJdbcInstrumentationTest.java | 2 -- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties b/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties index e26c2fced8a45f..93429306dc891c 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/main/resources/application.properties @@ -40,3 +40,7 @@ quarkus.hibernate-orm.db2.database.generation=none quarkus.datasource.db2.db-kind=db2 quarkus.datasource.db2.jdbc.max-size=1 quarkus.datasource.db2.jdbc.telemetry=true + +# speed up build +quarkus.otel.bsp.schedule.delay=100 +quarkus.otel.bsp.export.timeout=5s diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryJdbcInstrumentationTest.java b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryJdbcInstrumentationTest.java index 41a27b3731d8b3..8dde3f5b0ac029 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryJdbcInstrumentationTest.java +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryJdbcInstrumentationTest.java @@ -5,6 +5,7 @@ import static java.net.HttpURLConnection.HTTP_OK; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; @@ -47,22 +48,24 @@ protected void testQueryTraced(String dbKind, String expectedTable) { .statusCode(200) .body("message", Matchers.equalTo("Hit message.")); - Awaitility.await().atMost(Duration.ofSeconds(55)).until(() -> !getSpans().isEmpty()); + Awaitility.await().atMost(Duration.ofSeconds(55)).untilAsserted(() -> { + assertFalse(getSpans().isEmpty()); - // Assert insert has been traced - boolean hitInserted = false; - for (Map spanData : getSpans()) { - if (spanData.get("attributes") instanceof Map) { - final Map attributes = (Map) spanData.get("attributes"); - var dbOperation = attributes.get("db.operation"); - var dbTable = attributes.get("db.sql.table"); - if ("INSERT".equals(dbOperation) && expectedTable.equals(dbTable)) { - hitInserted = true; - break; + // Assert insert has been traced + boolean hitInserted = false; + for (Map spanData : getSpans()) { + if (spanData.get("attributes") instanceof Map) { + final Map attributes = (Map) spanData.get("attributes"); + var dbOperation = attributes.get("db.operation"); + var dbTable = attributes.get("db.sql.table"); + if ("INSERT".equals(dbOperation) && expectedTable.equals(dbTable)) { + hitInserted = true; + break; + } } } - } - assertTrue(hitInserted, "JDBC insert statement was not traced."); + assertTrue(hitInserted, "JDBC insert statement was not traced."); + }); } } diff --git a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgresOpenTelemetryJdbcInstrumentationTest.java b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgresOpenTelemetryJdbcInstrumentationTest.java index 6deaade8b59d2b..cde8956e413eb1 100644 --- a/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgresOpenTelemetryJdbcInstrumentationTest.java +++ b/integration-tests/opentelemetry-jdbc-instrumentation/src/test/java/io/quarkus/it/opentelemetry/PostgresOpenTelemetryJdbcInstrumentationTest.java @@ -1,6 +1,5 @@ package io.quarkus.it.opentelemetry; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.quarkus.test.common.QuarkusTestResource; @@ -8,7 +7,6 @@ @QuarkusTest @QuarkusTestResource(value = PostgreSqlLifecycleManager.class, restrictToAnnotatedClass = true) -@Disabled("flaky test") public class PostgresOpenTelemetryJdbcInstrumentationTest extends OpenTelemetryJdbcInstrumentationTest { @Test From 81cc1ea7837b6e712c3b8a61ab4db642ccd9ddce Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Thu, 9 Mar 2023 14:20:24 +0100 Subject: [PATCH 055/333] Update to Vert.x 4.4.1 - Update Netty to version 4.1.90.Final - Update Vert.x to version 4.4.1 - Update Mutiny bindings to version 3.3.0 - Update Quarkus HTTP to version 5.0.2.Final --- bom/application/pom.xml | 9 ++++----- .../reactive/oracle/client/DevModeResource.java | 3 ++- .../oracle/client/ReactiveOracleReloadTest.java | 2 ++ .../devmode/console/DevConsoleProcessor.java | 4 ++-- .../http/runtime/filters/AbstractResponseWrapper.java | 10 ++++++++++ .../vertx/core/runtime/graal/VertxSubstitutions.java | 9 +++++---- .../client/handlers/ClientSendRequestHandler.java | 10 +++++++++- .../resteasy/reactive/client/impl/ClientImpl.java | 11 +++++++++++ independent-projects/resteasy-reactive/pom.xml | 2 +- .../test/java/io/quarkus/it/vertx/Http2TestCase.java | 2 +- 10 files changed, 47 insertions(+), 15 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 55f392a06bcd03..1dcacc2f86d683 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -39,7 +39,7 @@ 1.23.1 1.23.0-alpha 1.8.1 - 5.0.1.Final + 5.0.2.Final 1.10.5 2.1.12 0.22.0 @@ -67,7 +67,7 @@ 2.1.0 1.0.13 3.0.0 - 3.2.0 + 3.3.0 4.4.0 2.1.0 2.1.1 @@ -119,8 +119,7 @@ 1.0.1.Final 2.1.0.Final 3.5.0.Final - 4.3.7 - + 4.4.1 4.5.14 4.4.16 4.1.5 @@ -142,7 +141,7 @@ 14.0.8.Final 4.6.2.Final 3.1.5 - 4.1.87.Final + 4.1.90.Final 1.11.0 1.0.4 3.5.0.Final diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java index 2dd4221f96c866..9a7db850fdd0b4 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java @@ -11,6 +11,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import io.vertx.oracleclient.OracleException; import io.vertx.oracleclient.OraclePool; @Path("/dev") @@ -25,7 +26,7 @@ public class DevModeResource { public CompletionStage checkConnectionFailure() throws SQLException { CompletableFuture future = new CompletableFuture<>(); client.query("SELECT 1 FROM DUAL").execute(ar -> { - Class expectedExceptionClass = SQLException.class; + Class expectedExceptionClass = OracleException.class; if (ar.succeeded()) { future.complete(Response.serverError().entity("Expected SQL query to fail").build()); } else if (!expectedExceptionClass.isAssignableFrom(ar.cause().getClass())) { diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java index b84018f654060a..a953a815000345 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java @@ -1,12 +1,14 @@ package io.quarkus.reactive.oracle.client; import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.test.QuarkusDevModeTest; import io.restassured.RestAssured; +@Disabled("Failing on CI but working locally - must be investigated") public class ReactiveOracleReloadTest { @RegisterExtension diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index ef7764b945efda..ef12ca50856a99 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -117,8 +117,8 @@ import io.vertx.core.impl.VertxBuilder; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.VertxThread; +import io.vertx.core.impl.transports.JDKTransport; import io.vertx.core.net.impl.VertxHandler; -import io.vertx.core.net.impl.transport.Transport; import io.vertx.core.spi.VertxThreadFactory; import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; @@ -238,7 +238,7 @@ public VertxThread newVertxThread(Runnable target, String name, boolean worker, return new VertxThread(target, "[DevConsole]" + name, worker, maxExecTime, maxExecTimeUnit); } }); - builder.transport(Transport.JDK); + builder.findTransport(JDKTransport.INSTANCE); builder.init(); return builder.vertx(); } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/filters/AbstractResponseWrapper.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/filters/AbstractResponseWrapper.java index d64a3311bfcd9d..959af552f0dc73 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/filters/AbstractResponseWrapper.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/filters/AbstractResponseWrapper.java @@ -198,6 +198,16 @@ public HttpServerResponse writeContinue() { return this; } + @Override + public Future writeEarlyHints(MultiMap headers) { + return delegate.writeEarlyHints(headers); + } + + @Override + public void writeEarlyHints(MultiMap headers, Handler> handler) { + delegate.writeEarlyHints(headers, handler); + } + @Override public Future end(String chunk) { return delegate.end(chunk); diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java index 0a7313ca0473b9..14b5ef952116ce 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/VertxSubstitutions.java @@ -30,15 +30,16 @@ import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.resolver.DefaultResolverProvider; +import io.vertx.core.impl.transports.JDKTransport; import io.vertx.core.net.NetServerOptions; -import io.vertx.core.net.impl.transport.Transport; import io.vertx.core.spi.resolver.ResolverProvider; +import io.vertx.core.spi.transport.Transport; -@TargetClass(className = "io.vertx.core.net.impl.transport.Transport") -final class Target_io_vertx_core_net_impl_transport_Transport { +@TargetClass(className = "io.vertx.core.impl.VertxBuilder") +final class Target_io_vertx_core_impl_VertxBuilder { @Substitute public static Transport nativeTransport() { - return Transport.JDK; + return JDKTransport.INSTANCE; } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java index 30df0dde5d5f57..2e534c08d8cd05 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java @@ -58,6 +58,7 @@ import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; +import io.vertx.core.http.HttpClosedException; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpVersion; import io.vertx.core.http.RequestOptions; @@ -375,7 +376,14 @@ public void handle(Buffer buffer) { .onFailure(new Handler<>() { @Override public void handle(Throwable failure) { - if (failure instanceof IOException) { + if (failure instanceof HttpClosedException) { + // This is because of the Rest Client TCK + // HttpClosedException is a runtime exception. If we complete with that exception, it gets + // unwrapped by the rest client proxy and thus fails the TCK. + // By creating an IOException, we avoid that and provide a meaningful exception (because + // it's an I/O exception) + requestContext.resume(new ProcessingException(new IOException(failure.getMessage()))); + } else if (failure instanceof IOException) { requestContext.resume(new ProcessingException(failure)); } else { requestContext.resume(failure); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java index e96e589754c263..476a8f662b887e 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java @@ -74,6 +74,7 @@ import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetServer; import io.vertx.core.net.NetServerOptions; +import io.vertx.core.net.SSLOptions; import io.vertx.core.shareddata.SharedData; import io.vertx.core.spi.VerticleFactory; @@ -698,6 +699,11 @@ public boolean isNativeTransportEnabled() { return getDelegate().isNativeTransportEnabled(); } + @Override + public Throwable unavailableNativeTransportCause() { + return getDelegate().unavailableNativeTransportCause(); + } + @Override public Vertx exceptionHandler(Handler handler) { return getDelegate().exceptionHandler(handler); @@ -829,6 +835,11 @@ public Future webSocketAbs(String url, MultiMap headers, WebsocketVer return getDelegate().webSocketAbs(url, headers, version, subProtocols); } + @Override + public Future updateSSLOptions(SSLOptions options) { + return getDelegate().updateSSLOptions(options); + } + @Override public HttpClient connectionHandler(Handler handler) { return getDelegate().connectionHandler(handler); diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 4edd0724deab77..155ee9656caffd 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -63,7 +63,7 @@ 1.6.8 2.1.0 2.1.0 - 4.3.8 + 4.4.1 5.3.0 1.0.0.Final 2.14.2 diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/Http2TestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/Http2TestCase.java index d140349416e12b..58ccc474cd1d33 100644 --- a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/Http2TestCase.java +++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/Http2TestCase.java @@ -49,7 +49,7 @@ public void testHttp2EnabledSslWithNotSelectedClientCert() throws ExecutionExcep ExecutionException exc = Assertions.assertThrows(ExecutionException.class, () -> runHttp2EnabledSsl("client-keystore-2.jks")); - Assertions.assertEquals("SSLHandshakeException: Received fatal alert: bad_certificate", + Assertions.assertEquals("VertxException: Connection was closed", ExceptionUtils.getRootCauseMessage(exc)); } From bdf1bc00727b31f89dadf5d2149b3b3af0f75e41 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 17 Apr 2023 15:41:48 +0100 Subject: [PATCH 056/333] Upgrade to Hibernate ORM 6.2.1.Final --- bom/application/pom.xml | 2 +- .../boot/registry/PreconfiguredServiceRegistryBuilder.java | 4 ++++ .../service/StandardHibernateORMInitiatorListProvider.java | 7 ++++++- .../PreconfiguredReactiveServiceRegistryBuilder.java | 4 ++++ .../registry/ReactiveHibernateInitiatorListProvider.java | 4 ++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 55f392a06bcd03..5c47f55841d33d 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -97,7 +97,7 @@ 3.12.0 1.15 1.5.1 - 6.2.0.Final + 6.2.1.Final 1.12.18 6.0.6.Final 2.0.0.Beta2 diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java index eef1d7cc1f4331..0cd850422385d0 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java @@ -17,6 +17,7 @@ import org.hibernate.engine.jdbc.cursor.internal.RefCursorSupportInitiator; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator; +import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; import org.hibernate.integrator.spi.Integrator; import org.hibernate.persister.internal.PersisterClassResolverInitiator; @@ -234,6 +235,9 @@ private static List> buildQuarkusServiceInitiatorLis // Default implementation serviceInitiators.add(ParameterMarkerStrategyInitiator.INSTANCE); + // Default implementation + serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); + serviceInitiators.trimToSize(); return serviceInitiators; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java index bb499897a92cb1..7d92e318ad25ec 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java @@ -12,6 +12,7 @@ import org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator; +import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; import org.hibernate.persister.internal.PersisterClassResolverInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; @@ -34,7 +35,8 @@ * Hibernate ORM when running on Quarkus. * WARNING: this is a customized list: we started from a copy of ORM's standard * list, then changes have evolved. - * Also: Hibernate Reactive uses a different list. + * Also: Hibernate Reactive uses a different list, and there's an additional definition of + * services in PreconfiguredServiceRegistryBuilder. */ public final class StandardHibernateORMInitiatorListProvider implements InitialInitiatorListProvider { @@ -104,6 +106,9 @@ public List> initialInitiatorList() { // Default implementation serviceInitiators.add(ParameterMarkerStrategyInitiator.INSTANCE); + // Default implementation + serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); + serviceInitiators.trimToSize(); return serviceInitiators; diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java index 5de1999a0e3b65..a5be0f364df0d3 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java @@ -16,6 +16,7 @@ import org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator; import org.hibernate.engine.jdbc.cursor.internal.RefCursorSupportInitiator; import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator; +import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; import org.hibernate.integrator.spi.Integrator; import org.hibernate.persister.internal.PersisterFactoryInitiator; @@ -241,6 +242,9 @@ private static List> buildQuarkusServiceInitiatorLis // Custom for Hibernate Reactive: ParameterMarkerStrategy serviceInitiators.add(NativeParametersHandling.INSTANCE); + // Default implementation + serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); + serviceInitiators.trimToSize(); return serviceInitiators; } diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java index 7334d7f96b905c..fa57b9e8b3bde8 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java @@ -12,6 +12,7 @@ import org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator; +import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; @@ -123,6 +124,9 @@ public List> initialInitiatorList() { // Custom for Hibernate Reactive: ParameterMarkerStrategy serviceInitiators.add(NativeParametersHandling.INSTANCE); + // Default implementation + serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); + serviceInitiators.trimToSize(); return serviceInitiators; } From e20b37dae461d48094801ed6c325e328bcefec17 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 18 Apr 2023 12:31:14 +0100 Subject: [PATCH 057/333] Only provide `quarkus.datasource.jdbc.url` from DevServices when Agroal is available --- .../DevServicesDatasourceProcessor.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java index 83af2b468d637a..bfeafc14aa2cf5 100644 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java @@ -23,6 +23,8 @@ import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -57,7 +59,9 @@ public class DevServicesDatasourceProcessor { static volatile boolean first = true; @BuildStep - DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem curateOutcomeBuildItem, + DevServicesDatasourceResultBuildItem launchDatabases( + Capabilities capabilities, + CurateOutcomeBuildItem curateOutcomeBuildItem, DockerStatusBuildItem dockerStatusBuildItem, List installedDrivers, List devDBProviders, @@ -133,7 +137,7 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura Map devDBProviderMap = devDBProviders.stream() .collect(Collectors.toMap(DevServicesDatasourceProviderBuildItem::getDatabase, DevServicesDatasourceProviderBuildItem::getDevServicesProvider)); - RunningDevService defaultDevService = startDevDb(null, curateOutcomeBuildItem, installedDrivers, + RunningDevService defaultDevService = startDevDb(null, capabilities, curateOutcomeBuildItem, installedDrivers, !dataSourceBuildTimeConfig.namedDataSources.isEmpty(), devDBProviderMap, dataSourceBuildTimeConfig.defaultDataSource, @@ -145,7 +149,7 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura } defaultResult = toDbResult(defaultDevService); for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) { - RunningDevService namedDevService = startDevDb(entry.getKey(), curateOutcomeBuildItem, + RunningDevService namedDevService = startDevDb(entry.getKey(), capabilities, curateOutcomeBuildItem, installedDrivers, true, devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, dockerStatusBuildItem, @@ -192,7 +196,9 @@ private String trim(String optional) { return optional.trim(); } - private RunningDevService startDevDb(String dbName, + private RunningDevService startDevDb( + String dbName, + Capabilities capabilities, CurateOutcomeBuildItem curateOutcomeBuildItem, List installedDrivers, boolean hasNamedDatasources, @@ -314,8 +320,17 @@ private RunningDevService startDevDb(String dbName, Map devDebProperties = new HashMap<>(); for (DevServicesDatasourceConfigurationHandlerBuildItem devDbConfigurationHandlerBuildItem : configHandlers) { - devDebProperties.putAll(devDbConfigurationHandlerBuildItem.getConfigProviderFunction() - .apply(dbName, datasource)); + Map properties = devDbConfigurationHandlerBuildItem.getConfigProviderFunction().apply(dbName, + datasource); + for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey().contains(".jdbc.") && entry.getKey().endsWith(".url")) { + if (capabilities.isCapabilityWithPrefixPresent(Capability.AGROAL)) { + devDebProperties.put(entry.getKey(), entry.getValue()); + } + } else { + devDebProperties.put(entry.getKey(), entry.getValue()); + } + } } setDataSourceProperties(devDebProperties, dbName, "db-kind", defaultDbKind.get()); if (datasource.getUsername() != null) { From bc5b77b3faa2b4b1d77e6199b4966d13e740eda5 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 18 Apr 2023 13:40:41 -0300 Subject: [PATCH 058/333] Remove unused .dockerignore - This seems to be unused and a leftover from https://github.com/quarkusio/quarkus/commit/352a9f9ec6ac74fb4fc869eff6e99e62a98854ff --- .dockerignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index eb5a316cbd195d..00000000000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -target From ea05dbfcb06160d061f8ce109da156fa4c67f6f4 Mon Sep 17 00:00:00 2001 From: Felipe Henrique Gross Windmoller Date: Tue, 18 Apr 2023 14:52:43 -0300 Subject: [PATCH 059/333] Fix guide oidc trust-store config parameter name --- .../asciidoc/security-openid-connect-client-reference.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 626da1b83a4e4c..06c36042845f8c 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -721,8 +721,8 @@ quarkus.oidc.client.tls.key-store-password=${key-store-password} #quarkus.oidc.client.tls.key-store-alias-password=keyAliasPassword # Truststore configuration -quarkus.oidc.client.tls.trust-store-file=client-truststore.jks -quarkus.oidc.client.tls.trust-store-password=${trust-store-password} +quarkus.oidc-client.tls.trust-store-file=client-truststore.jks +quarkus.oidc-client.tls.trust-store-password=${trust-store-password} # Add more truststore properties if needed: #quarkus.oidc.client.tls.trust-store-alias=certAlias ---- From fb56fc8e7ccc6e81c66d244558a4e94b21ada40b Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 18 Apr 2023 18:20:53 +0100 Subject: [PATCH 060/333] Make it possible to test Google OIDC from DevUI --- .../io/quarkus/oidc/common/runtime/OidcConstants.java | 2 ++ .../devservices/AbstractDevConsoleProcessor.java | 4 ++++ .../devservices/OidcDevConsoleProcessor.java | 3 ++- .../src/main/resources/dev-templates/provider.html | 9 +++++---- .../oidc/runtime/CodeAuthenticationMechanism.java | 2 +- .../oidc/runtime/OidcConfigPropertySupplier.java | 10 ++++++++++ 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java index 04fce11c4d0d3c..39d66426f92d0d 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java @@ -68,4 +68,6 @@ public final class OidcConstants { public static final String BACK_CHANNEL_LOGOUT_SID_CLAIM = "sid"; public static final String FRONT_CHANNEL_LOGOUT_SID_PARAM = "sid"; public static final String ID_TOKEN_SID_CLAIM = "sid"; + + public static final String OPENID_SCOPE = "openid"; } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java index 99568273ad37f4..2e6f427080efb4 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java @@ -18,6 +18,7 @@ public abstract class AbstractDevConsoleProcessor { protected static final String TOKEN_PATH_CONFIG_KEY = CONFIG_PREFIX + "token-path"; protected static final String END_SESSION_PATH_CONFIG_KEY = CONFIG_PREFIX + "end-session-path"; protected static final String POST_LOGOUT_URI_PARAM_CONFIG_KEY = CONFIG_PREFIX + "logout.post-logout-uri-param"; + protected static final String SCOPES_KEY = CONFIG_PREFIX + "authentication.scopes"; protected void produceDevConsoleTemplateItems(Capabilities capabilities, BuildProducer devConsoleTemplate, @@ -66,6 +67,9 @@ protected void produceDevConsoleTemplateItems(Capabilities capabilities, new DevConsoleRuntimeTemplateInfoBuildItem("postLogoutUriParam", new OidcConfigPropertySupplier(POST_LOGOUT_URI_PARAM_CONFIG_KEY), this.getClass(), curateOutcomeBuildItem)); + devConsoleRuntimeInfo.produce( + new DevConsoleRuntimeTemplateInfoBuildItem("scopes", + new OidcConfigPropertySupplier(SCOPES_KEY), this.getClass(), curateOutcomeBuildItem)); } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java index c0679b768a2ac1..84f96c04657684 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java @@ -101,7 +101,8 @@ public void run() { metadata != null ? metadata.getString("authorization_endpoint") : null, metadata != null ? metadata.getString("token_endpoint") : null, metadata != null ? metadata.getString("end_session_endpoint") : null, - metadata != null ? metadata.containsKey("introspection_endpoint") : false); + metadata != null ? metadata.containsKey("introspection_endpoint") + || metadata.containsKey("userinfo_endpoint") : false); produceDevConsoleRouteItems(devConsoleRoute, new OidcTestServiceHandler(vertxInstance, oidcConfig.devui.webClientTimeout), diff --git a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html index efd73823816177..0006e4572e6038 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html +++ b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html @@ -10,7 +10,7 @@ var port = {config:property('quarkus.http.port')}; -{#if info:oidcApplicationType is 'service'} +{#if info:oidcApplicationType is 'service' || info:oidcApplicationType is 'hybrid'} var devRoot = '{devRootAppend}'; var encodedDevRoot = devRoot.replaceAll("/", "%2F"); @@ -40,7 +40,7 @@ $('.loginError').hide(); $('.implicitLoggedIn').show(); var search = window.location.search; - var code = search.match(/code=([^&]+)/)[1]; + var code = decodeURIComponent(search.match(/code=([^&]+)/)[1]); var state = search.match(/state=([^&]+)/)[1]; exchangeCodeForTokens(code, state); }else if(errorInUrl()){ @@ -103,6 +103,7 @@ var address; var state; var clientId = getClientId(); + var scopes = '{info:scopes??}'; {#if info:keycloakAdminUrl?? && info:keycloakRealms??} address = '{info:keycloakAdminUrl??}' + "/realms/" + $('#keycloakRealm').val() + "/protocol/openid-connect/auth"; state = makeid() + "_" + $('#keycloakRealm').val() + "_" + clientId; @@ -114,14 +115,14 @@ window.location.href = address + "?client_id=" + clientId + "&redirect_uri=" + "http%3A%2F%2Flocalhost%3A" + port + encodedDevRoot + "%2Fio.quarkus.quarkus-oidc%2Fprovider" - + "&scope=openid&response_type=token id_token&response_mode=query&prompt=login" + + "&scope=" + scopes + "&response_type=token id_token&response_mode=query&prompt=login" + "&nonce=" + makeid() + "&state=" + state; {#else} window.location.href = address + "?client_id=" + clientId + "&redirect_uri=" + "http%3A%2F%2Flocalhost%3A" + port + encodedDevRoot + "%2Fio.quarkus.quarkus-oidc%2Fprovider" - + "&scope=openid&response_type=code&response_mode=query&prompt=login" + + "&scope=" + scopes + "&response_type=code&response_mode=query&prompt=login" + "&nonce=" + makeid() + "&state=" + state; {/if} 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 0dde01e5b97c2c..e76abffd709e74 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 @@ -533,7 +533,7 @@ && isRedirectFromProvider(context, configContext)) { : Collections.emptyList(); List scopes = new ArrayList<>(oidcConfigScopes.size() + 1); if (configContext.oidcConfig.getAuthentication().addOpenidScope.orElse(true)) { - scopes.add("openid"); + scopes.add(OidcConstants.OPENID_SCOPE); } scopes.addAll(oidcConfigScopes); codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java index 9220acadc32a68..bc7384b392e10a 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java @@ -1,15 +1,18 @@ package io.quarkus.oidc.runtime; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.oidc.common.runtime.OidcCommonUtils; +import io.quarkus.oidc.common.runtime.OidcConstants; public class OidcConfigPropertySupplier implements Supplier { private static final String AUTH_SERVER_URL_CONFIG_KEY = "quarkus.oidc.auth-server-url"; private static final String END_SESSION_PATH_KEY = "quarkus.oidc.end-session-path"; + private static final String SCOPES_KEY = "quarkus.oidc.authentication.scopes"; private String oidcConfigProperty; private String defaultValue; private boolean urlProperty; @@ -40,6 +43,13 @@ public String get() { return checkUrlProperty(value); } return defaultValue; + } else if (SCOPES_KEY.equals(oidcConfigProperty)) { + Optional> scopes = ConfigProvider.getConfig().getOptionalValues(oidcConfigProperty, String.class); + if (scopes.isPresent()) { + return OidcCommonUtils.urlEncode(String.join(" ", scopes.get())); + } else { + return OidcConstants.OPENID_SCOPE; + } } else { return checkUrlProperty(ConfigProvider.getConfig().getOptionalValue(oidcConfigProperty, String.class)); } From 6195e559c699380a0e3d26cf92db6344c58b1ab2 Mon Sep 17 00:00:00 2001 From: Alex Martel <13215031+manofthepeace@users.noreply.github.com> Date: Tue, 18 Apr 2023 15:13:38 -0400 Subject: [PATCH 061/333] Add missing static import in config interceptor doc --- docs/src/main/asciidoc/config-extending-support.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/config-extending-support.adoc b/docs/src/main/asciidoc/config-extending-support.adoc index ae4f9035d3586c..80a84b2fa5aec3 100644 --- a/docs/src/main/asciidoc/config-extending-support.adoc +++ b/docs/src/main/asciidoc/config-extending-support.adoc @@ -368,6 +368,8 @@ The `ConfigSourceInterceptorFactory` may initialize an interceptor with access t ---- package org.acme.config; +import static io.smallrye.config.SecretKeys.doLocked; + import jakarta.annotation.Priority; import io.smallrye.config.ConfigSourceInterceptor; From 0cf9384bd5d66a8651edd008b943209099e55613 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Apr 2023 22:04:47 +0000 Subject: [PATCH 062/333] Bump grpc.version from 1.54.0 to 1.54.1 Bumps `grpc.version` from 1.54.0 to 1.54.1. Updates `grpc-bom` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) Updates `protoc-gen-grpc-java` from 1.54.0 to 1.54.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.0...v1.54.1) --- updated-dependencies: - dependency-name: io.grpc:grpc-bom dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:linux-aarch_64 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:linux-x86_32 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:linux-x86_64 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:osx-x86_64 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:osx-aarch_64 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:windows-x86_32 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.grpc:protoc-gen-grpc-java:windows-x86_64 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 fd6cc716e19ea3..7d84fb426b9699 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 6.5.1 - 1.54.0 + 1.54.1 1.2.1 3.22.0 ${protoc.version} From 9d34eaa129fda13a46320a17fe90a98d15fc5367 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Apr 2023 22:05:33 +0000 Subject: [PATCH 063/333] Bump aws-xray-recorder-sdk-aws-sdk-v2 from 2.13.0 to 2.14.0 Bumps [aws-xray-recorder-sdk-aws-sdk-v2](https://github.com/aws/aws-xray-sdk-java) from 2.13.0 to 2.14.0. - [Release notes](https://github.com/aws/aws-xray-sdk-java/releases) - [Changelog](https://github.com/aws/aws-xray-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-xray-sdk-java/compare/v2.13.0...v2.14.0) --- updated-dependencies: - dependency-name: com.amazonaws:aws-xray-recorder-sdk-aws-sdk-v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 55f392a06bcd03..6df68dc78a77fc 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -155,7 +155,7 @@ 2.13.10 1.2.2 3.11.1 - 2.13.0 + 2.14.0 2.2.0 1.0.0 1.8.10 From b63fd259ba67792648027065d995b3ae28f43dc7 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 19 Apr 2023 11:06:52 +1000 Subject: [PATCH 064/333] Dev UI - add logger level to server log Signed-off-by: Phillip Kruger --- .../deployment/BuildTimeContentProcessor.java | 32 +++++ .../resources/dev-ui/qwc/qwc-server-log.js | 121 ++++++++++++++++-- .../logstream/LogStreamJsonRPCService.java | 68 ++++++++++ 3 files changed, 207 insertions(+), 14 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java index 8c86ff8afb119e..5fcf67a431e6ee 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java @@ -1,5 +1,20 @@ package io.quarkus.devui.deployment; +import static java.util.logging.Level.ALL; +import static java.util.logging.Level.CONFIG; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.FINER; +import static java.util.logging.Level.FINEST; +import static java.util.logging.Level.OFF; +import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; +import static org.jboss.logmanager.Level.DEBUG; +import static org.jboss.logmanager.Level.ERROR; +import static org.jboss.logmanager.Level.FATAL; +import static org.jboss.logmanager.Level.INFO; +import static org.jboss.logmanager.Level.TRACE; +import static org.jboss.logmanager.Level.WARN; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -379,6 +394,7 @@ private void addFooterTabBuildTimeData(BuildTimeConstBuildItem internalBuildTime } internalBuildTimeData.addBuildTimeData("footerTabs", footerTabs); + internalBuildTimeData.addBuildTimeData("loggerLevels", LEVELS); } private void addVersionInfoBuildTimeData(BuildTimeConstBuildItem internalBuildTimeData, @@ -394,6 +410,22 @@ private void addVersionInfoBuildTimeData(BuildTimeConstBuildItem internalBuildTi internalBuildTimeData.addBuildTimeData("applicationInfo", applicationInfo); } + private static final List LEVELS = List.of( + OFF.getName(), + SEVERE.getName(), + ERROR.getName(), + FATAL.getName(), + WARNING.getName(), + WARN.getName(), + INFO.getName(), + DEBUG.getName(), + TRACE.getName(), + CONFIG.getName(), + FINE.getName(), + FINER.getName(), + FINEST.getName(), + ALL.getName()); + private static void computeColors(Map> themes, Map dark, Map light) { // Quarkus logo colors diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js index 2396d0ee7c2a7f..9f1d10ffe35ccd 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js @@ -5,10 +5,16 @@ import { JsonRpc } from 'jsonrpc'; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; import '@vaadin/icon'; import '@vaadin/dialog'; +import '@vaadin/select'; import '@vaadin/checkbox'; import '@vaadin/checkbox-group'; import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js'; import 'qui-badge'; +import '@vaadin/grid'; +import { columnBodyRenderer } from '@vaadin/grid/lit.js'; +import '@vaadin/grid/vaadin-grid-sort-column.js'; +import '@vaadin/vertical-layout'; +import { loggerLevels } from 'devui-data'; /** * This component represent the Server Log @@ -73,13 +79,6 @@ export class QwcServerLog extends QwcHotReloadElement { color: var(--lumo-success-color-50pct); } - .columnsDialog{ - - } - - .levelsDialog{ - - } `; static properties = { @@ -91,10 +90,22 @@ export class QwcServerLog extends QwcHotReloadElement { _levelsDialogOpened: {state: true}, _columnsDialogOpened: {state: true}, _selectedColumns: {state: true}, + _allLoggers: {state: true, type: Array}, + _filteredLoggers: {state: true, type: Array}, + _loggerLevels: {state: false, type: Array} }; constructor() { super(); + this._loggerLevels = []; + for (let i in loggerLevels) { + let loggerLevel = loggerLevels[i]; + this._loggerLevels.push({ + 'label': loggerLevel, + 'value': loggerLevel, + }); + } + this.logControl .addToggle("On/off switch", true, (e) => { this._toggleOnOffClicked(e); @@ -129,6 +140,7 @@ export class QwcServerLog extends QwcHotReloadElement { this._addLogEntry(entry); }); }); + this._loadAllLoggers(); } disconnectedCallback() { @@ -136,11 +148,20 @@ export class QwcServerLog extends QwcHotReloadElement { this._toggleOnOff(false); } + _loadAllLoggers(){ + this.jsonRpc.getLoggers().then(jsonRpcResponse => { + this._allLoggers = jsonRpcResponse.result; + this._filteredLoggers = this._allLoggers; + }); + } + render() { - - return html` + if(this._filteredLoggers){ + return html` html` @@ -154,6 +175,8 @@ export class QwcServerLog extends QwcHotReloadElement { > html` @@ -176,7 +199,7 @@ export class QwcServerLog extends QwcHotReloadElement { ` )} `; - + } } _renderLogEntry(message){ @@ -379,11 +402,80 @@ export class QwcServerLog extends QwcHotReloadElement { } _renderLevelsDialog(){ - return html` - Hello levels - `; + if(this._filteredLoggers){ + return html` + + + + + + + + + + + + + `; + } } + _filterLoggers(e) { + const searchTerm = (e.detail.value || '').trim(); + if (searchTerm === '') { + this._filteredLoggers = this._allLoggers; + return; + } + + this._filteredLoggers = this._allLoggers.filter((level) => { + let i = this._matchLogger(level.name, searchTerm); + return i; + }); + } + + _matchLogger(value, term) { + if (!value) { + return false; + } + return value.toLowerCase().includes(term.toLowerCase()); + } + + _logLevelRenderer(logger){ + return html`${this._renderSelect(logger.name, logger.effectiveLevel)}`; + } + + _renderSelect(loggerName, loggerLevel){ + return html` + `; + } + + _logLevelSelectChanged(event){ + let name = event.target.id; + this._updateLogLevel(name, event.target.value); + } + + _updateLogLevel(name, value){ + this.jsonRpc.updateLogLevel({ + 'loggerName': name, + 'levelValue': value + }); + } + _renderColumnsDialog(){ return html` streamLog() { return logStreamBroadcaster.getLogStream(); } + @NonBlocking + public List getLoggers() { + LogContext logContext = LogContext.getLogContext(); + List values = new ArrayList<>(); + Enumeration loggerNames = logContext.getLoggerNames(); + while (loggerNames.hasMoreElements()) { + String loggerName = loggerNames.nextElement(); + JsonObject jsonObject = getLogger(loggerName); + if (jsonObject != null) { + values.add(jsonObject); + } + } + return values; + } + + @NonBlocking + public JsonObject getLogger(String loggerName) { + LogContext logContext = LogContext.getLogContext(); + if (loggerName != null && !loggerName.isEmpty()) { + Logger logger = logContext.getLogger(loggerName); + return JsonObject.of( + "name", loggerName, + "effectiveLevel", getEffectiveLogLevel(logger), + "configuredLevel", getConfiguredLogLevel(logger)); + } + return null; + } + + @NonBlocking + public JsonObject updateLogLevel(String loggerName, String levelValue) { + LogContext logContext = LogContext.getLogContext(); + Logger logger = logContext.getLogger(loggerName); + java.util.logging.Level level; + if (levelValue == null || levelValue.isBlank()) { + if (logger.getParent() != null) { + level = logger.getParent().getLevel(); + } else { + throw new IllegalArgumentException("The level of the root logger cannot be set to null"); + } + } else { + level = Level.parse(levelValue); + } + logger.setLevel(level); + LOG.info("Log level updated [" + loggerName + "] changed to [" + levelValue + "]"); + + return getLogger(loggerName); + } + + private String getConfiguredLogLevel(Logger logger) { + java.util.logging.Level level = logger.getLevel(); + return level != null ? level.getName() : null; + } + + private String getEffectiveLogLevel(Logger logger) { + if (logger == null) { + return null; + } + if (logger.getLevel() != null) { + return logger.getLevel().getName(); + } + return getEffectiveLogLevel(logger.getParent()); + } } From 4d183f9916235eb94a6345992c34f4d548d4d55a Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 19 Apr 2023 12:40:50 +1000 Subject: [PATCH 065/333] Dev UI DevService page to use new card component Signed-off-by: Phillip Kruger --- .../resources/dev-ui/qwc/qwc-dev-services.js | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js index f54e9d81a5ab02..16113ef4d330e2 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js @@ -1,8 +1,8 @@ import {LitElement, html, css} from 'lit'; import {devServices} from 'devui-data'; -import '@vaadin/vertical-layout'; import '@vaadin/icon'; import 'qui-code-block'; +import 'qui-card'; /** * This component shows the Dev Services Page @@ -11,29 +11,9 @@ export class QwcDevServices extends LitElement { static styles = css` .cards { height: 100%; + padding-right: 10px; } - .card { - display: flex; - flex-direction: column; - border: 1px solid var(--lumo-contrast-10pct); - border-radius: 4px; - margin-left: 30px; - margin-right: 30px; - } - - .card-header { - font-size: var(--lumo-font-size-l); - line-height: 1; - height: 25px; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px; - background-color: var(--lumo-contrast-5pct); - border-bottom: 1px solid var(--lumo-contrast-10pct); - } - + .configHeader { padding: 10px; } @@ -93,11 +73,12 @@ export class QwcDevServices extends LitElement { } _renderCard(devService){ - return html`
-
${devService.name}
- ${this._renderContainerDetails(devService)} - ${this._renderConfigDetails(devService)} -
`; + return html` +
+ ${this._renderContainerDetails(devService)} + ${this._renderConfigDetails(devService)} +
+
`; } _renderContainerDetails(devService){ From 99135b8511bfde2f7678dc5abf0ebb7187220e56 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 19 Apr 2023 13:54:47 +1000 Subject: [PATCH 066/333] Dev UI add label and icon to submenu Signed-off-by: Phillip Kruger --- .../dev-ui/controller/router-controller.js | 3 ++- .../main/resources/dev-ui/qwc/qwc-header.js | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js index f3f07f8ca09042..a0b6070bce0c9e 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js @@ -84,7 +84,8 @@ export class RouterController { subMenus.push({ "path" : pageRef, - "name" : pageForNamespace.title + "name" : pageForNamespace.title, // deprecate ? + "page" : pageForNamespace }); }); return { diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-header.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-header.js index 6f4d46f4a6cb26..720e89e91b5f22 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-header.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-header.js @@ -8,6 +8,7 @@ import { devuiState } from 'devui-state'; import '@vaadin/menu-bar'; import '@vaadin/tabs'; import '@vaadin/button'; +import 'qwc/qwc-extension-link.js'; /** * This component represent the Dev UI Header @@ -236,7 +237,9 @@ export class QwcHeader extends observeState(LitElement) { if(subMenu){ this._rightSideNav = html` ${subMenu.links.map(link => - html`${link.name}` + html` + ${this._renderSubMenuLink(link)} + ` )} `; }else{ @@ -248,6 +251,25 @@ export class QwcHeader extends observeState(LitElement) { } } + _renderSubMenuLink(link){ + + let relativePath = link.page.id.replace(link.page.namespace + "/", ""); + + return html` + `; + } + _reload(e) { fetch(devuiState.applicationInfo.contextRoot).then(response => { this.routerController.goHome(); From fcbdb8cd77abd5320bb53440990450d7f2dece9a Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 19 Apr 2023 08:31:07 +0300 Subject: [PATCH 067/333] Fix issue with quarkus.init-and-exit Fixes: #32729 --- .../init/InitializationTaskRecorder.java | 25 ++++++++++++++++++- .../arc/runtime/appcds/AppCDSRecorder.java | 22 +++------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java index 5a095f3d15ddf9..752efe3c9fcfa4 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java @@ -1,10 +1,14 @@ package io.quarkus.runtime.init; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.stream.StreamSupport; import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.runtime.PreventFurtherStepsException; +import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.annotations.Recorder; /** @@ -22,8 +26,27 @@ public void exitIfNeeded() { .anyMatch(n -> QUARKUS_INIT_AND_EXIT.equals(n)); if (initAndExitConfigured) { if (ConfigProvider.getConfig().getValue(QUARKUS_INIT_AND_EXIT, boolean.class)) { - throw new PreventFurtherStepsException("Gracefully exiting after initalization.", 0); + preventFurtherRecorderSteps(5, "Error attempting to gracefully shutdown after initialization", + () -> new PreventFurtherStepsException("Gracefully exiting after initialization.", 0)); } } } + + public static void preventFurtherRecorderSteps(int waitSeconds, String waitErrorMessage, + Supplier supplier) { + CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + Quarkus.blockingExit(); + latch.countDown(); + } + }).start(); + try { + latch.await(waitSeconds, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.err.println(waitErrorMessage); + } + throw supplier.get(); + } } diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java index ae93e5bed02f20..70e03db0d35455 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java @@ -1,31 +1,17 @@ package io.quarkus.arc.runtime.appcds; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - import io.quarkus.runtime.PreventFurtherStepsException; -import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.runtime.init.InitializationTaskRecorder; @Recorder public class AppCDSRecorder { public void controlGenerationAndExit() { if (Boolean.parseBoolean(System.getProperty("quarkus.appcds.generate", "false"))) { - CountDownLatch latch = new CountDownLatch(1); - new Thread(new Runnable() { - @Override - public void run() { - Quarkus.blockingExit(); - latch.countDown(); - } - }).start(); - try { - latch.await(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - System.err.println("Unable to properly shutdown Quarkus application when creating AppCDS"); - } - throw new PreventFurtherStepsException(); + InitializationTaskRecorder.preventFurtherRecorderSteps(5, + "Unable to properly shutdown Quarkus application when creating AppCDS", + PreventFurtherStepsException::new); } } } From 82cb5edc3a65cf190cf9c7aa224495a2ab5d486e Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 19 Apr 2023 10:00:59 +0200 Subject: [PATCH 068/333] Collect META-INF/resources only from the runtime classpath --- .../UndertowStaticResourcesBuildStep.java | 18 ++++++++++++------ .../classloading/QuarkusClassLoader.java | 10 +++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java b/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java index f0db4f8b07070a..5846c627da36ba 100644 --- a/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java +++ b/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Set; +import io.quarkus.bootstrap.classloading.ClassPathElement; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; @@ -22,7 +24,6 @@ import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.runtime.LaunchMode; -import io.quarkus.runtime.util.ClassPathUtils; /** * NOTE: Shared with Resteasy standalone! @@ -56,8 +57,8 @@ void scanStaticResources(Capabilities capabilities, ApplicationArchivesBuildItem } //we need to check for web resources in order to get welcome files to work //this kinda sucks - Set knownFiles = new HashSet<>(); - Set knownDirectories = new HashSet<>(); + final Set knownFiles = new HashSet<>(); + final Set knownDirectories = new HashSet<>(); for (ApplicationArchive i : applicationArchivesBuildItem.getAllApplicationArchives()) { i.accept(tree -> { Path resource = tree.getPath(META_INF_RESOURCES); @@ -67,9 +68,14 @@ void scanStaticResources(Capabilities capabilities, ApplicationArchivesBuildItem }); } - ClassPathUtils.consumeAsPaths(META_INF_RESOURCES, resource -> { - collectKnownPaths(resource, knownFiles, knownDirectories); - }); + for (ClassPathElement e : QuarkusClassLoader.getElements(META_INF_RESOURCES, false)) { + if (e.isRuntime()) { + e.apply(tree -> { + collectKnownPaths(tree.getPath(META_INF_RESOURCES), knownFiles, knownDirectories); + return null; + }); + } + } for (GeneratedWebResourceBuildItem genResource : generatedWebResources) { String sub = genResource.getName(); diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java index 440a8d6f5f2384..8b653f334f3c55 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java @@ -39,19 +39,19 @@ public class QuarkusClassLoader extends ClassLoader implements Closeable { registerAsParallelCapable(); } - public static List getElements(String resourceName, boolean localOnly) { + public static List getElements(String resourceName, boolean onlyFromCurrentClassLoader) { final ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (!(ccl instanceof QuarkusClassLoader)) { throw new IllegalStateException("The current classloader is not an instance of " + QuarkusClassLoader.class.getName() + " but " + ccl.getClass().getName()); } - return ((QuarkusClassLoader) ccl).getElementsWithResource(resourceName, localOnly); + return ((QuarkusClassLoader) ccl).getElementsWithResource(resourceName, onlyFromCurrentClassLoader); } - public List getAllElements(boolean localOnly) { + public List getAllElements(boolean onlyFromCurrentClassLoader) { List ret = new ArrayList<>(); - if (parent instanceof QuarkusClassLoader && !localOnly) { - ret.addAll(((QuarkusClassLoader) parent).getAllElements(localOnly)); + if (parent instanceof QuarkusClassLoader && !onlyFromCurrentClassLoader) { + ret.addAll(((QuarkusClassLoader) parent).getAllElements(onlyFromCurrentClassLoader)); } ret.addAll(elements); return ret; From 05787bcf5ed7cd698d07de217b0bcf75c63750ad Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 19 Apr 2023 10:20:21 +0200 Subject: [PATCH 069/333] Update openjdk container images to 1.15 --- .../deployment/RedHatOpenJDKRuntimeBaseProviderTest.java | 4 ++-- .../deployment/src/test/resources/openjdk-11-runtime | 2 +- .../deployment/src/test/resources/openjdk-17-runtime | 2 +- .../quarkus/container/image/jib/deployment/JibConfig.java | 4 ++-- .../container/image/jib/deployment/JibProcessor.java | 4 ++-- .../image/openshift/deployment/OpenshiftConfig.java | 8 ++++---- .../container/image/openshift/deployment/S2iConfig.java | 8 ++++---- .../dockerfiles/base/Dockerfile-layout.include.qute | 2 +- .../quarkus/QuarkusCodestartGenerationTest.java | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java index 489beb17221cb2..b7b1f61120dfdc 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java +++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java @@ -16,7 +16,7 @@ void testImageWithJava11() { Path path = getPath("openjdk-11-runtime"); var result = sut.determine(path); assertThat(result).hasValueSatisfying(v -> { - assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14"); + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15"); assertThat(v.getJavaVersion()).isEqualTo(11); }); } @@ -26,7 +26,7 @@ void testImageWithJava17() { Path path = getPath("openjdk-17-runtime"); var result = sut.determine(path); assertThat(result).hasValueSatisfying(v -> { - assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14"); + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15"); assertThat(v.getJavaVersion()).isEqualTo(17); }); } diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime index 0cbac0dfb55471..242b681bff8970 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14 +FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime index 1238ab8318d4a1..ae53314ed64b58 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime @@ -1,5 +1,5 @@ # Use Java 17 base image -FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14 +FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java index c5a8fea44e41d8..9b1ac89915b6ca 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java @@ -16,9 +16,9 @@ public class JibConfig { /** * The base image to be used when a container image is being produced for the jar build. * - * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14} + * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15} * is used as the default. - * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14} is used as the default. + * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15} is used as the default. */ @ConfigItem public Optional baseJvmImage; diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index 6b14aa638525ad..3bca8933391757 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -89,8 +89,8 @@ public class JibProcessor { private static final IsClassPredicate IS_CLASS_PREDICATE = new IsClassPredicate(); private static final String BINARY_NAME_IN_CONTAINER = "application"; - private static final String JAVA_17_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14"; - private static final String JAVA_11_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14"; + private static final String JAVA_17_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15"; + private static final String JAVA_11_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15"; private static final String DEFAULT_BASE_IMAGE_USER = "185"; private static final String OPENTELEMETRY_CONTEXT_CONTEXT_STORAGE_PROVIDER_SYS_PROP = "io.opentelemetry.context.contextStorageProvider"; diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java index 245c6bb941019e..5739ded667a7c5 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java @@ -15,8 +15,8 @@ @ConfigRoot(phase = ConfigPhase.BUILD_TIME) public class OpenshiftConfig { - public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.14"; - public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.14"; + public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.15"; + public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.15"; public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0"; public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application"; @@ -46,9 +46,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion /** * The base image to be used when a container image is being produced for the jar build. * - * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.14} + * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.15} * is used as the default. - * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.14} is used as the default. + * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.15} is used as the default. */ @ConfigItem public Optional baseJvmImage; diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java index b01459912145c9..098725815e8330 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java @@ -12,8 +12,8 @@ @ConfigRoot(phase = ConfigPhase.BUILD_TIME) public class S2iConfig { - public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.14"; - public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.14"; + public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.15"; + public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.15"; public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0"; public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application"; @@ -41,9 +41,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion /** * The base image to be used when a container image is being produced for the jar build. * - * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.14} + * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.15} * is used as the default. - * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.14} is used as the default. + * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.15} is used as the default. */ @ConfigItem public Optional baseJvmImage; diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute index 91fbf53ebbc5e4..279d54e968b887 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-{java.version}:1.14 +FROM registry.access.redhat.com/ubi8/openjdk-{java.version}:1.15 ENV LANGUAGE='en_US:en' diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java index b0a167d704e74a..5a0436c96b36a7 100644 --- a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java @@ -297,13 +297,13 @@ private void checkDockerfilesWithMaven(Path projectDir) { assertThat(projectDir.resolve("src/main/docker/Dockerfile.jvm")).exists() .satisfies(checkContains("./mvnw package")) .satisfies(checkContains("docker build -f src/main/docker/Dockerfile.jvm")) - .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.14"))//TODO: make a teste to java17 + .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.15"))//TODO: make a test for java17 .satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"")) .satisfies(checkNotContains("ENTRYPOINT")); assertThat(projectDir.resolve("src/main/docker/Dockerfile.legacy-jar")).exists() .satisfies(checkContains("./mvnw package -Dquarkus.package.type=legacy-jar")) .satisfies(checkContains("docker build -f src/main/docker/Dockerfile.legacy-jar")) - .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.14")) + .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.15")) .satisfies(checkContains("EXPOSE 8080")) .satisfies(checkContains("USER 185")) .satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"")) From 703ae09c3cdf7a18a3cc047f2e1eef986eae801a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 18 Apr 2023 12:57:53 +0200 Subject: [PATCH 070/333] Allow the OPTIONS method for static resources Fixes #32696 --- .../io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java index a6a841e585b474..7738d9bfe34cd9 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/StaticResourcesRecorder.java @@ -113,6 +113,7 @@ public void accept(Route route) { // No other HTTP methods should be used route.method(HttpMethod.GET); route.method(HttpMethod.HEAD); + route.method(HttpMethod.OPTIONS); for (Handler i : handlers) { route.handler(i); } From 527a44ef4e1f787ebfed4b3ce3431592f426bb76 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 18 Apr 2023 09:10:28 +0200 Subject: [PATCH 071/333] ArC - support multiple interceptor methods in a class hierarchy --- .../arc/processor/InterceptorGenerator.java | 182 +++++++++++----- .../arc/processor/InterceptorInfo.java | 197 ++++++++++++++---- .../arc/processor/MethodDescriptors.java | 8 + .../arc/impl/AbstractInvocationContext.java | 2 +- .../arc/impl/InnerInvocationContext.java | 155 ++++++++++++++ .../quarkus/arc/impl/InvocationContexts.java | 14 ++ .../arc/impl/SuperclassInvocationContext.java | 58 ++++++ .../inheritance/hierarchy/AlphaBinding.java | 19 ++ .../hierarchy/AlphaInterceptor.java | 43 ++++ .../inheritance/hierarchy/Bravo.java | 36 ++++ .../inheritance/hierarchy/Charlie.java | 34 +++ ...rceptorMethodDeclaredOnSuperclassTest.java | 69 ++++++ .../SuperclassInterceptorMethodsTest.java | 54 +++++ 13 files changed, 776 insertions(+), 95 deletions(-) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaBinding.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaInterceptor.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Bravo.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Charlie.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/MultipleInterceptorMethodDeclaredOnSuperclassTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/SuperclassInterceptorMethodsTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java index 0ce5a09586659a..b710f6633ac999 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java @@ -5,12 +5,15 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; @@ -34,6 +37,8 @@ import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FieldCreator; import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.FunctionCreator; +import io.quarkus.gizmo.Gizmo; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; @@ -111,7 +116,7 @@ Collection generate(InterceptorInfo interceptor) { createProviderFields(interceptorCreator, interceptor, injectionPointToProviderField, Collections.emptyMap(), Collections.emptyMap()); createConstructor(classOutput, interceptorCreator, interceptor, injectionPointToProviderField, - bindings.getFieldDescriptor(), reflectionRegistration); + bindings.getFieldDescriptor(), reflectionRegistration, isApplicationClass, providerType); implementGetIdentifier(interceptor, interceptorCreator); implementSupplierGet(interceptorCreator); @@ -139,8 +144,8 @@ Collection generate(InterceptorInfo interceptor) { } protected void createConstructor(ClassOutput classOutput, ClassCreator creator, InterceptorInfo interceptor, - Map injectionPointToProviderField, - FieldDescriptor bindings, ReflectionRegistration reflectionRegistration) { + Map injectionPointToProviderField, FieldDescriptor bindings, + ReflectionRegistration reflectionRegistration, boolean isApplicationClass, ProviderType providerType) { MethodCreator constructor = initConstructor(classOutput, creator, interceptor, injectionPointToProviderField, Collections.emptyMap(), Collections.emptyMap(), annotationLiterals, reflectionRegistration); @@ -156,9 +161,41 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator creator, annotationLiterals.create(constructor, bindingClass, bindingAnnotation)); } constructor.writeInstanceField(bindings, constructor.getThis(), bindingsHandle); + + // Initialize a list of BiFunction for each interception type if multiple interceptor methods are declared in a hierarchy + initInterceptorMethodsField(creator, constructor, InterceptionType.AROUND_INVOKE, interceptor.getAroundInvokes(), + providerType.className(), isApplicationClass); + initInterceptorMethodsField(creator, constructor, InterceptionType.AROUND_CONSTRUCT, interceptor.getAroundConstructs(), + providerType.className(), isApplicationClass); + initInterceptorMethodsField(creator, constructor, InterceptionType.POST_CONSTRUCT, interceptor.getPostConstructs(), + providerType.className(), isApplicationClass); + initInterceptorMethodsField(creator, constructor, InterceptionType.PRE_DESTROY, interceptor.getPreDestroys(), + providerType.className(), isApplicationClass); + constructor.returnValue(null); } + private void initInterceptorMethodsField(ClassCreator creator, MethodCreator constructor, InterceptionType interceptionType, + List methods, String interceptorClass, boolean isApplicationClass) { + if (methods.size() < 2) { + return; + } + FieldCreator field = creator.getFieldCreator(interceptorMethodsField(interceptionType), List.class) + .setModifiers(ACC_PRIVATE); + ResultHandle methodsList = constructor.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + for (MethodInfo method : methods) { + // BiFunction + FunctionCreator fun = constructor.createFunction(BiFunction.class); + BytecodeCreator funBytecode = fun.getBytecode(); + ResultHandle ret = invokeInterceptorMethod(funBytecode, interceptorClass, method, + interceptionType, isApplicationClass, funBytecode.getMethodParam(1), + funBytecode.getMethodParam(0)); + funBytecode.returnValue(interceptionType == InterceptionType.AROUND_INVOKE ? ret : funBytecode.loadNull()); + constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, methodsList, fun.getInstance()); + } + constructor.writeInstanceField(field.getFieldDescriptor(), constructor.getThis(), methodsList); + } + /** * * @see InjectableInterceptor#getInterceptorBindings() @@ -214,64 +251,105 @@ protected void implementIntercept(ClassCreator creator, InterceptorInfo intercep .getMethodCreator("intercept", Object.class, InterceptionType.class, Object.class, InvocationContext.class) .setModifiers(ACC_PUBLIC).addException(Exception.class); - addIntercept(intercept, interceptor.getAroundInvoke(), InterceptionType.AROUND_INVOKE, providerType, + addIntercept(creator, intercept, interceptor.getAroundInvokes(), InterceptionType.AROUND_INVOKE, providerType, reflectionRegistration, isApplicationClass); - addIntercept(intercept, interceptor.getPostConstruct(), InterceptionType.POST_CONSTRUCT, providerType, + addIntercept(creator, intercept, interceptor.getPostConstructs(), InterceptionType.POST_CONSTRUCT, providerType, reflectionRegistration, isApplicationClass); - addIntercept(intercept, interceptor.getPreDestroy(), InterceptionType.PRE_DESTROY, providerType, + addIntercept(creator, intercept, interceptor.getPreDestroys(), InterceptionType.PRE_DESTROY, providerType, reflectionRegistration, isApplicationClass); - addIntercept(intercept, interceptor.getAroundConstruct(), InterceptionType.AROUND_CONSTRUCT, providerType, + addIntercept(creator, intercept, interceptor.getAroundConstructs(), InterceptionType.AROUND_CONSTRUCT, providerType, reflectionRegistration, isApplicationClass); intercept.returnValue(intercept.loadNull()); } - private void addIntercept(MethodCreator intercept, MethodInfo interceptorMethod, InterceptionType interceptionType, - ProviderType providerType, - ReflectionRegistration reflectionRegistration, boolean isApplicationClass) { - if (interceptorMethod != null) { - ResultHandle enumValue = intercept - .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), - InterceptionType.class.getName())); - BranchResult result = intercept.ifNonZero( - intercept.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, intercept.getMethodParam(0))); - BytecodeCreator trueBranch = result.trueBranch(); - Class retType = null; - if (InterceptionType.AROUND_INVOKE.equals(interceptionType)) { - retType = Object.class; - } else { - // @PostConstruct, @PreDestroy, @AroundConstruct - retType = interceptorMethod.returnType().kind().equals(Type.Kind.VOID) ? void.class : Object.class; - } - ResultHandle ret; - // Check if interceptor method uses InvocationContext or ArcInvocationContext - Class invocationContextClass; - if (interceptorMethod.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT)) { - invocationContextClass = InvocationContext.class; - } else { - invocationContextClass = ArcInvocationContext.class; - } - if (Modifier.isPrivate(interceptorMethod.flags())) { - privateMembers.add(isApplicationClass, - String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(), - interceptorMethod.name())); - // Use reflection fallback - ResultHandle paramTypesArray = trueBranch.newArray(Class.class, trueBranch.load(1)); - trueBranch.writeArrayValue(paramTypesArray, 0, trueBranch.loadClass(invocationContextClass)); - ResultHandle argsArray = trueBranch.newArray(Object.class, trueBranch.load(1)); - trueBranch.writeArrayValue(argsArray, 0, intercept.getMethodParam(2)); - reflectionRegistration.registerMethod(interceptorMethod); - ret = trueBranch.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - trueBranch.loadClass(interceptorMethod.declaringClass() - .name() - .toString()), - trueBranch.load(interceptorMethod.name()), paramTypesArray, intercept.getMethodParam(1), argsArray); + private void addIntercept(ClassCreator creator, MethodCreator intercept, List interceptorMethods, + InterceptionType interceptionType, ProviderType providerType, ReflectionRegistration reflectionRegistration, + boolean isApplicationClass) { + if (interceptorMethods.isEmpty()) { + return; + } + BranchResult result = intercept + .ifTrue(Gizmo.equals(intercept, intercept.load(interceptionType), intercept.getMethodParam(0))); + BytecodeCreator trueBranch = result.trueBranch(); + ResultHandle ret; + if (interceptorMethods.size() == 1) { + MethodInfo interceptorMethod = interceptorMethods.get(0); + ret = invokeInterceptorMethod(trueBranch, providerType.className(), interceptorMethod, + interceptionType, isApplicationClass, trueBranch.getMethodParam(2), trueBranch.getMethodParam(1)); + } else { + // Multiple interceptor methods found in the hierarchy + ResultHandle methodList = trueBranch.readInstanceField( + FieldDescriptor.of(creator.getClassName(), interceptorMethodsField(interceptionType), List.class), + trueBranch.getThis()); + ResultHandle params; + if (interceptionType == InterceptionType.AROUND_INVOKE) { + params = trueBranch.invokeInterfaceMethod(MethodDescriptors.INVOCATION_CONTEXT_GET_PARAMETERS, + trueBranch.getMethodParam(2)); } else { - ret = trueBranch.invokeVirtualMethod( - MethodDescriptor.ofMethod(providerType.className(), interceptorMethod.name(), retType, - invocationContextClass), - intercept.getMethodParam(1), intercept.getMethodParam(2)); + params = trueBranch.loadNull(); } - trueBranch.returnValue(InterceptionType.AROUND_INVOKE.equals(interceptionType) ? ret : trueBranch.loadNull()); + ret = trueBranch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXTS_PERFORM_SUPERCLASS, + trueBranch.getMethodParam(2), methodList, + trueBranch.getMethodParam(1), params); + + } + trueBranch.returnValue(InterceptionType.AROUND_INVOKE.equals(interceptionType) ? ret : trueBranch.loadNull()); + } + + private String interceptorMethodsField(InterceptionType interceptionType) { + switch (interceptionType) { + case AROUND_INVOKE: + return "aroundInvokes"; + case AROUND_CONSTRUCT: + return "aroundConstructs"; + case POST_CONSTRUCT: + return "postConstructs"; + case PRE_DESTROY: + return "preDestroys"; + default: + throw new IllegalArgumentException("Unsupported interception type: " + interceptionType); + } + } + + private ResultHandle invokeInterceptorMethod(BytecodeCreator creator, String interceptorClass, MethodInfo interceptorMethod, + InterceptionType interceptionType, boolean isApplicationClass, ResultHandle invocationContext, + ResultHandle interceptorInstance) { + Class retType = null; + if (InterceptionType.AROUND_INVOKE.equals(interceptionType)) { + retType = Object.class; + } else { + // @PostConstruct, @PreDestroy, @AroundConstruct + retType = interceptorMethod.returnType().kind().equals(Type.Kind.VOID) ? void.class : Object.class; + } + ResultHandle ret; + // Check if interceptor method uses InvocationContext or ArcInvocationContext + Class invocationContextClass; + if (interceptorMethod.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT)) { + invocationContextClass = InvocationContext.class; + } else { + invocationContextClass = ArcInvocationContext.class; + } + if (Modifier.isPrivate(interceptorMethod.flags())) { + privateMembers.add(isApplicationClass, + String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(), + interceptorMethod.name())); + // Use reflection fallback + ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(1)); + creator.writeArrayValue(paramTypesArray, 0, creator.loadClass(invocationContextClass)); + ResultHandle argsArray = creator.newArray(Object.class, creator.load(1)); + creator.writeArrayValue(argsArray, 0, invocationContext); + reflectionRegistration.registerMethod(interceptorMethod); + ret = creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + creator.loadClass(interceptorMethod.declaringClass() + .name() + .toString()), + creator.load(interceptorMethod.name()), paramTypesArray, interceptorInstance, argsArray); + } else { + ret = creator.invokeVirtualMethod( + MethodDescriptor.ofMethod(interceptorClass, interceptorMethod.name(), retType, + invocationContextClass), + interceptorInstance, invocationContext); } + return ret; } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java index 79b90f70aa15a7..21beeb93509ed9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java @@ -33,14 +33,10 @@ public class InterceptorInfo extends BeanInfo implements Comparable bindings; - - private final MethodInfo aroundInvoke; - - private final MethodInfo aroundConstruct; - - private final MethodInfo postConstruct; - - private final MethodInfo preDestroy; + private final List aroundInvokes; + private final List aroundConstructs; + private final List postConstructs; + private final List preDestroys; InterceptorInfo(AnnotationTarget target, BeanDeployment beanDeployment, Set bindings, List injections, int priority) { @@ -54,8 +50,12 @@ public class InterceptorInfo extends BeanInfo implements Comparable postConstructs = new ArrayList<>(); List preDestroys = new ArrayList<>(); + List allMethods = new ArrayList<>(); ClassInfo aClass = target.asClass(); while (aClass != null) { + // Only one interceptor method of a given type may be declared on a given class + int aroundInvokesFound = 0, aroundConstructsFound = 0, postConstructsFound = 0, preDestroysFound = 0; + for (MethodInfo method : aClass.methods()) { if (Modifier.isStatic(method.flags())) { continue; @@ -67,17 +67,34 @@ public class InterceptorInfo extends BeanInfo implements Comparable 1) { + throw new DefinitionException( + "Multiple @AroundInvoke interceptor methods declared on class: " + aClass); + } } if (store.hasAnnotation(method, DotNames.AROUND_CONSTRUCT)) { - aroundConstructs.add(validateSignature(method)); + addInterceptorMethod(allMethods, aroundConstructs, method); + if (++aroundConstructsFound > 1) { + throw new DefinitionException( + "Multiple @AroundConstruct interceptor methods declared on class: " + aClass); + } } if (store.hasAnnotation(method, DotNames.POST_CONSTRUCT)) { - postConstructs.add(validateSignature(method)); + addInterceptorMethod(allMethods, postConstructs, method); + if (++postConstructsFound > 1) { + throw new DefinitionException( + "Multiple @PostConstruct interceptor methods declared on class: " + aClass); + } } if (store.hasAnnotation(method, DotNames.PRE_DESTROY)) { - preDestroys.add(validateSignature(method)); + addInterceptorMethod(allMethods, preDestroys, method); + if (++preDestroysFound > 1) { + throw new DefinitionException( + "Multiple @PreDestroy interceptor methods declared on class: " + aClass); + } } + allMethods.add(method); } for (FieldInfo field : aClass.fields()) { @@ -93,62 +110,123 @@ public class InterceptorInfo extends BeanInfo implements Comparable parameters = method.parameterTypes(); - if (parameters.size() != 1 || !(parameters.get(0).name().equals(DotNames.INVOCATION_CONTEXT) - || parameters.get(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { - throw new IllegalStateException( - "An interceptor method must accept exactly one parameter of type jakarta.interceptor.InvocationContext: " - + method + " declared on " + method.declaringClass()); - } - if (!method.returnType().kind().equals(Type.Kind.VOID) && - !method.returnType().name().equals(DotNames.OBJECT)) { - throw new IllegalStateException( - "The return type of an interceptor method must be java.lang.Object or void: " - + method + " declared on " + method.declaringClass()); + this.aroundInvokes = List.copyOf(aroundInvokes); + this.aroundConstructs = List.copyOf(aroundConstructs); + this.postConstructs = List.copyOf(postConstructs); + this.preDestroys = List.copyOf(preDestroys); + + if (aroundConstructs.isEmpty() && aroundInvokes.isEmpty() && preDestroys.isEmpty() && postConstructs.isEmpty()) { + LOGGER.warnf("%s declares no around-invoke method nor a lifecycle callback!", this); } - return method; } public Set getBindings() { return bindings; } + /** + * Returns all methods annotated with {@link jakarta.interceptor.AroundInvoke} found in the hierarchy of the interceptor + * class. + *

+ * The returned list is sorted. The method declared on the most general superclass is first. The method declared on the + * interceptor class is last. + * + * @return the interceptor methods + */ + public List getAroundInvokes() { + return aroundInvokes; + } + + /** + * Returns all methods annotated with {@link jakarta.interceptor.AroundConstruct} found in the hierarchy of the interceptor + * class. + *

+ * The returned list is sorted. The method declared on the most general superclass is first. The method declared on the + * interceptor class is last. + * + * @return the interceptor methods + */ + public List getAroundConstructs() { + return aroundConstructs; + } + + /** + * Returns all methods annotated with {@link jakarta.annotation.PostConstruct} found in the hierarchy of the interceptor + * class. + *

+ * The returned list is sorted. The method declared on the most general superclass is first. The method declared on the + * interceptor class is last. + * + * @return the interceptor methods + */ + public List getPostConstructs() { + return postConstructs; + } + + /** + * Returns all methods annotated with {@link jakarta.annotation.PreDestroy} found in the hierarchy of the interceptor class. + *

+ * The returned list is sorted. The method declared on the most general superclass is first. The method declared on the + * interceptor class is last. + * + * @return the interceptor methods + */ + public List getPreDestroys() { + return preDestroys; + } + + /** + * + * @deprecated Use {@link #getAroundInvokes()} instead + */ + @Deprecated(since = "3.1", forRemoval = true) public MethodInfo getAroundInvoke() { - return aroundInvoke; + return aroundInvokes.get(aroundInvokes.size() - 1); } + /** + * + * @deprecated Use {@link #getAroundConstructs()} instead + */ + @Deprecated(since = "3.1", forRemoval = true) public MethodInfo getAroundConstruct() { - return aroundConstruct; + return aroundConstructs.get(aroundConstructs.size() - 1); } + /** + * + * @deprecated Use {@link #getPostConstructs()} instead + */ + @Deprecated(since = "3.1", forRemoval = true) public MethodInfo getPostConstruct() { - return postConstruct; + return postConstructs.get(postConstructs.size() - 1); } + /** + * + * @deprecated Use {@link #getPreDestroys()} instead + */ + @Deprecated(since = "3.1", forRemoval = true) public MethodInfo getPreDestroy() { - return preDestroy; + return preDestroys.get(preDestroys.size() - 1); } public boolean intercepts(InterceptionType interceptionType) { switch (interceptionType) { case AROUND_INVOKE: - return aroundInvoke != null; + return !aroundInvokes.isEmpty(); case AROUND_CONSTRUCT: - return aroundConstruct != null; + return !aroundConstructs.isEmpty(); case POST_CONSTRUCT: - return postConstruct != null; + return !postConstructs.isEmpty(); case PRE_DESTROY: - return preDestroy != null; + return !preDestroys.isEmpty(); default: return false; } @@ -169,4 +247,39 @@ public int compareTo(InterceptorInfo other) { return getTarget().toString().compareTo(other.getTarget().toString()); } + private void addInterceptorMethod(List allMethods, List interceptorMethods, MethodInfo method) { + validateSignature(method); + if (!isInterceptorMethodOverriden(allMethods, method)) { + interceptorMethods.add(method); + } + } + + private boolean isInterceptorMethodOverriden(Iterable allMethods, MethodInfo method) { + for (MethodInfo m : allMethods) { + if (m.name().equals(method.name()) && m.parametersCount() == 1 + && (m.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT) + || m.parameterType(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { + return true; + } + } + return false; + } + + private MethodInfo validateSignature(MethodInfo method) { + List parameters = method.parameterTypes(); + if (parameters.size() != 1 || !(parameters.get(0).name().equals(DotNames.INVOCATION_CONTEXT) + || parameters.get(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { + throw new IllegalStateException( + "An interceptor method must accept exactly one parameter of type jakarta.interceptor.InvocationContext: " + + method + " declared on " + method.declaringClass()); + } + if (!method.returnType().kind().equals(Type.Kind.VOID) && + !method.returnType().name().equals(DotNames.OBJECT)) { + throw new IllegalStateException( + "The return type of an interceptor method must be java.lang.Object or void: " + + method + " declared on " + method.declaringClass()); + } + return method; + } + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index c1e33488f6ef7f..a3048c9c10de80 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -179,6 +179,10 @@ public final class MethodDescriptors { public static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class, "preDestroy", InvocationContext.class, Object.class, List.class, Set.class); + + public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_SUPERCLASS = MethodDescriptor.ofMethod(InvocationContexts.class, + "performSuperclassInterception", + Object.class, InvocationContext.class, List.class, Object.class, Object[].class); public static final MethodDescriptor INVOCATION_CONTEXT_PROCEED = MethodDescriptor.ofMethod(InvocationContext.class, "proceed", @@ -188,6 +192,10 @@ public final class MethodDescriptors { "getTarget", Object.class); + public static final MethodDescriptor INVOCATION_CONTEXT_GET_PARAMETERS = MethodDescriptor.ofMethod(InvocationContext.class, + "getParameters", + Object[].class); + public static final MethodDescriptor CREATIONAL_CTX_ADD_DEP_TO_PARENT = MethodDescriptor.ofMethod( CreationalContextImpl.class, "addDependencyToParent", void.class, diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java index 3feff00e1769e0..8a58ee4338c5cb 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java @@ -55,7 +55,7 @@ public List findIterceptorBindings(Class annotation return found; } - protected void validateParameters(Executable executable, Object[] params) { + static void validateParameters(Executable executable, Object[] params) { int newParametersCount = Objects.requireNonNull(params).length; Class[] parameterTypes = executable.getParameterTypes(); if (parameterTypes.length != newParametersCount) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java new file mode 100644 index 00000000000000..26b617dcb1869e --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java @@ -0,0 +1,155 @@ +package io.quarkus.arc.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.interceptor.InvocationContext; + +import io.quarkus.arc.ArcInvocationContext; + +abstract class InnerInvocationContext implements ArcInvocationContext { + + protected final ArcInvocationContext delegate; + protected Object[] parameters; + + InnerInvocationContext(InvocationContext delegate, Object[] parameters) { + this.delegate = (ArcInvocationContext) delegate; + this.parameters = parameters; + } + + @Override + public Set getInterceptorBindings() { + return delegate.getInterceptorBindings(); + } + + public Method getMethod() { + return delegate.getMethod(); + } + + @Override + public Object[] getParameters() { + if (parameters == null) { + throw new IllegalStateException(); + } + return parameters; + } + + @Override + public void setParameters(Object[] params) { + if (parameters == null) { + throw new IllegalStateException(); + } + AbstractInvocationContext.validateParameters(delegate.getMethod(), params); + this.parameters = params; + } + + @Override + public Object getTarget() { + return delegate.getTarget(); + } + + @Override + public Object getTimer() { + return delegate.getTimer(); + } + + @Override + public Constructor getConstructor() { + return delegate.getConstructor(); + } + + @Override + public Map getContextData() { + return delegate.getContextData(); + } + + @Override + public T findIterceptorBinding(Class annotationType) { + return delegate.findIterceptorBinding(annotationType); + } + + @Override + public List findIterceptorBindings(Class annotationType) { + return delegate.findIterceptorBindings(annotationType); + } + + @Override + public Object proceed() throws Exception { + return proceed(1); + } + + protected abstract Object proceed(int currentPosition) throws Exception; + + class NextInnerInvocationContext implements ArcInvocationContext { + + private final int position; + protected Object[] parameters; + + public NextInnerInvocationContext(int position, Object[] parameters) { + this.position = position; + this.parameters = parameters; + } + + @Override + public Object proceed() throws Exception { + return InnerInvocationContext.this.proceed(position); + } + + @Override + public Object getTarget() { + return InnerInvocationContext.this.getTarget(); + } + + @Override + public Object getTimer() { + return InnerInvocationContext.this.getTimer(); + } + + @Override + public Method getMethod() { + return InnerInvocationContext.this.getMethod(); + } + + @Override + public Constructor getConstructor() { + return InnerInvocationContext.this.getConstructor(); + } + + @Override + public Object[] getParameters() { + return parameters; + } + + @Override + public void setParameters(Object[] params) { + AbstractInvocationContext.validateParameters(InnerInvocationContext.this.delegate.getMethod(), params); + this.parameters = params; + } + + @Override + public Map getContextData() { + return InnerInvocationContext.this.getContextData(); + } + + @Override + public Set getInterceptorBindings() { + return InnerInvocationContext.this.getInterceptorBindings(); + } + + @Override + public T findIterceptorBinding(Class annotationType) { + return InnerInvocationContext.this.findIterceptorBinding(annotationType); + } + + @Override + public List findIterceptorBindings(Class annotationType) { + return InnerInvocationContext.this.findIterceptorBindings(annotationType); + } + + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java index 51aee7b76d825e..9ae287ea3cb468 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java @@ -66,4 +66,18 @@ public static InvocationContext aroundConstruct(Constructor constructor, aroundConstructForward); } + /** + * + * @param delegate + * @param methods + * @param interceptorInstance + * @return the return value + * @throws Exception + */ + public static Object performSuperclassInterception(InvocationContext delegate, + List> methods, Object interceptorInstance, Object[] parameters) + throws Exception { + return SuperclassInvocationContext.perform(delegate, methods, interceptorInstance, parameters); + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java new file mode 100644 index 00000000000000..77d1f8c1cd48a2 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.impl; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.function.BiFunction; + +import jakarta.interceptor.InvocationContext; + +/** + * A special {@link javax.interceptor.InvocationContext} that is used if multiple interceptor methods are declared in a + * hierarchy of an interceptor class. + *

+ * The interceptor methods defined by the superclasses are invoked before the interceptor method defined by the interceptor + * class, most general superclass first. + */ +class SuperclassInvocationContext extends InnerInvocationContext { + + static Object perform(InvocationContext delegate, List> methods, + Object interceptorInstance, Object[] parameters) + throws Exception { + return methods.get(0).apply(interceptorInstance, + new SuperclassInvocationContext(delegate, methods, interceptorInstance, parameters)); + } + + private final Object interceptorInstance; + private final List> methods; + + SuperclassInvocationContext(InvocationContext delegate, List> methods, + Object interceptorInstance, Object[] parameters) { + super(delegate, parameters); + this.methods = methods; + this.interceptorInstance = interceptorInstance; + } + + protected Object proceed(int currentPosition) throws Exception { + try { + if (currentPosition < methods.size()) { + // Invoke the next interceptor method from the hierarchy + return methods.get(currentPosition) + .apply(interceptorInstance, + new NextInnerInvocationContext(currentPosition + 1, parameters)); + } else { + // Invoke the next interceptor in the chain + return delegate.proceed(); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaBinding.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaBinding.java new file mode 100644 index 00000000000000..85c51ebf7a473c --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaBinding.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.test.interceptors.inheritance.hierarchy; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.interceptor.InterceptorBinding; + +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@Documented +@InterceptorBinding +public @interface AlphaBinding { + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaInterceptor.java new file mode 100644 index 00000000000000..4fdd8b25959a44 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/AlphaInterceptor.java @@ -0,0 +1,43 @@ +package io.quarkus.arc.test.interceptors.inheritance.hierarchy; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; + +@Priority(1) +@AlphaBinding +@Interceptor +public class AlphaInterceptor extends Bravo { + + @AroundInvoke + public Object intercept(InvocationContext ctx) throws Exception { + return "a/" + ctx.proceed() + "/a"; + } + + @PostConstruct + void init(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("a"); + ctx.proceed(); + } + + @PreDestroy + void destroy(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("a"); + ctx.proceed(); + } + + @AroundConstruct + public void construct(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("A"); + ctx.proceed(); + } + + // "If an interceptor method is overridden by another method (regardless whether that method is itself an interceptor method), it will not be invoked." + void alphaDummyInit(InvocationContext ctx) throws Exception { + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Bravo.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Bravo.java new file mode 100644 index 00000000000000..4c3c9240ea07c4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Bravo.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.interceptors.inheritance.hierarchy; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +public class Bravo extends Charlie { + + @AroundInvoke + public Object bravoIntercept(InvocationContext ctx) throws Exception { + return "b/" + ctx.proceed() + "/b"; + } + + // This callback is overriden + @PostConstruct + void alphaDummyInit(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("b"); + ctx.proceed(); + } + + @PreDestroy + void bravoDestroy(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("b"); + ctx.proceed(); + } + + // This callback is overriden + @AroundConstruct + public void construct(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("B"); + ctx.proceed(); + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Charlie.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Charlie.java new file mode 100644 index 00000000000000..8289dd573a919a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/Charlie.java @@ -0,0 +1,34 @@ +package io.quarkus.arc.test.interceptors.inheritance.hierarchy; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +public class Charlie { + + @AroundInvoke + public Object charlieIntercept(InvocationContext ctx) throws Exception { + return "c/" + ctx.proceed() + "/c"; + } + + @PostConstruct + void charlieInit(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("c"); + ctx.proceed(); + } + + // This callback is overriden + @PreDestroy + void destroy(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("c"); + ctx.proceed(); + } + + @AroundConstruct + public void charlieConstruct(InvocationContext ctx) throws Exception { + SuperclassInterceptorMethodsTest.LIFECYCLE_CALLBACKS.add("C"); + ctx.proceed(); + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/MultipleInterceptorMethodDeclaredOnSuperclassTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/MultipleInterceptorMethodDeclaredOnSuperclassTest.java new file mode 100644 index 00000000000000..0a2f4ac5ac4318 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/MultipleInterceptorMethodDeclaredOnSuperclassTest.java @@ -0,0 +1,69 @@ +package io.quarkus.arc.test.interceptors.inheritance.hierarchy; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class MultipleInterceptorMethodDeclaredOnSuperclassTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(AlphaInterceptor.class, Bravo.class, AlphaBinding.class, + Fool.class) + .shouldFail().build(); + + @Test + public void testFailure() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertEquals( + "Multiple @AroundInvoke interceptor methods declared on class: io.quarkus.arc.test.interceptors.inheritance.hierarchy.MultipleInterceptorMethodDeclaredOnSuperclassTest$Bravo", + error.getMessage()); + } + + @Priority(1) + @AlphaBinding + @Interceptor + static class AlphaInterceptor extends Bravo { + + @AroundInvoke + public Object intercept(InvocationContext ctx) throws Exception { + return "a/" + ctx.proceed() + "/a"; + } + + } + + static class Bravo { + + @AroundInvoke + public Object b1(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + + @AroundInvoke + public Object b2(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + } + + @AlphaBinding + @ApplicationScoped + public static class Fool { + + String ping() { + return "ping"; + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/SuperclassInterceptorMethodsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/SuperclassInterceptorMethodsTest.java new file mode 100644 index 00000000000000..c331d0144fd7d9 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/inheritance/hierarchy/SuperclassInterceptorMethodsTest.java @@ -0,0 +1,54 @@ +package io.quarkus.arc.test.interceptors.inheritance.hierarchy; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class SuperclassInterceptorMethodsTest { + + static final List LIFECYCLE_CALLBACKS = new CopyOnWriteArrayList<>(); + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(AlphaInterceptor.class, Bravo.class, AlphaBinding.class, + Fool.class); + + @Test + public void testInterception() { + LIFECYCLE_CALLBACKS.clear(); + InstanceHandle handle = Arc.container().instance(Fool.class); + Fool fool = handle.get(); + assertEquals("c/b/a/ping/a/b/c", fool.ping()); + assertEquals(4, LIFECYCLE_CALLBACKS.size(), LIFECYCLE_CALLBACKS.toString()); + assertEquals("C", LIFECYCLE_CALLBACKS.get(0)); + assertEquals("A", LIFECYCLE_CALLBACKS.get(1)); + assertEquals("c", LIFECYCLE_CALLBACKS.get(2)); + assertEquals("a", LIFECYCLE_CALLBACKS.get(3)); + + LIFECYCLE_CALLBACKS.clear(); + handle.destroy(); + assertEquals(2, LIFECYCLE_CALLBACKS.size(), LIFECYCLE_CALLBACKS.toString()); + assertEquals("b", LIFECYCLE_CALLBACKS.get(0)); + assertEquals("a", LIFECYCLE_CALLBACKS.get(1)); + } + + @AlphaBinding + @ApplicationScoped + public static class Fool { + + String ping() { + return "ping"; + } + + } + +} From 59916eec3d1ea41eef0d78b0e623d2946011618b Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 18 Apr 2023 13:05:39 +0200 Subject: [PATCH 072/333] ArC - support around invoke method declared on target class - and its superclasses --- .../io/quarkus/arc/processor/BeanInfo.java | 25 +++++- .../quarkus/arc/processor/BeanProcessor.java | 2 +- .../java/io/quarkus/arc/processor/Beans.java | 28 +++++++ .../arc/processor/MethodDescriptors.java | 9 ++- .../io/quarkus/arc/processor/Methods.java | 16 ++-- .../arc/processor/SubclassGenerator.java | 77 ++++++++++++++++++- .../impl/AroundInvokeInvocationContext.java | 11 ++- .../quarkus/arc/impl/InvocationContexts.java | 17 +++- .../TargetAroundInvokeInvocationContext.java | 58 ++++++++++++++ 9 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 858b76965aa98c..ead53d8a417038 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -90,6 +90,8 @@ public class BeanInfo implements InjectionTargetInfo { private final String targetPackageName; + private final List aroundInvokes; + BeanInfo(AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, Set types, Set qualifiers, List injections, BeanInfo declaringBean, DisposerInfo disposer, boolean alternative, List stereotypes, String name, boolean isDefaultBean, String targetPackageName, @@ -143,6 +145,7 @@ public class BeanInfo implements InjectionTargetInfo { this.lifecycleInterceptors = Collections.emptyMap(); this.forceApplicationClass = forceApplicationClass; this.targetPackageName = targetPackageName; + this.aroundInvokes = isInterceptor() || isDecorator() ? List.of() : Beans.getAroundInvokes(implClazz, beanDeployment); } @Override @@ -360,8 +363,10 @@ public boolean hasAroundInvokeInterceptors() { } boolean isSubclassRequired() { - return !interceptedMethods.isEmpty() || !decoratedMethods.isEmpty() - || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); + return !interceptedMethods.isEmpty() + || !decoratedMethods.isEmpty() + || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY) + || !aroundInvokes.isEmpty(); } /** @@ -445,6 +450,18 @@ public List getBoundDecorators() { return bound; } + /** + * + * @return the list of around invoke interceptor methods declared in the hierarchy of a bean class + */ + List getAroundInvokes() { + return aroundInvokes; + } + + boolean hasAroundInvokes() { + return !aroundInvokes.isEmpty(); + } + public DisposerInfo getDisposer() { return disposer; } @@ -603,7 +620,7 @@ private Map initInterceptedMethods(List } Set finalMethods = Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(), - candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses); + candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, aroundInvokes); if (!finalMethods.isEmpty()) { String additionalError = ""; if (finalMethods.stream().anyMatch(KotlinUtils::isNoninterceptableKotlinMethod)) { @@ -620,7 +637,7 @@ private Map initInterceptedMethods(List for (Entry> entry : candidates.entrySet()) { List interceptors = beanDeployment.getInterceptorResolver() .resolve(InterceptionType.AROUND_INVOKE, entry.getValue()); - if (!interceptors.isEmpty()) { + if (!interceptors.isEmpty() || !aroundInvokes.isEmpty()) { interceptedMethods.put(entry.getKey().method, new InterceptionInfo(interceptors, entry.getValue())); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 465dc1447aa21e..ba749360e3c546 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -216,7 +216,7 @@ public List generateResources(ReflectionRegistration reflectionRegistr } SubclassGenerator subclassGenerator = new SubclassGenerator(annotationLiterals, applicationClassPredicate, - generateSources, refReg, existingClasses); + generateSources, refReg, existingClasses, privateMembers); ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate, privateMembers, generateSources, refReg, existingClasses, observerToGeneratedName, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 2cd2e56e3d8b75..141acb76201844 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -649,6 +649,34 @@ static List getCallbacks(ClassInfo beanClass, DotName annotation, In return callbacks; } + static List getAroundInvokes(ClassInfo beanClass, BeanDeployment deployment) { + List methods = new ArrayList<>(); + AnnotationStore store = deployment.getAnnotationStore(); + Set processed = new HashSet<>(); + + ClassInfo aClass = beanClass; + while (aClass != null) { + int aroundInvokesFound = 0; + for (MethodInfo method : aClass.methods()) { + if (Modifier.isStatic(method.flags())) { + continue; + } + if (store.hasAnnotation(method, DotNames.AROUND_INVOKE) && !processed.contains(method.name())) { + methods.add(method); + if (++aroundInvokesFound > 1) { + throw new DefinitionException( + "Multiple @AroundInvoke interceptor methods declared on class: " + aClass); + } + } + } + DotName superTypeName = aClass.superName(); + aClass = superTypeName == null || DotNames.OBJECT.equals(superTypeName) ? null + : getClassByName(deployment.getBeanArchiveIndex(), superTypeName); + } + Collections.reverse(methods); + return methods.isEmpty() ? List.of() : List.copyOf(methods); + } + static void analyzeType(Type type, BeanDeployment beanDeployment) { if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) { for (Type argument : type.asParameterizedType().arguments()) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index a3048c9c10de80..9d14b1dfad9918 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -166,6 +166,10 @@ public final class MethodDescriptors { InvocationContexts.class, "performAroundInvoke", Object.class, Object.class, Object[].class, InterceptedMethodMetadata.class); + public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_TARGET_AROUND_INVOKE = MethodDescriptor.ofMethod( + InvocationContexts.class, + "performTargetAroundInvoke", Object.class, InvocationContext.class, List.class, BiFunction.class); + public static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( InvocationContexts.class, "aroundConstruct", @@ -179,8 +183,9 @@ public final class MethodDescriptors { public static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class, "preDestroy", InvocationContext.class, Object.class, List.class, Set.class); - - public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_SUPERCLASS = MethodDescriptor.ofMethod(InvocationContexts.class, + + public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_SUPERCLASS = MethodDescriptor.ofMethod( + InvocationContexts.class, "performSuperclassInterception", Object.class, InvocationContext.class, List.class, Object.class, Object[].class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index ad6e073eec5318..1dc877ce6a2ded 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -152,12 +152,12 @@ static boolean isObjectToString(MethodInfo method) { static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, Map> candidates, List classLevelBindings, Consumer bytecodeTransformerConsumer, - boolean transformUnproxyableClasses) { + boolean transformUnproxyableClasses, List aroundInvokes) { return addInterceptedMethodCandidates(beanDeployment, classInfo, classInfo, candidates, Set.copyOf(classLevelBindings), bytecodeTransformerConsumer, transformUnproxyableClasses, new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom, beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods()), - false, new HashSet<>()); + false, new HashSet<>(), aroundInvokes); } private static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, @@ -165,16 +165,20 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea Map> candidates, Set classLevelBindings, Consumer bytecodeTransformerConsumer, boolean transformUnproxyableClasses, SubclassSkipPredicate skipPredicate, boolean ignoreMethodLevelBindings, - Set noClassInterceptorsMethods) { + Set noClassInterceptorsMethods, List aroundInvokes) { Set methodsFromWhichToRemoveFinal = new HashSet<>(); Set finalMethodsFoundAndNotChanged = new HashSet<>(); skipPredicate.startProcessing(classInfo, originalClassInfo); for (MethodInfo method : classInfo.methods()) { + if (aroundInvokes.contains(method)) { + // Around invoke method declared in the target class hierarchy + continue; + } Set merged = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings, ignoreMethodLevelBindings, method, noClassInterceptorsMethods); - if (merged.isEmpty() || skipPredicate.test(method)) { + if ((merged.isEmpty() && aroundInvokes.isEmpty()) || skipPredicate.test(method)) { continue; } boolean addToCandidates = true; @@ -204,7 +208,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea finalMethodsFoundAndNotChanged .addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, classInfo, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, skipPredicate, - ignoreMethodLevelBindings, noClassInterceptorsMethods)); + ignoreMethodLevelBindings, noClassInterceptorsMethods, aroundInvokes)); } } @@ -214,7 +218,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea //interfaces can't have final methods addInterceptedMethodCandidates(beanDeployment, interfaceInfo, originalClassInfo, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, - skipPredicate, true, noClassInterceptorsMethods); + skipPredicate, true, noClassInterceptorsMethods, aroundInvokes); } } return finalMethodsFoundAndNotChanged; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index ea4aa6ab8b4fb1..fcd318c61d779d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -38,6 +38,7 @@ import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import io.quarkus.arc.ArcInvocationContext; import io.quarkus.arc.ArcUndeclaredThrowableException; import io.quarkus.arc.InjectableDecorator; import io.quarkus.arc.InjectableInterceptor; @@ -45,6 +46,7 @@ import io.quarkus.arc.impl.InterceptedMethodMetadata; import io.quarkus.arc.processor.BeanInfo.DecorationInfo; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; import io.quarkus.arc.processor.Methods.MethodKey; import io.quarkus.arc.processor.ResourceOutput.Resource; import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; @@ -80,6 +82,7 @@ public class SubclassGenerator extends AbstractGenerator { private final Predicate applicationClassPredicate; private final Set existingClasses; + private final PrivateMembersCollector privateMembers; static String generatedName(DotName providerTypeName, String baseName) { String packageName = DotNames.internalPackageNameWithTrailingSlash(providerTypeName); @@ -90,11 +93,12 @@ static String generatedName(DotName providerTypeName, String baseName) { public SubclassGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate applicationClassPredicate, boolean generateSources, ReflectionRegistration reflectionRegistration, - Set existingClasses) { + Set existingClasses, PrivateMembersCollector privateMembers) { super(generateSources, reflectionRegistration); this.applicationClassPredicate = applicationClassPredicate; this.annotationLiterals = annotationLiterals; this.existingClasses = existingClasses; + this.privateMembers = privateMembers; } Collection generate(BeanInfo bean, String beanClassName) { @@ -325,6 +329,25 @@ public String apply(List keys) { } } + // Initialize the "aroundInvokes" field if necessary + if (bean.hasAroundInvokes()) { + FieldCreator field = subclass.getFieldCreator("aroundInvokes", List.class) + .setModifiers(ACC_PRIVATE); + ResultHandle methodsList = constructor.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + for (MethodInfo method : bean.getAroundInvokes()) { + // BiFunction + FunctionCreator fun = constructor.createFunction(BiFunction.class); + BytecodeCreator funBytecode = fun.getBytecode(); + ResultHandle ret = invokeInterceptorMethod(funBytecode, method, + applicationClassPredicate.test(bean.getBeanClass()), + funBytecode.getMethodParam(1), + funBytecode.getMethodParam(0)); + funBytecode.returnValue(ret); + constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, methodsList, fun.getInstance()); + } + constructor.writeInstanceField(field.getFieldDescriptor(), constructor.getThis(), methodsList); + } + // Split initialization of InterceptedMethodMetadata into multiple methods int group = 0; int groupLimit = 30; @@ -422,6 +445,7 @@ public String apply(List keys) { superParamHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, i); } } + // If a decorator is bound then invoke the method upon the decorator instance instead of the generated forwarding method if (decorator != null) { AssignableResultHandle funDecoratorInstance = funcBytecode.createVariable(Object.class); @@ -445,10 +469,28 @@ public String apply(List keys) { funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull()); } + ResultHandle aroundForwardFun = func.getInstance(); + + if (bean.hasAroundInvokes()) { + // Wrap the forwarding function with a function that calls around invoke methods declared in a hierarchy of the target class first + AssignableResultHandle methodsList = initMetadataMethod.createVariable(List.class); + initMetadataMethod.assign(methodsList, initMetadataMethod.readInstanceField( + FieldDescriptor.of(subclass.getClassName(), "aroundInvokes", List.class), + initMetadataMethod.getThis())); + FunctionCreator targetFun = initMetadataMethod.createFunction(BiFunction.class); + BytecodeCreator targetFunBytecode = targetFun.getBytecode(); + ResultHandle ret = targetFunBytecode.invokeStaticMethod( + MethodDescriptors.INVOCATION_CONTEXTS_PERFORM_TARGET_AROUND_INVOKE, + targetFunBytecode.getMethodParam(1), + methodsList, aroundForwardFun); + targetFunBytecode.returnValue(ret); + aroundForwardFun = targetFun.getInstance(); + } + // Now create metadata for the given intercepted method ResultHandle methodMetadataHandle = initMetadataMethod.newInstance( MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR, - chainHandle, methodHandle, bindingsHandle, func.getInstance()); + chainHandle, methodHandle, bindingsHandle, aroundForwardFun); FieldDescriptor metadataField = FieldDescriptor.of(subclass.getClassName(), "arc$" + methodIdx++, InterceptedMethodMetadata.class.getName()); @@ -513,6 +555,37 @@ public String apply(List keys) { return preDestroysField != null ? preDestroysField.getFieldDescriptor() : null; } + private ResultHandle invokeInterceptorMethod(BytecodeCreator creator, MethodInfo interceptorMethod, + boolean isApplicationClass, ResultHandle invocationContext, ResultHandle targetInstance) { + ResultHandle ret; + // Check if interceptor method uses InvocationContext or ArcInvocationContext + Class invocationContextClass; + if (interceptorMethod.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT)) { + invocationContextClass = InvocationContext.class; + } else { + invocationContextClass = ArcInvocationContext.class; + } + if (Modifier.isPrivate(interceptorMethod.flags())) { + privateMembers.add(isApplicationClass, + String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(), + interceptorMethod.name())); + // Use reflection fallback + ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(1)); + creator.writeArrayValue(paramTypesArray, 0, creator.loadClass(invocationContextClass)); + ResultHandle argsArray = creator.newArray(Object.class, creator.load(1)); + creator.writeArrayValue(argsArray, 0, invocationContext); + reflectionRegistration.registerMethod(interceptorMethod); + ret = creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + creator.loadClass(interceptorMethod.declaringClass() + .name() + .toString()), + creator.load(interceptorMethod.name()), paramTypesArray, targetInstance, argsArray); + } else { + ret = creator.invokeVirtualMethod(interceptorMethod, targetInstance, invocationContext); + } + return ret; + } + private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type providerType, String providerTypeName, ClassCreator subclass, ClassOutput classOutput, MethodCreator subclassConstructor, int paramIndex, Map decoratorToResultHandle, diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java index eea2a771e2728e..73b87a7649c597 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java @@ -27,6 +27,13 @@ */ class AroundInvokeInvocationContext extends AbstractInvocationContext { + static Object perform(Object target, Object[] args, InterceptedMethodMetadata metadata) throws Exception { + if (metadata.chain.isEmpty()) { + return metadata.aroundInvokeForward.apply(target, new AroundInvokeInvocationContext(target, args, metadata)); + } + return metadata.chain.get(0).invoke(new AroundInvokeInvocationContext(target, args, metadata)); + } + private final InterceptedMethodMetadata metadata; AroundInvokeInvocationContext(Object target, Object[] args, InterceptedMethodMetadata metadata) { @@ -34,10 +41,6 @@ class AroundInvokeInvocationContext extends AbstractInvocationContext { this.metadata = metadata; } - static Object perform(Object target, Object[] args, InterceptedMethodMetadata metadata) throws Exception { - return metadata.chain.get(0).invoke(new AroundInvokeInvocationContext(target, args, metadata)); - } - @Override public Set getInterceptorBindings() { return metadata.bindings; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java index 9ae287ea3cb468..fa137958f4c7c0 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java @@ -4,6 +4,7 @@ import java.lang.reflect.Constructor; import java.util.List; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Supplier; import jakarta.interceptor.InvocationContext; @@ -26,6 +27,20 @@ public static Object performAroundInvoke(Object target, Object[] args, Intercept return AroundInvokeInvocationContext.perform(target, args, metadata); } + /** + * + * @param delegate + * @param aroundInvokeMethods + * @param aroundInvokeForward + * @return the return value + * @throws Exception + */ + public static Object performTargetAroundInvoke(InvocationContext delegate, + List> aroundInvokeMethods, + BiFunction aroundInvokeForward) throws Exception { + return TargetAroundInvokeInvocationContext.perform(delegate, aroundInvokeMethods, aroundInvokeForward); + } + /** * * @param target @@ -67,7 +82,7 @@ public static InvocationContext aroundConstruct(Constructor constructor, } /** - * + * * @param delegate * @param methods * @param interceptorInstance diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java new file mode 100644 index 00000000000000..782f1768b8dfe4 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.impl; + +import java.util.List; +import java.util.function.BiFunction; + +import jakarta.interceptor.InvocationContext; + +/** + * A special {@link javax.interceptor.InvocationContext} that is used for around invoke methods declared in a hierarchy of a + * target class. + *

+ * The interceptor methods defined by the superclasses are invoked before the interceptor method defined by the interceptor + * class, most general superclass first. + */ +class TargetAroundInvokeInvocationContext extends InnerInvocationContext { + + static Object perform(InvocationContext delegate, + List> methods, + BiFunction aroundInvokeForward) + throws Exception { + return methods.get(0).apply(delegate.getTarget(), + new TargetAroundInvokeInvocationContext(delegate, methods, aroundInvokeForward)); + } + + private final List> methods; + private final BiFunction aroundInvokeForward; + + TargetAroundInvokeInvocationContext(InvocationContext delegate, List> methods, + BiFunction aroundInvokeForward) { + super(delegate, delegate.getParameters()); + this.methods = methods; + this.aroundInvokeForward = aroundInvokeForward; + } + + protected Object proceed(int currentPosition) throws Exception { + try { + if (currentPosition < methods.size()) { + // Invoke the next interceptor method from the hierarchy + return methods.get(currentPosition) + .apply(delegate.getTarget(), + new NextInnerInvocationContext(currentPosition + 1, parameters)); + } else { + // Invoke the target method + return aroundInvokeForward.apply(delegate.getTarget(), this); + } + } catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } + } + +} From 186a263084a6ca17dd78933b5166af7ad59e152e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 19 Apr 2023 12:47:35 +0300 Subject: [PATCH 073/333] Display JVM CDS logs when debug logging is enabled --- .../io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java index 49d64e6e452512..7e99084f775380 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java @@ -280,9 +280,13 @@ private Path createAppCDSFromExit(JarBuildItem jarResult, Path workingDirectory = appCDSPathsContainer.workingDirectory; Path appCDSPath = appCDSPathsContainer.resultingFile; - List javaArgs = new ArrayList<>(3); + boolean debug = log.isDebugEnabled(); + List javaArgs = new ArrayList<>(debug ? 4 : 3); javaArgs.add("-XX:ArchiveClassesAtExit=" + appCDSPath.getFileName().toString()); javaArgs.add(String.format("-D%s=true", MainClassBuildStep.GENERATE_APP_CDS_SYSTEM_PROPERTY)); + if (debug) { + javaArgs.add("-Xlog:cds=debug"); + } javaArgs.add("-jar"); List command; From 5e58d94aed9b52b07d83010d15bbc5864b5c655e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 19 Apr 2023 11:48:29 +0200 Subject: [PATCH 074/333] Also create the default mailer if @CheckedTemplate is used Fixes #32755 --- .../java/io/quarkus/mailer/deployment/MailerProcessor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java index 4d5dc582cf76b0..26efb7775171aa 100644 --- a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java +++ b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java @@ -27,6 +27,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; @@ -45,6 +46,7 @@ import io.quarkus.mailer.runtime.Mailers; import io.quarkus.mailer.runtime.MailersBuildTimeConfig; import io.quarkus.mailer.runtime.MailersRuntimeConfig; +import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.deployment.CheckedTemplateAdapterBuildItem; import io.quarkus.qute.deployment.QuteProcessor; import io.quarkus.qute.deployment.TemplatePathBuildItem; @@ -91,13 +93,15 @@ void registerBeans(BuildProducer beans) { @Record(ExecutionTime.STATIC_INIT) @BuildStep MailersBuildItem generateMailerSupportBean(MailerRecorder recorder, + CombinedIndexBuildItem index, BeanDiscoveryFinishedBuildItem beans, BuildProducer syntheticBeans) { List mailerInjectionPoints = beans.getInjectionPoints().stream() .filter(i -> SUPPORTED_INJECTION_TYPES.contains(i.getRequiredType().name())) .collect(Collectors.toList()); - boolean hasDefaultMailer = mailerInjectionPoints.stream().anyMatch(i -> i.hasDefaultedQualifier()); + boolean hasDefaultMailer = mailerInjectionPoints.stream().anyMatch(i -> i.hasDefaultedQualifier()) + || !index.getIndex().getAnnotations(CheckedTemplate.class).isEmpty(); Set namedMailers = mailerInjectionPoints.stream() .map(i -> i.getRequiredQualifier(MAILER_NAME)) From 7feb34fc58fbbb5854723d0409b75db16288cea4 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 19 Apr 2023 14:27:45 +0300 Subject: [PATCH 075/333] Restore the ability to use @TestReactiveTransaction on a test class Fixes: #32650 --- .../it/panache/reactive/TestReactiveTransactionTest.java | 6 +----- .../test/vertx/RunOnVertxContextTestMethodInvoker.java | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/TestReactiveTransactionTest.java b/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/TestReactiveTransactionTest.java index 241f7bf51e6e56..57e872960e8a18 100644 --- a/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/TestReactiveTransactionTest.java +++ b/integration-tests/hibernate-reactive-panache/src/test/java/io/quarkus/it/panache/reactive/TestReactiveTransactionTest.java @@ -6,21 +6,17 @@ import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.test.TestReactiveTransaction; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.vertx.RunOnVertxContext; import io.quarkus.test.vertx.UniAsserter; @QuarkusTest +@TestReactiveTransaction public class TestReactiveTransactionTest { - @RunOnVertxContext - @TestReactiveTransaction @Test public void testTestTransaction(UniAsserter asserter) { asserter.assertNotNull(() -> Panache.currentTransaction()); } - @RunOnVertxContext - @TestReactiveTransaction @BeforeEach public void beforeEach(UniAsserter asserter) { asserter.assertNotNull(() -> Panache.currentTransaction()); diff --git a/test-framework/vertx/src/main/java/io/quarkus/test/vertx/RunOnVertxContextTestMethodInvoker.java b/test-framework/vertx/src/main/java/io/quarkus/test/vertx/RunOnVertxContextTestMethodInvoker.java index a348c0a83c4300..39214de3f94b30 100644 --- a/test-framework/vertx/src/main/java/io/quarkus/test/vertx/RunOnVertxContextTestMethodInvoker.java +++ b/test-framework/vertx/src/main/java/io/quarkus/test/vertx/RunOnVertxContextTestMethodInvoker.java @@ -99,7 +99,8 @@ private boolean shouldContextBeDuplicated(Class c, Method m) { } if (runOnVertxContext == null) { // Use duplicated context if @TestReactiveTransaction is present - return m.isAnnotationPresent(TestReactiveTransaction.class); + return m.isAnnotationPresent(TestReactiveTransaction.class) + || m.getDeclaringClass().isAnnotationPresent(TestReactiveTransaction.class); } else { return runOnVertxContext.duplicateContext(); } From 9eba0c5d88f0bb5ca0b17b25db74942c0cd1008c Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Thu, 9 Mar 2023 10:57:03 +0100 Subject: [PATCH 076/333] Infinispan named configurations (ISPN-14012) --- docs/src/main/asciidoc/dev-services.adoc | 2 +- .../asciidoc/infinispan-client-reference.adoc | 61 +- .../asciidoc/infinispan-dev-services.adoc | 11 +- .../infinispan-client/deployment/pom.xml | 26 + .../InfinispanBindingProcessor.java | 29 + .../deployment/InfinispanClientBuildItem.java | 28 + ...nispanClientDevServiceBuildTimeConfig.java | 45 -- .../InfinispanClientNameBuildItem.java | 19 + .../deployment/InfinispanClientProcessor.java | 536 ++++++++++++++---- .../InfinispanPropertiesBuildItem.java | 7 +- .../deployment/MarshallingBuildItem.java | 28 + .../InfinispanDevConsoleProcessor.java | 13 +- .../InfinispanDevServiceProcessor.java | 169 ++++-- .../resources/dev-templates/embedded.html | 12 +- ...cheManagerCreationWithRemoteCacheTest.java | 48 ++ .../MultipleNamedInfinispanClientsTest.java | 53 ++ ...edAndDefaultRemoteCacheClientNameTest.java | 61 ++ ...CreationWithRemoteCacheClientNameTest.java | 46 ++ ...e-devservices-infinispan-client.properties | 7 + ...2-application-infinispan-client.properties | 8 + ...2-application-infinispan-client.properties | 2 + ...y-application-infinispan-client.properties | 1 + .../client/InfinispanClientName.java | 61 ++ .../io/quarkus/infinispan/client/Remote.java | 21 +- .../InfinispanClientBuildTimeConfig.java | 55 +- .../runtime/InfinispanClientProducer.java | 255 ++++----- .../InfinispanClientRuntimeConfig.java | 16 +- .../client/runtime/InfinispanClientUtil.java | 33 ++ .../InfinispanClientsBuildTimeConfig.java | 46 ++ .../InfinispanClientsRuntimeConfig.java | 43 ++ .../runtime}/InfinispanDevServicesConfig.java | 12 +- .../client/runtime/InfinispanRecorder.java | 71 ++- .../runtime/InfinispanServerUrlSupplier.java | 26 - .../runtime/cache/CacheInterceptor.java | 12 +- .../cache/CacheInvalidateAllInterceptor.java | 2 +- .../cache/CacheInvalidateInterceptor.java | 2 +- .../runtime/cache/CacheResultInterceptor.java | 3 +- .../InfinispanClientsContainer.java | 62 ++ .../runtime/health/InfinispanHealthCheck.java | 157 ++++- ...oreService.java => BookStoreResource.java} | 2 +- .../it/infinispan/client/CacheSetup.java | 77 +-- .../it/infinispan/client/TestServlet.java | 32 +- .../src/main/resources/application.properties | 15 +- .../it/infinispan/client/HealthCheckTest.java | 6 +- .../InfinispanClientFunctionalityTest.java | 2 +- 45 files changed, 1721 insertions(+), 502 deletions(-) create mode 100644 extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanBindingProcessor.java create mode 100644 extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientBuildItem.java delete mode 100644 extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientDevServiceBuildTimeConfig.java create mode 100644 extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientNameBuildItem.java create mode 100644 extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/MarshallingBuildItem.java create mode 100644 extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/DefaultRemoteCacheManagerCreationWithRemoteCacheTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/MultipleNamedInfinispanClientsTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/NamedAndDefaultRemoteCacheClientNameTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/RemoteCacheManagerCreationWithRemoteCacheClientNameTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/resources/application-multiple-devservices-infinispan-client.properties create mode 100644 extensions/infinispan-client/deployment/src/test/resources/default-and-conn-2-application-infinispan-client.properties create mode 100644 extensions/infinispan-client/deployment/src/test/resources/dev-service-conn-2-application-infinispan-client.properties create mode 100644 extensions/infinispan-client/deployment/src/test/resources/empty-application-infinispan-client.properties create mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/InfinispanClientName.java create mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientUtil.java create mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java create mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsRuntimeConfig.java rename extensions/infinispan-client/{deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices => runtime/src/main/java/io/quarkus/infinispan/client/runtime}/InfinispanDevServicesConfig.java (90%) delete mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServerUrlSupplier.java create mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanClientsContainer.java rename integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/{BookStoreService.java => BookStoreResource.java} (97%) diff --git a/docs/src/main/asciidoc/dev-services.adoc b/docs/src/main/asciidoc/dev-services.adoc index d7724be316243f..b4440dfeca43ce 100644 --- a/docs/src/main/asciidoc/dev-services.adoc +++ b/docs/src/main/asciidoc/dev-services.adoc @@ -125,7 +125,7 @@ The Infinispan Dev Service will be enabled when the `quarkus-infinispan-client` the server address has not been explicitly configured. More information can be found in the xref:infinispan-dev-services.adoc[Infinispan Dev Services Guide]. -include::{generated-dir}/config/quarkus-infinispan-client-infinispan-client-dev-service-build-time-config.adoc[opts=optional, leveloffset=+1] +include::{generated-dir}/config/quarkus-infinispan-client-config-group-infinispan-client-build-time-config-dev-service-configuration.adoc[opts=optional, leveloffset=+1] == Elasticsearch diff --git a/docs/src/main/asciidoc/infinispan-client-reference.adoc b/docs/src/main/asciidoc/infinispan-client-reference.adoc index 8391103168c2e0..8ee6a65733c0be 100644 --- a/docs/src/main/asciidoc/infinispan-client-reference.adoc +++ b/docs/src/main/asciidoc/infinispan-client-reference.adoc @@ -115,6 +115,41 @@ quarkus.infinispan-client.client-intelligence=BASIC <2> Use Infinispan Dev Services to run a server and connect without configuration. ==== +=== Default and named connections +This extension lets you configure a _default_ Infinispan client connections and _named_ ones. +Named connections are essential to connect to multiple Infinispan clusters. + +The default connection is configured using the `quarkus.infinispan-client.*` properties as seen above. +When using the default connection, you can inject using a _plain_ `@Inject`: + +_Named_ clients are configured using the `quarkus.infinispan-client..*` properties: + +[source,properties] +---- +quarkus.infinispan-client.site-lon.hosts=localhost:11222 +quarkus.infinispan-client.site-lon.username=admin +quarkus.infinispan-client.site-lon.password=password + +quarkus.infinispan-client.site-nyc.hosts=localhost:31222 +quarkus.infinispan-client.site-nyc.username=admin +quarkus.infinispan-client.site-nyc.password=password +---- + +Use the `@InfinispanClientName` qualifier with dependency injection: +[source,java] +---- +@ApplicationScoped +public class InfinispanExample { + @Inject + @InfinispanClientName("site-lon") + RemoteCacheManager rcmLon; + + @Inject + @InfinispanClientName("site-nyc") + RemoteCacheManager rmcNyc; +} +---- + === Infinispan Health Check If you are using the quarkus-smallrye-health extension, the Infinispan client extensions will automatically add a readiness health check to validate the connection. @@ -495,7 +530,8 @@ the field, constructor or method. In the below code we utilize field and constru .SomeClass.java [source,java] ---- - @Inject SomeClass(RemoteCacheManager remoteCacheManager) { + @Inject + SomeClass(RemoteCacheManager remoteCacheManager) { this.remoteCacheManager = remoteCacheManager; } @@ -506,9 +542,28 @@ the field, constructor or method. In the below code we utilize field and constru RemoteCacheManager remoteCacheManager; ---- -If you notice the `RemoteCache` declaration has an additional optional annotation named `Remote`. -This is a qualifier annotation allowing you to specify which named cache that will be injected. This +If you notice the `RemoteCache` declaration has an additional annotation named `Remote`. +This is a *qualifier* annotation allowing you to specify which named cache that will be injected. This annotation is not required and if it is not supplied, the default cache will be injected. +The RemoteCacheManager bean scope is `@ApplicationScope`. +The RemoteCache bean scope is `@Singleton`. + +For non default connections, combine the qualifier `@InfinispanClientName` and `@Remote`. + +.SomeClass.java +[source,java] +---- + @Inject + @InfinispanClientName("lon-site") + @Remote("books") + RemoteCache lonBooks; + + @Inject + @InfinispanClientName("nyc-site") + @Remote("books") + RemoteCache nycBooks; +---- + NOTE: Other types may be supported for injection, please see other sections for more information diff --git a/docs/src/main/asciidoc/infinispan-dev-services.adoc b/docs/src/main/asciidoc/infinispan-dev-services.adoc index fb0c29b4aace58..84a9b9570cc878 100644 --- a/docs/src/main/asciidoc/infinispan-dev-services.adoc +++ b/docs/src/main/asciidoc/infinispan-dev-services.adoc @@ -14,7 +14,7 @@ Quarkus will automatically start an Infinispan container when running tests or d The following properties are available to customize the Infinispan Dev Services: -include::{generated-dir}/config/quarkus-infinispan-client-config-group-devservices-infinispan-dev-services-config.adoc[opts=optional, leveloffset=+1] +include::{generated-dir}/config/quarkus-infinispan-client-config-group-infinispan-client-build-time-config-dev-service-configuration.adoc[opts=optional, leveloffset=+1] When running the production version of the application, the Infinispan connection need to be configured as normal, so if you want to include a production database config in your `application.properties` and continue to use Dev Services @@ -79,6 +79,15 @@ and in the Infinispan Cross Site Replication link:https://infinispan.org/tutorials/simple/simple_tutorials.html#cross-site-replication_remote-cache-tutorials[simple code tutorial]. ==== +== Multiple Dev Services for named connections +The Infinispan Client extension supports connecting to more than one Infinispan Cluster with +the named connections. If you need to spin an additional dev service for a connection name, configure +at least on property in the application properties: + +[source,properties] +---- +quarkus.infinispan-client.conn-2.devservices.enabled=true +---- == Tracing with OpenTelemetry diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index fba0493740e4b1..bef2b1168ff346 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -108,6 +108,17 @@ + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-junit5-internal + test + + @@ -126,4 +137,19 @@ + + + + skip-tests-windows + + + windows + + + + true + true + + + diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanBindingProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanBindingProcessor.java new file mode 100644 index 00000000000000..78b22f6058d7b8 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanBindingProcessor.java @@ -0,0 +1,29 @@ + +package io.quarkus.infinispan.client.deployment; + +import static io.quarkus.infinispan.client.runtime.InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME; + +import java.util.List; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.infinispan.client.runtime.InfinispanClientUtil; +import io.quarkus.kubernetes.service.binding.spi.ServiceBindingQualifierBuildItem; + +public class InfinispanBindingProcessor { + + private static final String INFINISPAN = "infinispan"; + + @BuildStep + public void process(List clients, + BuildProducer bindings) { + clients.forEach(client -> { + if (InfinispanClientUtil.isDefault(client.getName())) { + bindings.produce( + new ServiceBindingQualifierBuildItem(INFINISPAN, INFINISPAN, DEFAULT_INFINISPAN_CLIENT_NAME)); + } else { + bindings.produce(new ServiceBindingQualifierBuildItem(INFINISPAN, client.getName())); + } + }); + } +} diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientBuildItem.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientBuildItem.java new file mode 100644 index 00000000000000..13c0e8fb701673 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientBuildItem.java @@ -0,0 +1,28 @@ +package io.quarkus.infinispan.client.deployment; + +import org.infinispan.client.hotrod.RemoteCacheManager; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; + +/** + * Provide the Infinispan clients as RuntimeValue's. + */ +public final class InfinispanClientBuildItem extends MultiBuildItem { + private final RuntimeValue client; + private final String name; + + public InfinispanClientBuildItem(RuntimeValue client, + String name) { + this.client = client; + this.name = name; + } + + public RuntimeValue getClient() { + return client; + } + + public String getName() { + return name; + } +} diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientDevServiceBuildTimeConfig.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientDevServiceBuildTimeConfig.java deleted file mode 100644 index 193287e07cde32..00000000000000 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientDevServiceBuildTimeConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.quarkus.infinispan.client.deployment; - -import java.util.Objects; - -import io.quarkus.infinispan.client.deployment.devservices.InfinispanDevServicesConfig; -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; - -@ConfigRoot(name = "infinispan-client", phase = ConfigPhase.BUILD_TIME) -public class InfinispanClientDevServiceBuildTimeConfig { - - /** - * Default Dev services configuration. - */ - @ConfigItem(name = ConfigItem.PARENT) - public DevServiceConfiguration devService; - - @ConfigGroup - public static class DevServiceConfiguration { - /** - * Configuration for DevServices - *

- * DevServices allows Quarkus to automatically start Infinispan in dev and test mode. - */ - @ConfigItem - public InfinispanDevServicesConfig devservices; - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - DevServiceConfiguration that = (DevServiceConfiguration) o; - return Objects.equals(devservices, that.devservices); - } - - @Override - public int hashCode() { - return Objects.hash(devservices); - } - } -} diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientNameBuildItem.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientNameBuildItem.java new file mode 100644 index 00000000000000..95481aa42144a5 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientNameBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.infinispan.client.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Represents the values of the {@link io.quarkus.infinispan.client.InfinispanClientName}. + */ +public final class InfinispanClientNameBuildItem extends MultiBuildItem { + + private final String name; + + public InfinispanClientNameBuildItem(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java index bfffcab42a6b9f..1d871298e268d2 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java @@ -1,5 +1,9 @@ package io.quarkus.infinispan.client.deployment; +import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import static io.quarkus.infinispan.client.runtime.InfinispanClientProducer.PROTOBUF_FILE_PREFIX; +import static io.quarkus.infinispan.client.runtime.InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -7,13 +11,28 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Properties; +import java.util.Scanner; import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Default; +import jakarta.inject.Singleton; + +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.annotation.ClientListener; import org.infinispan.client.hotrod.configuration.NearCacheMode; import org.infinispan.client.hotrod.exceptions.HotRodClientException; @@ -22,6 +41,7 @@ import org.infinispan.client.hotrod.logging.LogFactory; import org.infinispan.commons.marshall.ProtoStreamMarshaller; import org.infinispan.commons.util.Util; +import org.infinispan.counter.api.CounterManager; import org.infinispan.protostream.BaseMarshaller; import org.infinispan.protostream.EnumMarshaller; import org.infinispan.protostream.FileDescriptorSource; @@ -29,15 +49,26 @@ import org.infinispan.protostream.MessageMarshaller; import org.infinispan.protostream.RawProtobufMarshaller; import org.infinispan.protostream.SerializationContextInitializer; +import org.infinispan.protostream.WrappedMessage; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; +import org.jboss.jandex.Type; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem; +import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; +import io.quarkus.arc.deployment.InjectionPointTransformerBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.Annotations; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.InjectionPointsTransformer; import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; @@ -58,8 +89,12 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; +import io.quarkus.infinispan.client.InfinispanClientName; +import io.quarkus.infinispan.client.Remote; import io.quarkus.infinispan.client.runtime.InfinispanClientBuildTimeConfig; import io.quarkus.infinispan.client.runtime.InfinispanClientProducer; +import io.quarkus.infinispan.client.runtime.InfinispanClientUtil; +import io.quarkus.infinispan.client.runtime.InfinispanClientsBuildTimeConfig; import io.quarkus.infinispan.client.runtime.InfinispanRecorder; import io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter; import io.quarkus.infinispan.client.runtime.cache.CacheInvalidateAllInterceptor; @@ -73,21 +108,78 @@ class InfinispanClientProcessor { private static final Log log = LogFactory.getLog(InfinispanClientProcessor.class); private static final String SERVICE_BINDING_INTERFACE_NAME = "io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter"; + private static final DotName INFINISPAN_CLIENT_ANNOTATION = DotName.createSimple(InfinispanClientName.class.getName()); + private static final DotName INFINISPAN_REMOTE_ANNOTATION = DotName.createSimple(Remote.class.getName()); + + private static final DotName INFINISPAN_CLIENT = DotName.createSimple(RemoteCacheManager.class.getName()); + private static final DotName INFINISPAN_COUNTER_MANAGER = DotName.createSimple(CounterManager.class.getName()); + private static final DotName INFINISPAN_CACHE_CLIENT = DotName.createSimple(RemoteCache.class.getName()); private static final String META_INF = "META-INF"; - private static final String HOTROD_CLIENT_PROPERTIES = "hotrod-client.properties"; + private static final String DEFAULT_HOTROD_CLIENT_PROPERTIES = "hotrod-client.properties"; private static final String PROTO_EXTENSION = ".proto"; private static final String SASL_SECURITY_PROVIDER = "com.sun.security.sasl.Provider"; + private static final List SUPPORTED_INJECTION_TYPE = List.of( + // Client types + INFINISPAN_CLIENT, + INFINISPAN_COUNTER_MANAGER, + INFINISPAN_CACHE_CLIENT); + /** * The Infinispan client build time configuration. */ - InfinispanClientBuildTimeConfig infinispanClient; + InfinispanClientsBuildTimeConfig infinispanClientsBuildTimeConfig; @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) NativeImageFeatureBuildItem nativeImageFeature() { return new NativeImageFeatureBuildItem(DisableLoggingFeature.class); } + /** + * Sets up additional properties for use when proto stream marshaller is in use + */ + @BuildStep + public void handleProtoStreamRequirements(BuildProducer protostreamPropertiesBuildItem) + throws ClassNotFoundException { + // We only apply this if we are in native mode in build time to apply to the properties + // Note that the other half is done in QuerySubstitutions.SubstituteMarshallerRegistration class + // Note that the registration of these files are done twice in normal VM mode + // (once during init and once at runtime) + Properties properties = new Properties(); + try { + properties.put(PROTOBUF_FILE_PREFIX + WrappedMessage.PROTO_FILE, + getContents("/" + WrappedMessage.PROTO_FILE)); + String queryProtoFile = "org/infinispan/query/remote/client/query.proto"; + properties.put(PROTOBUF_FILE_PREFIX + queryProtoFile, getContents("/" + queryProtoFile)); + } catch (Exception ex) { + // Do nothing if fails + } + + Map marshallers = new HashMap<>(); + initMarshaller(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME, + infinispanClientsBuildTimeConfig.defaultInfinispanClient.marshallerClass, marshallers); + for (String clientName : infinispanClientsBuildTimeConfig.getInfinispanNamedClientConfigNames()) { + initMarshaller(clientName, + infinispanClientsBuildTimeConfig.getInfinispanClientBuildTimeConfig(clientName).marshallerClass, + marshallers); + } + protostreamPropertiesBuildItem.produce(new MarshallingBuildItem(properties, marshallers)); + } + + private static void initMarshaller(String clientName, Optional marshallerOpt, Map marshallers) + throws ClassNotFoundException { + + if (marshallerOpt.isPresent()) { + Class marshallerClass = Class.forName( + marshallerOpt.get(), false, + Thread.currentThread().getContextClassLoader()); + marshallers.put(clientName, Util.getInstance(marshallerClass)); + } else { + // Default to proto stream marshaller if one is not provided + marshallers.put(clientName, new ProtoStreamMarshaller()); + } + } + @BuildStep InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArchivesBuildItem, BuildProducer reflectiveClass, @@ -98,6 +190,8 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch BuildProducer sslNativeSupport, BuildProducer nativeImageSecurityProviders, BuildProducer nativeImageConfig, + BuildProducer infinispanClientNames, + MarshallingBuildItem marshallingBuildItem, CombinedIndexBuildItem applicationIndexBuildItem) throws ClassNotFoundException, IOException { feature.produce(new FeatureBuildItem(Feature.INFINISPAN_CLIENT)); @@ -106,98 +200,81 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CacheResultInterceptor.class)); additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CacheInvalidateInterceptor.class)); additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(SynchronousInfinispanGet.class)); + additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(InfinispanClientName.class).build()); + additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(Remote.class).build()); + systemProperties.produce(new SystemPropertyBuildItem("io.netty.noUnsafe", "true")); - hotDeployment.produce(new HotDeploymentWatchedFileBuildItem(META_INF + File.separator + HOTROD_CLIENT_PROPERTIES)); + hotDeployment + .produce(new HotDeploymentWatchedFileBuildItem(META_INF + File.separator + DEFAULT_HOTROD_CLIENT_PROPERTIES)); // Enable SSL support by default sslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(Feature.INFINISPAN_CLIENT)); nativeImageSecurityProviders.produce(new NativeImageSecurityProviderBuildItem(SASL_SECURITY_PROVIDER)); - InputStream stream = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(META_INF + "/" + HOTROD_CLIENT_PROPERTIES); - Properties properties; - if (stream == null) { - properties = new Properties(); - if (log.isTraceEnabled()) { - log.trace("There was no hotrod-client.properties file found - using defaults"); - } - } else { - try { - properties = loadFromStream(stream); - if (log.isDebugEnabled()) { - log.debugf("Found HotRod properties of %s", properties); - } - } finally { - Util.close(stream); - } + Map propertiesMap = new HashMap<>(); + IndexView index = applicationIndexBuildItem.getIndex(); - // We use caffeine for bounded near cache - so register that reflection if we have a bounded near cache - if (properties.containsKey(ConfigurationProperties.NEAR_CACHE_MAX_ENTRIES)) { - reflectiveClass.produce(ReflectiveClassBuildItem.builder("com.github.benmanes.caffeine.cache.SSMS") - .build()); - reflectiveClass.produce(ReflectiveClassBuildItem.builder("com.github.benmanes.caffeine.cache.PSMS") - .build()); - } - } + // named and default + Set allClientNames = infinispanClientNames(applicationIndexBuildItem, infinispanClientNames); + allClientNames.addAll(infinispanClientsBuildTimeConfig.getInfinispanNamedClientConfigNames()); + for (String clientName : allClientNames) { + Properties properties = loadHotrodProperties(clientName, reflectiveClass, marshallingBuildItem); + propertiesMap.put(clientName, properties); - InfinispanClientProducer.replaceProperties(properties); + // This is always non-null + Object marshaller = properties.get(ConfigurationProperties.MARSHALLER); - IndexView index = applicationIndexBuildItem.getIndex(); + if (marshaller instanceof ProtoStreamMarshaller) { + for (ApplicationArchive applicationArchive : applicationArchivesBuildItem.getAllApplicationArchives()) { + // If we have properties file we may have to care about + Path metaPath = applicationArchive.getChildPath(META_INF); - // This is always non-null - Object marshaller = properties.get(ConfigurationProperties.MARSHALLER); - - if (marshaller instanceof ProtoStreamMarshaller) { - for (ApplicationArchive applicationArchive : applicationArchivesBuildItem.getAllApplicationArchives()) { - // If we have properties file we may have to care about - Path metaPath = applicationArchive.getChildPath(META_INF); - - if (metaPath != null) { - try (Stream dirElements = Files.list(metaPath)) { - Iterator protoFiles = dirElements - .filter(Files::isRegularFile) - .filter(p -> p.toString().endsWith(PROTO_EXTENSION)) - .iterator(); - // We monitor the entire meta inf directory if properties are available - if (protoFiles.hasNext()) { - // Quarkus doesn't currently support hot deployment watching directories - // hotDeployment.produce(new HotDeploymentConfigFileBuildItem(META_INF)); - } - while (protoFiles.hasNext()) { - Path path = protoFiles.next(); - if (log.isDebugEnabled()) { - log.debug(" " + path.toAbsolutePath()); + if (metaPath != null) { + try (Stream dirElements = Files.list(metaPath)) { + Iterator protoFiles = dirElements + .filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(PROTO_EXTENSION)) + .iterator(); + // We monitor the entire meta inf directory if properties are available + if (protoFiles.hasNext()) { + // Quarkus doesn't currently support hot deployment watching directories + // hotDeployment.produce(new HotDeploymentConfigFileBuildItem(META_INF)); + } + while (protoFiles.hasNext()) { + Path path = protoFiles.next(); + if (log.isDebugEnabled()) { + log.debug(" " + path.toAbsolutePath()); + } + byte[] bytes = Files.readAllBytes(path); + // This uses the default file encoding - should we enforce UTF-8? + properties.put(PROTOBUF_FILE_PREFIX + path.getFileName().toString(), + new String(bytes, StandardCharsets.UTF_8)); } - byte[] bytes = Files.readAllBytes(path); - // This uses the default file encoding - should we enforce UTF-8? - properties.put(InfinispanClientProducer.PROTOBUF_FILE_PREFIX + path.getFileName().toString(), - new String(bytes, StandardCharsets.UTF_8)); } } } - } + properties.putAll(marshallingBuildItem.getProperties()); + Collection initializerClasses = index.getAllKnownImplementors(DotName.createSimple( + SerializationContextInitializer.class.getName())); + initializerClasses + .addAll(index.getAllKnownImplementors(DotName.createSimple(GeneratedSchema.class.getName()))); - InfinispanClientProducer.handleProtoStreamRequirements(properties); - Collection initializerClasses = index.getAllKnownImplementors(DotName.createSimple( - SerializationContextInitializer.class.getName())); - initializerClasses - .addAll(index.getAllKnownImplementors(DotName.createSimple(GeneratedSchema.class.getName()))); - - Set initializers = new HashSet<>(initializerClasses.size()); - for (ClassInfo ci : initializerClasses) { - Class initializerClass = Thread.currentThread().getContextClassLoader().loadClass(ci.toString()); - try { - SerializationContextInitializer sci = (SerializationContextInitializer) initializerClass - .getDeclaredConstructor().newInstance(); - initializers.add(sci); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException e) { - // This shouldn't ever be possible as annotation processor should generate empty constructor - throw new RuntimeException(e); + Set initializers = new HashSet<>(initializerClasses.size()); + for (ClassInfo ci : initializerClasses) { + Class initializerClass = Thread.currentThread().getContextClassLoader().loadClass(ci.toString()); + try { + SerializationContextInitializer sci = (SerializationContextInitializer) initializerClass + .getDeclaredConstructor().newInstance(); + initializers.add(sci); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + // This shouldn't ever be possible as annotation processor should generate empty constructor + throw new RuntimeException(e); + } + } + if (!initializers.isEmpty()) { + properties.put(InfinispanClientProducer.PROTOBUF_INITIALIZERS, initializers); } - } - if (!initializers.isEmpty()) { - properties.put(InfinispanClientProducer.PROTOBUF_INITIALIZERS, initializers); } } @@ -207,28 +284,134 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch for (AnnotationInstance instance : listenerInstances) { AnnotationTarget target = instance.target(); if (target.kind() == AnnotationTarget.Kind.CLASS) { - reflectiveClass.produce(ReflectiveClassBuildItem.builder(target.asClass().name().toString()).methods() - .build()); + reflectiveClass.produce(ReflectiveClassBuildItem.builder( + target.asClass().name().toString()) + .methods().build()); } } // This is required for netty to work properly - reflectiveClass.produce(ReflectiveClassBuildItem.builder("io.netty.channel.socket.nio.NioSocketChannel") - .build()); + reflectiveClass.produce(ReflectiveClassBuildItem.builder( + "io.netty.channel.socket.nio.NioSocketChannel").build()); // We use reflection to have continuous queries work - reflectiveClass.produce(ReflectiveClassBuildItem - .builder("org.infinispan.client.hotrod.event.impl.ContinuousQueryImpl$ClientEntryListener").methods() - .build()); + reflectiveClass.produce(ReflectiveClassBuildItem.builder( + "org.infinispan.client.hotrod.event.impl.ContinuousQueryImpl$ClientEntryListener") + .methods().build()); // We use reflection to allow for near cache invalidations - reflectiveClass.produce(ReflectiveClassBuildItem - .builder("org.infinispan.client.hotrod.near.NearCacheService$InvalidatedNearCacheListener").methods() - .build()); + reflectiveClass.produce(ReflectiveClassBuildItem.builder( + "org.infinispan.client.hotrod.near.NearCacheService$InvalidatedNearCacheListener") + .methods().build()); // This is required when a cache is clustered to tell us topology reflectiveClass.produce( - ReflectiveClassBuildItem.builder("org.infinispan.client.hotrod.impl.consistenthash.SegmentConsistentHash") + ReflectiveClassBuildItem.builder( + "org.infinispan.client.hotrod.impl.consistenthash.SegmentConsistentHash") .build()); - return new InfinispanPropertiesBuildItem(properties); + return new InfinispanPropertiesBuildItem(propertiesMap); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + BeanContainerListenerBuildItem build(InfinispanRecorder recorder, InfinispanPropertiesBuildItem builderBuildItem) { + Map propertiesMap = builderBuildItem.getProperties(); + + addMaxEntries(DEFAULT_INFINISPAN_CLIENT_NAME, + infinispanClientsBuildTimeConfig.defaultInfinispanClient, propertiesMap.get(DEFAULT_INFINISPAN_CLIENT_NAME)); + for (Map.Entry config : infinispanClientsBuildTimeConfig.namedInfinispanClients + .entrySet()) { + addMaxEntries(config.getKey(), config.getValue(), propertiesMap.get(config.getKey())); + } + + // This is necessary to be done for Protostream Marshaller init in native + return new BeanContainerListenerBuildItem(recorder.configureInfinispan(propertiesMap)); + } + + /** + * Reads all the contents of the file as a single string using default charset + * + * @param fileName file on class path to read contents of + * @return string containing the contents of the file + */ + private static String getContents(String fileName) { + InputStream stream = InfinispanClientProducer.class.getResourceAsStream(fileName); + return getContents(stream); + } + + /** + * Reads all the contents of the input stream as a single string using default charset + * + * @param stream to read contents of + * @return string containing the contents of the file + */ + private static String getContents(InputStream stream) { + try (Scanner scanner = new Scanner(stream, "UTF-8")) { + return scanner.useDelimiter("\\A").next(); + } + } + + private Set infinispanClientNames(CombinedIndexBuildItem indexBuildItem, + BuildProducer infinispanClientNames) { + Set clientNames = new HashSet<>(); + IndexView indexView = indexBuildItem.getIndex(); + // adds to clientNames all the client names scanned from @InfinispanClientName annotation + Collection infinispanClientAnnotations = indexView.getAnnotations(INFINISPAN_CLIENT_ANNOTATION); + for (AnnotationInstance annotation : infinispanClientAnnotations) { + clientNames.add(annotation.value().asString()); + } + // dev mode client name for default + if (infinispanClientsBuildTimeConfig.defaultInfinispanClient.devService.devservices.enabled) { + clientNames.add(DEFAULT_INFINISPAN_CLIENT_NAME); + } + + for (String clientName : clientNames) { + // Produce a client name for each client (produces later RemoteCacheManager and CounterManager instances) + infinispanClientNames.produce(new InfinispanClientNameBuildItem(clientName)); + } + return clientNames; + } + + private Properties loadHotrodProperties(String clientName, + BuildProducer reflectiveClass, + MarshallingBuildItem marshallingBuildItem) { + String filePath; + if (InfinispanClientUtil.isDefault(clientName)) { + filePath = META_INF + "/" + DEFAULT_HOTROD_CLIENT_PROPERTIES; + } else { + filePath = META_INF + "/" + clientName + "-" + DEFAULT_HOTROD_CLIENT_PROPERTIES; + } + InputStream stream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(filePath); + Properties properties; + if (stream == null) { + properties = new Properties(); + if (log.isTraceEnabled()) { + log.tracef("There was no %s file found - using defaults", filePath); + } + } else { + try { + properties = loadFromStream(stream); + if (log.isDebugEnabled()) { + log.debugf("Found %s properties of %s", filePath, properties); + } + } finally { + Util.close(stream); + } + + // We use caffeine for bounded near cache - so register that reflection if we have a bounded near cache + if (properties.containsKey(ConfigurationProperties.NEAR_CACHE_MAX_ENTRIES)) { + reflectiveClass + .produce(ReflectiveClassBuildItem.builder("com.github.benmanes.caffeine.cache.SSMS").build()); + reflectiveClass + .produce(ReflectiveClassBuildItem.builder("com.github.benmanes.caffeine.cache.PSMS").build()); + } + } + + Object marshaller = marshallingBuildItem.getMarshallerForClientName(clientName); + if (marshaller == null) { + marshaller = new ProtoStreamMarshaller(); + } + properties.put(ConfigurationProperties.MARSHALLER, marshaller); + return properties; } private Properties loadFromStream(InputStream stream) { @@ -241,23 +424,18 @@ private Properties loadFromStream(InputStream stream) { return properties; } - @BuildStep - @Record(ExecutionTime.STATIC_INIT) - BeanContainerListenerBuildItem build(InfinispanRecorder recorder, InfinispanPropertiesBuildItem builderBuildItem) { - Properties properties = builderBuildItem.getProperties(); - InfinispanClientBuildTimeConfig conf = infinispanClient; + private void addMaxEntries(String clientName, InfinispanClientBuildTimeConfig config, Properties properties) { if (log.isDebugEnabled()) { - log.debugf("Applying micro profile configuration: %s", conf); + log.debugf("Applying micro profile configuration: %s", config); } - int maxEntries = conf.nearCacheMaxEntries; // Only write the entries if it is a valid number and it isn't already configured - if (maxEntries > 0 && !properties.containsKey(ConfigurationProperties.NEAR_CACHE_MODE)) { + if (config.nearCacheMaxEntries > 0 && !properties.containsKey(ConfigurationProperties.NEAR_CACHE_MODE)) { // This is already empty so no need for putIfAbsent - properties.put(ConfigurationProperties.NEAR_CACHE_MODE, NearCacheMode.INVALIDATED.toString()); - properties.putIfAbsent(ConfigurationProperties.NEAR_CACHE_MAX_ENTRIES, maxEntries); + if (InfinispanClientUtil.isDefault(clientName)) { + properties.put(ConfigurationProperties.NEAR_CACHE_MODE, NearCacheMode.INVALIDATED.toString()); + properties.putIfAbsent(ConfigurationProperties.NEAR_CACHE_MAX_ENTRIES, config.nearCacheMaxEntries); + } } - - return new BeanContainerListenerBuildItem(recorder.configureInfinispan(properties)); } @BuildStep @@ -267,7 +445,7 @@ UnremovableBeanBuildItem ensureBeanLookupAvailable() { } @BuildStep - HealthBuildItem addHealthCheck(InfinispanClientBuildTimeConfig buildTimeConfig) { + HealthBuildItem addHealthCheck(InfinispanClientsBuildTimeConfig buildTimeConfig) { return new HealthBuildItem("io.quarkus.infinispan.client.runtime.health.InfinispanHealthCheck", buildTimeConfig.healthEnabled); } @@ -281,4 +459,156 @@ void registerServiceBinding(Capabilities capabilities, BuildProducer infinispanClientNames, + BeanDiscoveryFinishedBuildItem beans, + BuildProducer syntheticBeanBuildItemBuildProducer) { + + Set clientNames = infinispanClientNames.stream().map(icn -> icn.getName()).collect(Collectors.toSet()); + + Set remoteCacheBeans = beans.getInjectionPoints().stream() + .filter(ip -> ip.getRequiredQualifier(INFINISPAN_REMOTE_ANNOTATION) != null) + .map(ip -> { + AnnotationInstance remoteCacheQualifier = ip.getRequiredQualifier(INFINISPAN_REMOTE_ANNOTATION); + AnnotationInstance clientNameQualifier = ip.getRequiredQualifier(INFINISPAN_CLIENT_ANNOTATION); + + RemoteCacheBean remoteCacheBean = new RemoteCacheBean(); + remoteCacheBean.type = ip.getType(); + remoteCacheBean.cacheName = remoteCacheQualifier.value().asString(); + remoteCacheBean.clientName = clientNameQualifier == null ? DEFAULT_INFINISPAN_CLIENT_NAME + : clientNameQualifier.value().asString(); + return remoteCacheBean; + }) + .collect(Collectors.toSet()); + + if (!clientNames.contains(DEFAULT_INFINISPAN_CLIENT_NAME)) { + boolean createDefaultCacheManager = finishedBuildItem.getInjectionPoints().stream() + .filter(i -> SUPPORTED_INJECTION_TYPE.contains(i.getRequiredType().name()) + && i.getRequiredQualifier(INFINISPAN_CLIENT_ANNOTATION) == null) + .findAny() + .isPresent(); + if (createDefaultCacheManager) { + clientNames.add(DEFAULT_INFINISPAN_CLIENT_NAME); + } + } + + // Produce default and/or named RemoteCacheManager and CounterManager beans + for (String clientName : clientNames) { + syntheticBeanBuildItemBuildProducer.produce( + configureAndCreateSyntheticBean(clientName, RemoteCacheManager.class, + recorder.infinispanClientSupplier(clientName))); + syntheticBeanBuildItemBuildProducer.produce( + configureAndCreateSyntheticBean(clientName, CounterManager.class, + recorder.infinispanCounterManagerSupplier(clientName))); + } + + // Produce RemoteCache beans + for (RemoteCacheBean remoteCacheBean : remoteCacheBeans) { + syntheticBeanBuildItemBuildProducer.produce( + configureAndCreateSyntheticBean(remoteCacheBean, + recorder.infinispanRemoteCacheClientSupplier(remoteCacheBean.clientName, + remoteCacheBean.cacheName))); + } + } + + static SyntheticBeanBuildItem configureAndCreateSyntheticBean(String name, + Class type, + Supplier supplier) { + + SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem + .configure(type) + .supplier(supplier) + .scope(ApplicationScoped.class) + .unremovable() + .setRuntimeInit(); + + if (InfinispanClientUtil.isDefault(name)) { + configurator.addQualifier(Default.class); + } else { + configurator.addQualifier().annotation(DotNames.NAMED).addValue("value", name).done(); + configurator.addQualifier().annotation(INFINISPAN_CLIENT_ANNOTATION).addValue("value", name).done(); + } + + return configurator.done(); + } + + static SyntheticBeanBuildItem configureAndCreateSyntheticBean(RemoteCacheBean remoteCacheBean, Supplier supplier) { + SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem.configure(RemoteCache.class) + .types(remoteCacheBean.type) + .scope(Singleton.class) // Some Infinispan API won't work if this is not a Mock + .supplier(supplier) + .unremovable() + .setRuntimeInit(); + + configurator.addQualifier().annotation(INFINISPAN_REMOTE_ANNOTATION).addValue("value", remoteCacheBean.cacheName) + .done(); + + configurator.addQualifier().annotation(INFINISPAN_CLIENT_ANNOTATION).addValue("value", remoteCacheBean.clientName) + .done(); + return configurator.done(); + } + + @BuildStep + @Record(value = RUNTIME_INIT, optional = true) + List infinispanClients(InfinispanRecorder recorder, + List infinispanClientNames, + // make sure all beans have been initialized + @SuppressWarnings("unused") BeanContainerBuildItem beanContainer) { + List result = new ArrayList<>(infinispanClientNames.size()); + for (InfinispanClientNameBuildItem ic : infinispanClientNames) { + String name = ic.getName(); + result.add(new InfinispanClientBuildItem(recorder.getClient(name), name)); + } + return result; + } + } diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java index f275389a28d8ce..b70022097c3aaf 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java @@ -1,18 +1,19 @@ package io.quarkus.infinispan.client.deployment; +import java.util.Map; import java.util.Properties; import io.quarkus.builder.item.SimpleBuildItem; public final class InfinispanPropertiesBuildItem extends SimpleBuildItem { - private final Properties properties; + private final Map properties; - public InfinispanPropertiesBuildItem(Properties properties) { + public InfinispanPropertiesBuildItem(Map properties) { this.properties = properties; } - public Properties getProperties() { + public Map getProperties() { return properties; } } diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/MarshallingBuildItem.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/MarshallingBuildItem.java new file mode 100644 index 00000000000000..3b8af2f0f9b15e --- /dev/null +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/MarshallingBuildItem.java @@ -0,0 +1,28 @@ +package io.quarkus.infinispan.client.deployment; + +import java.util.Map; +import java.util.Properties; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class MarshallingBuildItem extends SimpleBuildItem { + + // holds protostream requierements + private final Properties properties; + + // a marshaller can be defined for different client names + private Map marshallers; + + public MarshallingBuildItem(Properties properties, Map marshallers) { + this.properties = properties; + this.marshallers = marshallers; + } + + public Object getMarshallerForClientName(String clientName) { + return marshallers.get(clientName); + } + + public Properties getProperties() { + return properties; + } +} diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevConsoleProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevConsoleProcessor.java index c6738fa21f4108..dbaad2ecbdcb6d 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevConsoleProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevConsoleProcessor.java @@ -1,16 +1,25 @@ package io.quarkus.infinispan.client.deployment.devconsole; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.runtime.BeanLookupSupplier; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; -import io.quarkus.infinispan.client.runtime.InfinispanServerUrlSupplier; +import io.quarkus.infinispan.client.runtime.devconsole.InfinispanClientsContainer; public class InfinispanDevConsoleProcessor { @BuildStep(onlyIf = IsDevelopment.class) public DevConsoleRuntimeTemplateInfoBuildItem infinispanServer(CurateOutcomeBuildItem curateOutcomeBuildItem) { - return new DevConsoleRuntimeTemplateInfoBuildItem("serverUrl", new InfinispanServerUrlSupplier(), this.getClass(), + return new DevConsoleRuntimeTemplateInfoBuildItem("infinispanClient", + new BeanLookupSupplier(InfinispanClientsContainer.class), + this.getClass(), curateOutcomeBuildItem); } + + @BuildStep(onlyIf = IsDevelopment.class) + public AdditionalBeanBuildItem beans() { + return AdditionalBeanBuildItem.unremovableOf(InfinispanClientsContainer.class); + } } diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java index fdb355c3f0a6fb..436353068a1bd6 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java @@ -5,7 +5,6 @@ import java.io.Closeable; import java.time.Duration; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,6 +13,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import org.infinispan.client.hotrod.configuration.ClientIntelligence; import org.infinispan.client.hotrod.impl.ConfigurationProperties; import org.infinispan.commons.util.Version; import org.infinispan.server.test.core.InfinispanContainer; @@ -36,7 +36,10 @@ import io.quarkus.deployment.logging.LoggingSetupBuildItem; import io.quarkus.devservices.common.ConfigureUtil; import io.quarkus.devservices.common.ContainerLocator; -import io.quarkus.infinispan.client.deployment.InfinispanClientDevServiceBuildTimeConfig; +import io.quarkus.infinispan.client.runtime.InfinispanClientBuildTimeConfig; +import io.quarkus.infinispan.client.runtime.InfinispanClientUtil; +import io.quarkus.infinispan.client.runtime.InfinispanClientsBuildTimeConfig; +import io.quarkus.infinispan.client.runtime.InfinispanDevServicesConfig; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigUtils; @@ -56,27 +59,40 @@ public class InfinispanDevServiceProcessor { private static final String DEFAULT_PASSWORD = "password"; private static final String QUARKUS = "quarkus."; private static final String DOT = "."; - private static volatile List devServices; - private static volatile InfinispanClientDevServiceBuildTimeConfig.DevServiceConfiguration capturedDevServicesConfiguration; + private static final String UNDERSCORE = "_"; + private static volatile Map devServices; + private static volatile Map capturedDevServicesConfiguration; private static volatile boolean first = true; + private static volatile Map properties = new HashMap<>(); @BuildStep public List startInfinispanContainers(LaunchModeBuildItem launchMode, DockerStatusBuildItem dockerStatusBuildItem, List devServicesSharedNetworkBuildItem, - InfinispanClientDevServiceBuildTimeConfig config, + InfinispanClientsBuildTimeConfig config, Optional consoleInstalledBuildItem, CuratedApplicationShutdownBuildItem closeBuildItem, - LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) { + LoggingSetupBuildItem loggingSetupBuildItem, + GlobalDevServicesConfig globalDevServicesConfig) { // figure out if we need to shut down and restart existing Infinispan containers // if not and the Infinispan containers have already started we just return if (devServices != null) { - boolean restartRequired = !config.devService.equals(capturedDevServicesConfiguration); + boolean restartRequired = false; + for (String devServiceName : devServices.keySet()) { + InfinispanClientBuildTimeConfig.DevServiceConfiguration devServiceConfig = capturedDevServicesConfiguration.get( + devServiceName); + restartRequired = restartRequired + || !config.getInfinispanClientBuildTimeConfig(devServiceName).devService.equals( + devServiceConfig); + + } + if (!restartRequired) { - return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); + return devServices.values().stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); } - for (Closeable closeable : devServices) { + + for (Closeable closeable : devServices.values()) { try { closeable.close(); } catch (Throwable e) { @@ -87,28 +103,29 @@ public List startInfinispanContainers(LaunchModeBuil capturedDevServicesConfiguration = null; } - capturedDevServicesConfiguration = config.devService; - List newDevServices = new ArrayList<>(); + capturedDevServicesConfiguration = new HashMap<>(); + Map newDevServices = new HashMap<>(); + capturedDevServicesConfiguration.put(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME, + config.defaultInfinispanClient.devService); + for (Map.Entry entry : config.namedInfinispanClients.entrySet()) { + capturedDevServicesConfiguration.put(entry.getKey(), entry.getValue().devService); + } StartupLogCompressor compressor = new StartupLogCompressor( (launchMode.isTest() ? "(test) " : "") + "Infinispan Dev Services Starting:", consoleInstalledBuildItem, loggingSetupBuildItem); - try { - RunningDevService devService = startContainer(dockerStatusBuildItem, config.devService.devservices, - launchMode.getLaunchMode(), - !devServicesSharedNetworkBuildItem.isEmpty(), devServicesConfig.timeout); - if (devService == null) { - compressor.closeAndDumpCaptured(); - return null; - } - newDevServices.add(devService); - log.infof("The infinispan server is ready to accept connections on %s", - devService.getConfig().get(getConfigPrefix() + "hosts")); - compressor.close(); - } catch (Throwable t) { - compressor.closeAndDumpCaptured(); - throw new RuntimeException(t); - } + + runInfinispanDevService(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME, launchMode, + compressor, dockerStatusBuildItem, devServicesSharedNetworkBuildItem, config.defaultInfinispanClient, + globalDevServicesConfig, newDevServices, + properties); + + config.namedInfinispanClients.entrySet().forEach(dServ -> { + runInfinispanDevService(dServ.getKey(), launchMode, + compressor, dockerStatusBuildItem, devServicesSharedNetworkBuildItem, dServ.getValue(), + globalDevServicesConfig, + newDevServices, properties); + }); devServices = newDevServices; @@ -116,7 +133,7 @@ public List startInfinispanContainers(LaunchModeBuil first = false; Runnable closeTask = () -> { if (devServices != null) { - for (Closeable closeable : devServices) { + for (Closeable closeable : devServices.values()) { try { closeable.close(); } catch (Throwable t) { @@ -130,19 +147,50 @@ public List startInfinispanContainers(LaunchModeBuil }; closeBuildItem.addCloseTask(closeTask, true); } - return devServices.stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); + return devServices.values().stream().map(RunningDevService::toBuildItem).collect(Collectors.toList()); + } + + private void runInfinispanDevService(String clientName, + LaunchModeBuildItem launchMode, + StartupLogCompressor compressor, + DockerStatusBuildItem dockerStatusBuildItem, + List devServicesSharedNetworkBuildItem, + InfinispanClientBuildTimeConfig config, + GlobalDevServicesConfig globalDevServicesConfig, + Map newDevServices, + Map properties) { + try { + log.infof("Starting Dev Service for connection %s", clientName); + InfinispanDevServicesConfig namedDevServiceConfig = config.devService.devservices; + log.infof("Apply Dev Services config %s", namedDevServiceConfig); + RunningDevService devService = startContainer(clientName, dockerStatusBuildItem, namedDevServiceConfig, + launchMode.getLaunchMode(), + !devServicesSharedNetworkBuildItem.isEmpty(), globalDevServicesConfig.timeout, properties); + if (devService == null) { + compressor.closeAndDumpCaptured(); + return; + } + newDevServices.put(clientName, devService); + log.infof("The infinispan server is ready to accept connections on %s", + devService.getConfig().get(getConfigPrefix(clientName) + "hosts")); + compressor.close(); + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); + } } - private RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuildItem, + private RunningDevService startContainer(String clientName, DockerStatusBuildItem dockerStatusBuildItem, InfinispanDevServicesConfig devServicesConfig, LaunchMode launchMode, - boolean useSharedNetwork, Optional timeout) { + boolean useSharedNetwork, Optional timeout, Map properties) { if (!devServicesConfig.enabled) { // explicitly disabled log.debug("Not starting devservices for Infinispan as it has been disabled in the config"); return null; } - String configPrefix = getConfigPrefix(); + String configPrefix = getConfigPrefix(clientName); + log.info("Config prefix " + configPrefix); boolean needToStart = !ConfigUtils.isPropertyPresent(configPrefix + "hosts") && !ConfigUtils.isPropertyPresent(configPrefix + "server-list"); @@ -158,36 +206,49 @@ private RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuild return null; } - Supplier defaultInfinispanServerSupplier = () -> { - QuarkusInfinispanContainer infinispanContainer = new QuarkusInfinispanContainer(devServicesConfig, launchMode, + Supplier infinispanServerSupplier = () -> { + QuarkusInfinispanContainer infinispanContainer = new QuarkusInfinispanContainer(clientName, devServicesConfig, + launchMode, useSharedNetwork); timeout.ifPresent(infinispanContainer::withStartupTimeout); infinispanContainer.start(); - return getRunningDevService(infinispanContainer.getContainerId(), infinispanContainer::close, + return getRunningDevService(clientName, infinispanContainer.getContainerId(), infinispanContainer::close, infinispanContainer.getHost() + ":" + infinispanContainer.getPort(), - infinispanContainer.getUser(), infinispanContainer.getPassword()); + infinispanContainer.getUser(), infinispanContainer.getPassword(), properties); }; return infinispanContainerLocator.locateContainer(devServicesConfig.serviceName, devServicesConfig.shared, launchMode) - .map(containerAddress -> getRunningDevService(containerAddress.getId(), null, - containerAddress.getUrl(), DEFAULT_USERNAME, DEFAULT_PASSWORD)) // TODO can this be always right ? - .orElseGet(defaultInfinispanServerSupplier); + .map(containerAddress -> getRunningDevService(clientName, containerAddress.getId(), null, + containerAddress.getUrl(), DEFAULT_USERNAME, DEFAULT_PASSWORD, properties)) // TODO can this be always right ? + .orElseGet(infinispanServerSupplier); } @NotNull - private RunningDevService getRunningDevService(String containerId, Closeable closeable, String serverList, - String username, String password) { - Map config = new HashMap<>(); - config.put(getConfigPrefix() + "hosts", serverList); - config.put(getConfigPrefix() + "client-intelligence", "BASIC"); - config.put(getConfigPrefix() + "username", username); - config.put(getConfigPrefix() + "password", password); - return new RunningDevService(Feature.INFINISPAN_CLIENT.getName(), containerId, closeable, config); + private RunningDevService getRunningDevService(String clientName, String containerId, Closeable closeable, String hosts, + String username, String password, Map config) { + config.put(getConfigPrefix(clientName) + "hosts", hosts); + config.put(getConfigPrefix(clientName) + "client-intelligence", ClientIntelligence.BASIC.name()); + config.put(getConfigPrefix(clientName) + "username", username); + config.put(getConfigPrefix(clientName) + "password", password); + return new RunningDevService(runningServiceName(clientName), containerId, closeable, config); + } + + private String runningServiceName(String clientName) { + if (InfinispanClientUtil.isDefault(clientName)) { + return Feature.INFINISPAN_CLIENT.getName(); + } + + return Feature.INFINISPAN_CLIENT.getName() + UNDERSCORE + clientName; + } - private String getConfigPrefix() { - return QUARKUS + "infinispan-client" + DOT; + private String getConfigPrefix(String name) { + if (name.equals(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME)) { + return QUARKUS + InfinispanClientUtil.INFINISPAN_CLIENT_CONFIG_ROOT_NAME + DOT; + } + + return QUARKUS + InfinispanClientUtil.INFINISPAN_CLIENT_CONFIG_ROOT_NAME + DOT + name + DOT; } private static class QuarkusInfinispanContainer extends InfinispanContainer { @@ -196,13 +257,19 @@ private static class QuarkusInfinispanContainer extends InfinispanContainer { private String hostName = null; - public QuarkusInfinispanContainer(InfinispanDevServicesConfig config, + public QuarkusInfinispanContainer(String clientName, InfinispanDevServicesConfig config, LaunchMode launchMode, boolean useSharedNetwork) { super(config.imageName.orElse(IMAGE_BASENAME + ":" + Version.getMajorMinor())); this.fixedExposedPort = config.port; this.useSharedNetwork = useSharedNetwork; if (launchMode == DEVELOPMENT) { - withLabel(DEV_SERVICE_LABEL, config.serviceName); + String label = config.serviceName; + if (InfinispanClientUtil.DEFAULT_INFINISPAN_DEV_SERVICE_NAME.equals(label) + && !InfinispanClientUtil.isDefault(clientName)) { + // Adds the client name suffix to create a different service name in named connections + label = label + UNDERSCORE + clientName; + } + withLabel(DEV_SERVICE_LABEL, label); } withUser(DEFAULT_USERNAME); withPassword(InfinispanDevServiceProcessor.DEFAULT_PASSWORD); diff --git a/extensions/infinispan-client/deployment/src/main/resources/dev-templates/embedded.html b/extensions/infinispan-client/deployment/src/main/resources/dev-templates/embedded.html index ff40695d67944d..7df7fba953e51d 100644 --- a/extensions/infinispan-client/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/infinispan-client/deployment/src/main/resources/dev-templates/embedded.html @@ -1,12 +1,8 @@ -{#if info:serverUrl != ""} - - Web Console +{#for clientInfo in info:infinispanClient.clientsInfo} + + Web Console ({clientInfo.name})
-{#else} - - Configuration is missing -
-{/if} +{/for} Documentation
diff --git a/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/DefaultRemoteCacheManagerCreationWithRemoteCacheTest.java b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/DefaultRemoteCacheManagerCreationWithRemoteCacheTest.java new file mode 100644 index 00000000000000..fa36ec921eb0b5 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/DefaultRemoteCacheManagerCreationWithRemoteCacheTest.java @@ -0,0 +1,48 @@ + +package io.quarkus.infinispan.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.inject.Default; +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.counter.api.CounterManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.infinispan.client.Remote; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultRemoteCacheManagerCreationWithRemoteCacheTest { + + //tag::injection[] + @Inject + @Remote("cache1") // default connection + RemoteCache cache1; + + @Inject + @Remote("cache2") // default connection + RemoteCache cache2; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("empty-application-infinispan-client.properties"); + + @Test + public void remoteCacheManagerDefaultBeansAccessible() { + assertThat(Arc.container().instance(RemoteCacheManager.class, Default.Literal.INSTANCE).get()).isNotNull(); + assertThat(Arc.container().instance(CounterManager.class, Default.Literal.INSTANCE).get()).isNotNull(); + assertThat(Arc.container().listAll(RemoteCacheManager.class).size()).isEqualTo(1); + assertThat(Arc.container().listAll(CounterManager.class).size()).isEqualTo(1); + assertThat(Arc.container().listAll(RemoteCache.class).size()).isEqualTo(2); + } + + @Test + public void cachesAreAccessible() { + assertThat(cache1).isNotNull(); + assertThat(cache2).isNotNull(); + } +} diff --git a/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/MultipleNamedInfinispanClientsTest.java b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/MultipleNamedInfinispanClientsTest.java new file mode 100644 index 00000000000000..d5adfc15c74c79 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/MultipleNamedInfinispanClientsTest.java @@ -0,0 +1,53 @@ +package io.quarkus.infinispan.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.literal.NamedLiteral; +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.counter.api.CounterManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.infinispan.client.InfinispanClientName; +import io.quarkus.test.QuarkusUnitTest; + +public class MultipleNamedInfinispanClientsTest { + + //tag::injection[] + @InfinispanClientName("site-lon") + @Inject + RemoteCacheManager siteLonCm; + + @InfinispanClientName("site-nyc") + @Inject + RemoteCacheManager siteNycCm; + //end::injection[] + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-multiple-devservices-infinispan-client.properties"); + + @Test + public void testContainerHasBeans() { + assertThat(Arc.container().listAll(RemoteCacheManager.class).size()).isEqualTo(2); + assertThat(Arc.container().listAll(CounterManager.class).size()).isEqualTo(2); + assertThat(Arc.container().instance(RemoteCacheManager.class, Default.Literal.INSTANCE).get()).isNull(); + assertThat(Arc.container().instance(CounterManager.class, Default.Literal.INSTANCE).get()).isNull(); + assertThat(Arc.container().instance(RemoteCacheManager.class, NamedLiteral.of("site-lon")).get()).isNotNull(); + assertThat(Arc.container().instance(CounterManager.class, NamedLiteral.of("site-lon")).get()).isNotNull(); + assertThat(Arc.container().instance(RemoteCacheManager.class, NamedLiteral.of("site-nyc")).get()).isNotNull(); + assertThat(Arc.container().instance(CounterManager.class, NamedLiteral.of("site-nyc")).get()).isNotNull(); + } + + @Test + public void testNamedDevServices() { + assertThat(siteLonCm.getConfiguration().servers().get(0).host()).isEqualTo("localhost"); + assertThat(siteLonCm.getConfiguration().servers().get(0).port()).isEqualTo(11222); + assertThat(siteNycCm.getConfiguration().servers().get(0).host()).isEqualTo("localhost"); + assertThat(siteNycCm.getConfiguration().servers().get(0).port()).isEqualTo(31222); + } +} diff --git a/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/NamedAndDefaultRemoteCacheClientNameTest.java b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/NamedAndDefaultRemoteCacheClientNameTest.java new file mode 100644 index 00000000000000..178f58d7ff5eb4 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/NamedAndDefaultRemoteCacheClientNameTest.java @@ -0,0 +1,61 @@ + +package io.quarkus.infinispan.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.literal.NamedLiteral; +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.counter.api.CounterManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.infinispan.client.InfinispanClientName; +import io.quarkus.infinispan.client.Remote; +import io.quarkus.test.QuarkusUnitTest; + +public class NamedAndDefaultRemoteCacheClientNameTest { + + //tag::injection[] + @Inject + @Remote("cache") // default connection + RemoteCache cache; + + @Inject + @InfinispanClientName("conn-2") // conn-2 connection + @Remote("cache") + RemoteCache cacheConn2; + //tag::injection[] + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("default-and-conn-2-application-infinispan-client.properties"); + + @Test + public void remoteCacheManagerDefaultBeansAccessible() { + assertThat(Arc.container().instance(RemoteCacheManager.class, Default.Literal.INSTANCE).get()).isNotNull(); + assertThat(Arc.container().instance(CounterManager.class, Default.Literal.INSTANCE).get()).isNotNull(); + assertThat(Arc.container().instance(RemoteCacheManager.class, NamedLiteral.of("conn-2")).get()).isNotNull(); + assertThat(Arc.container().instance(CounterManager.class, NamedLiteral.of("conn-2")).get()).isNotNull(); + assertThat(Arc.container().listAll(RemoteCache.class).size()).isEqualTo(2); + assertThat(Arc.container().listAll(RemoteCacheManager.class).size()).isEqualTo(2); + assertThat(Arc.container().listAll(CounterManager.class).size()).isEqualTo(2); + } + + @Test + public void cachesAreAccessible() { + assertThat(cache).isNotNull(); + assertThat(cacheConn2).isNotNull(); + + assertThat(cache.getRemoteCacheContainer().getConfiguration().servers().size()).isEqualTo(1); + assertThat(cache.getRemoteCacheContainer().getConfiguration().servers().get(0).host()).isEqualTo("localhost"); + assertThat(cache.getRemoteCacheContainer().getConfiguration().servers().get(0).port()).isEqualTo(11222); + assertThat(cacheConn2.getRemoteCacheContainer().getConfiguration().servers().size()).isEqualTo(1); + assertThat(cacheConn2.getRemoteCacheContainer().getConfiguration().servers().get(0).host()).isEqualTo("localhost"); + assertThat(cacheConn2.getRemoteCacheContainer().getConfiguration().servers().get(0).port()).isEqualTo(31222); + } +} diff --git a/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/RemoteCacheManagerCreationWithRemoteCacheClientNameTest.java b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/RemoteCacheManagerCreationWithRemoteCacheClientNameTest.java new file mode 100644 index 00000000000000..dc5ca15cc095b7 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/io/quarkus/infinispan/test/RemoteCacheManagerCreationWithRemoteCacheClientNameTest.java @@ -0,0 +1,46 @@ + +package io.quarkus.infinispan.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.literal.NamedLiteral; +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.counter.api.CounterManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.infinispan.client.InfinispanClientName; +import io.quarkus.infinispan.client.Remote; +import io.quarkus.test.QuarkusUnitTest; + +public class RemoteCacheManagerCreationWithRemoteCacheClientNameTest { + + //tag::injection[] + @Inject + @InfinispanClientName("conn-2") + @Remote("cache1") + RemoteCache cache1; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("dev-service-conn-2-application-infinispan-client.properties"); + + @Test + public void remoteCacheManagerDefaultBeansAccessible() { + assertThat(Arc.container().instance(RemoteCacheManager.class, Default.Literal.INSTANCE).get()).isNull(); + assertThat(Arc.container().instance(CounterManager.class, Default.Literal.INSTANCE).get()).isNull(); + assertThat(Arc.container().instance(RemoteCacheManager.class, NamedLiteral.of("conn-2")).get()).isNotNull(); + assertThat(Arc.container().instance(CounterManager.class, NamedLiteral.of("conn-2")).get()).isNotNull(); + assertThat(Arc.container().listAll(RemoteCache.class).size()).isEqualTo(1); + } + + @Test + public void cacheIsAccessible() { + assertThat(cache1).isNotNull(); + } +} diff --git a/extensions/infinispan-client/deployment/src/test/resources/application-multiple-devservices-infinispan-client.properties b/extensions/infinispan-client/deployment/src/test/resources/application-multiple-devservices-infinispan-client.properties new file mode 100644 index 00000000000000..e4675af6ad7a26 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/application-multiple-devservices-infinispan-client.properties @@ -0,0 +1,7 @@ +quarkus.infinispan-client.devservices.enabled=false + +quarkus.infinispan-client.site-lon.devservices.enabled=true +quarkus.infinispan-client.site-lon.devservices.port=11222 + +quarkus.infinispan-client.site-nyc.devservices.enabled=true +quarkus.infinispan-client.site-nyc.devservices.port=31222 diff --git a/extensions/infinispan-client/deployment/src/test/resources/default-and-conn-2-application-infinispan-client.properties b/extensions/infinispan-client/deployment/src/test/resources/default-and-conn-2-application-infinispan-client.properties new file mode 100644 index 00000000000000..7bb2a3c28fe5b4 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/default-and-conn-2-application-infinispan-client.properties @@ -0,0 +1,8 @@ +quarkus.infinispan-client.devservices.enabled=true +quarkus.infinispan-client.devservices.port=11222 +quarkus.infinispan-client.devservices.mcast-port=46667 + +quarkus.infinispan-client.conn-2.devservices.enabled=true +quarkus.infinispan-client.conn-2.devservices.port=31222 +quarkus.infinispan-client.conn-2.devservices.mcast-port=46666 + diff --git a/extensions/infinispan-client/deployment/src/test/resources/dev-service-conn-2-application-infinispan-client.properties b/extensions/infinispan-client/deployment/src/test/resources/dev-service-conn-2-application-infinispan-client.properties new file mode 100644 index 00000000000000..82b3468952d60f --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/dev-service-conn-2-application-infinispan-client.properties @@ -0,0 +1,2 @@ +quarkus.infinispan-client.devservices.enabled=false +quarkus.infinispan-client.conn-2.devservices.enabled=true diff --git a/extensions/infinispan-client/deployment/src/test/resources/empty-application-infinispan-client.properties b/extensions/infinispan-client/deployment/src/test/resources/empty-application-infinispan-client.properties new file mode 100644 index 00000000000000..8b137891791fe9 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/empty-application-infinispan-client.properties @@ -0,0 +1 @@ + diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/InfinispanClientName.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/InfinispanClientName.java new file mode 100644 index 00000000000000..812b0afb769a8d --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/InfinispanClientName.java @@ -0,0 +1,61 @@ +package io.quarkus.infinispan.client; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Qualifier; + +/** + * Marker annotation to select the Infinispan client. + * + * For example, if the Infinispan connection is configured like so in {@code application.properties}: + * + *

+ * quarkus.infinispan-client.site-lon.hosts=localhost:11222
+ * 
+ * + * Then to inject the proper {@code RemoteCacheManager}, you would need to use {@code InfinispanClientName} like indicated + * below: + * + *
+ *     @Inject
+ *     @InfinispanClientName("site-lon")
+ *     RemoteCacheManager remoteCacheManager;
+ * 
+ */ +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +@Retention(RUNTIME) +@Documented +@Qualifier +public @interface InfinispanClientName { + /** + * The remote cache manager name. If no value is provided the default cache manager is assumed. + */ + String value(); + + class Literal extends AnnotationLiteral implements InfinispanClientName { + + public static Literal of(String value) { + return new Literal(value); + } + + private final String value; + + public Literal(String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + } +} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/Remote.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/Remote.java index 08b023d32b8892..840870a2c07e04 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/Remote.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/Remote.java @@ -10,7 +10,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import jakarta.enterprise.util.Nonbinding; +import jakarta.enterprise.util.AnnotationLiteral; import jakarta.inject.Qualifier; /** @@ -26,6 +26,23 @@ /** * The remote cache name. If no value is provided the default cache is assumed. */ - @Nonbinding String value() default ""; + + class Literal extends AnnotationLiteral implements Remote { + + public static Literal of(String value) { + return new Literal(value); + } + + private final String value; + + public Literal(String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java index c474d7d77da782..6f625e0d6ee3b3 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java @@ -1,31 +1,66 @@ package io.quarkus.infinispan.client.runtime; +import java.util.Objects; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; /** * @author William Burns */ -@ConfigRoot(name = "infinispan-client", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +@ConfigGroup public class InfinispanClientBuildTimeConfig { /** - * Whether a health check is published in case the smallrye-health extension is present. + * Sets the bounded entry count for near cache. If this value is 0 or less near cache is disabled. */ - @ConfigItem(name = "health.enabled", defaultValue = "true") - public boolean healthEnabled; + @ConfigItem + public int nearCacheMaxEntries; /** - * Sets the bounded entry count for near cache. If this value is 0 or less near cache is disabled. + * Sets the marshallerClass. Default is ProtoStreamMarshaller */ @ConfigItem - public int nearCacheMaxEntries; + public Optional marshallerClass; + + /** + * Configuration for DevServices. DevServices allows Quarkus to automatically start an Infinispan Server in dev and test + * mode. + */ + + @ConfigItem(name = ConfigItem.PARENT) + public DevServiceConfiguration devService; + + @ConfigGroup + public static class DevServiceConfiguration { + /** + * Configuration for DevServices + *

+ * DevServices allows Quarkus to automatically start Infinispan in dev and test mode. + */ + @ConfigItem + public InfinispanDevServicesConfig devservices; + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + DevServiceConfiguration that = (DevServiceConfiguration) o; + return Objects.equals(devservices, that.devservices); + } + + @Override + public int hashCode() { + return Objects.hash(devservices); + } + } @Override public String toString() { return "InfinispanClientBuildTimeConfig{" + - "healthEnabled=" + healthEnabled + ", nearCacheMaxEntries=" - + nearCacheMaxEntries + '}'; + "nearCacheMaxEntries=" + nearCacheMaxEntries + '}'; } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java index f87311b1efdcb6..dbf227a7078242 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java @@ -1,22 +1,21 @@ package io.quarkus.infinispan.client.runtime; -import java.io.InputStream; -import java.lang.annotation.Annotation; import java.net.URL; +import java.util.HashMap; import java.util.Map; import java.util.Properties; -import java.util.Scanner; import java.util.Set; import jakarta.annotation.PreDestroy; -import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.literal.NamedLiteral; import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; -import jakarta.enterprise.inject.spi.InjectionPoint; import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; @@ -25,160 +24,120 @@ import org.infinispan.client.hotrod.impl.ConfigurationProperties; import org.infinispan.client.hotrod.logging.Log; import org.infinispan.client.hotrod.logging.LogFactory; -import org.infinispan.commons.configuration.XMLStringConfiguration; +import org.infinispan.commons.configuration.StringConfiguration; import org.infinispan.commons.marshall.Marshaller; import org.infinispan.commons.marshall.ProtoStreamMarshaller; -import org.infinispan.commons.util.Util; import org.infinispan.counter.api.CounterManager; import org.infinispan.protostream.BaseMarshaller; import org.infinispan.protostream.FileDescriptorSource; import org.infinispan.protostream.SerializationContext; import org.infinispan.protostream.SerializationContextInitializer; -import org.infinispan.protostream.WrappedMessage; import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; +import io.quarkus.arc.Arc; + /** * Produces a configured remote cache manager instance */ -@ApplicationScoped +@Singleton public class InfinispanClientProducer { private static final Log log = LogFactory.getLog(InfinispanClientProducer.class); - public static final String DEFAULT_CONFIG = ""; + public static final StringConfiguration DEFAULT_CONFIG = new StringConfiguration( + ""); public static final String PROTOBUF_FILE_PREFIX = "infinispan.client.hotrod.protofile."; public static final String PROTOBUF_INITIALIZERS = "infinispan.client.hotrod.proto-initializers"; + private final Map remoteCacheManagers = new HashMap<>(); + @Inject private BeanManager beanManager; - private volatile Properties properties; - private volatile RemoteCacheManager cacheManager; @Inject - private Instance infinispanClientRuntimeConfig; - - private void initialize() { - log.debug("Initializing CacheManager"); - if (properties == null) { - // We already loaded and it wasn't present - so don't initialize the cache manager - return; + private Instance infinispanClientsRuntimeConfigHandle; + + private void registerSchemaInServer(String infinispanConfigName, + Map properties, + RemoteCacheManager cacheManager) { + RemoteCache protobufMetadataCache = null; + Properties namedProperties = properties.get(infinispanConfigName); + Set initializers = (Set) namedProperties.remove(PROTOBUF_INITIALIZERS); + if (initializers != null) { + for (SerializationContextInitializer initializer : initializers) { + if (protobufMetadataCache == null) { + protobufMetadataCache = cacheManager.getCache( + ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); + } + protobufMetadataCache.put(initializer.getProtoFileName(), initializer.getProtoFile()); + } } - ConfigurationBuilder conf = builderFromProperties(properties); - if (conf.servers().isEmpty()) { - return; - } - // Build de cache manager if the server list is present - cacheManager = new RemoteCacheManager(conf.build()); - InfinispanClientRuntimeConfig infinispanClientRuntimeConfig = this.infinispanClientRuntimeConfig.get(); - - if (infinispanClientRuntimeConfig.useSchemaRegistration.orElse(Boolean.TRUE)) { - RemoteCache protobufMetadataCache = null; - Set initializers = (Set) properties.remove(PROTOBUF_INITIALIZERS); - if (initializers != null) { - for (SerializationContextInitializer initializer : initializers) { + for (Map.Entry property : namedProperties.entrySet()) { + Object key = property.getKey(); + if (key instanceof String) { + String keyString = (String) key; + if (keyString.startsWith(PROTOBUF_FILE_PREFIX)) { + String fileName = keyString.substring(PROTOBUF_FILE_PREFIX.length()); + String fileContents = (String) property.getValue(); if (protobufMetadataCache == null) { protobufMetadataCache = cacheManager.getCache( ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); } - protobufMetadataCache.put(initializer.getProtoFileName(), initializer.getProtoFile()); - } - } - for (Map.Entry property : properties.entrySet()) { - Object key = property.getKey(); - if (key instanceof String) { - String keyString = (String) key; - if (keyString.startsWith(InfinispanClientProducer.PROTOBUF_FILE_PREFIX)) { - String fileName = keyString.substring(InfinispanClientProducer.PROTOBUF_FILE_PREFIX.length()); - String fileContents = (String) property.getValue(); - if (protobufMetadataCache == null) { - protobufMetadataCache = cacheManager.getCache( - ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); - } - protobufMetadataCache.put(fileName, fileContents); - } + protobufMetadataCache.put(fileName, fileContents); } } } } - /** - * This method is designed to be called during static initialization time. This is so we have access to the - * classes, and thus we can use reflection to find and instantiate any instances we may need - * - * @param properties properties file read from hot rod - * @throws ClassNotFoundException if a class is not actually found that should be present - */ - public static void replaceProperties(Properties properties) throws ClassNotFoundException { - // If you are changing this method, you will most likely have to change builderFromProperties as well - String marshallerClassName = (String) properties.get(ConfigurationProperties.MARSHALLER); - if (marshallerClassName != null) { - Class marshallerClass = Class.forName(marshallerClassName, false, - Thread.currentThread().getContextClassLoader()); - properties.put(ConfigurationProperties.MARSHALLER, Util.getInstance(marshallerClass)); - } else { - // Default to proto stream marshaller if one is not provided - properties.put(ConfigurationProperties.MARSHALLER, new ProtoStreamMarshaller()); + private void initialize(String infinispanConfigName, Map properties) { + log.debug("Initializing default RemoteCacheManager"); + if (properties.isEmpty()) { + // We already loaded and it wasn't present - so don't initialize the cache manager + return; } - } - /** - * Sets up additional properties for use when proto stream marshaller is in use - * - * @param properties the properties to be updated for querying - */ - public static void handleProtoStreamRequirements(Properties properties) { - // We only apply this if we are in native mode in build time to apply to the properties - // Note that the other half is done in QuerySubstitutions.SubstituteMarshallerRegistration class - // Note that the registration of these files are done twice in normal VM mode - // (once during init and once at runtime) - properties.put(InfinispanClientProducer.PROTOBUF_FILE_PREFIX + WrappedMessage.PROTO_FILE, - getContents("/" + WrappedMessage.PROTO_FILE)); - String queryProtoFile = "org/infinispan/query/remote/client/query.proto"; - properties.put(InfinispanClientProducer.PROTOBUF_FILE_PREFIX + queryProtoFile, getContents("/" + queryProtoFile)); - } + ConfigurationBuilder conf = builderFromProperties(infinispanConfigName, properties); + if (conf.servers().isEmpty()) { + return; + } + // Build de cache manager if the server list is present + RemoteCacheManager cacheManager = new RemoteCacheManager(conf.build()); + remoteCacheManagers.put(infinispanConfigName, cacheManager); - /** - * Reads all the contents of the file as a single string using default charset - * - * @param fileName file on class path to read contents of - * @return string containing the contents of the file - */ - private static String getContents(String fileName) { - InputStream stream = InfinispanClientProducer.class.getResourceAsStream(fileName); - return getContents(stream); - } + InfinispanClientsRuntimeConfig infinispanClientsRuntimeConfig = this.infinispanClientsRuntimeConfigHandle.get(); - /** - * Reads all the contents of the input stream as a single string using default charset - * - * @param stream to read contents of - * @return string containing the contents of the file - */ - private static String getContents(InputStream stream) { - try (Scanner scanner = new Scanner(stream, "UTF-8")) { - return scanner.useDelimiter("\\A").next(); + if (infinispanClientsRuntimeConfig.useSchemaRegistration.orElse(Boolean.TRUE)) { + registerSchemaInServer(infinispanConfigName, properties, cacheManager); } } /** - * The mirror side of {@link #replaceProperties(Properties)} so that we can take out any objects that were - * instantiated during static init time and inject them properly + * Configures the client using the client name * - * @param properties the properties that was static constructed * @return the configuration builder based on the provided properties * @throws RuntimeException if the cache configuration file is not present in the resources folder */ - private ConfigurationBuilder builderFromProperties(Properties properties) { + private ConfigurationBuilder builderFromProperties(String infinispanClientName, Map propertiesMap) { // If you are changing this method, you will most likely have to change replaceProperties as well ConfigurationBuilder builder = new ConfigurationBuilder(); + Properties properties = propertiesMap.get(infinispanClientName); + // remove from properties Object marshallerInstance = properties.remove(ConfigurationProperties.MARSHALLER); if (marshallerInstance != null) { if (marshallerInstance instanceof ProtoStreamMarshaller) { handleProtoStreamMarshaller((ProtoStreamMarshaller) marshallerInstance, properties, beanManager); } + // add to the builder directly builder.marshaller((Marshaller) marshallerInstance); } - InfinispanClientRuntimeConfig infinispanClientRuntimeConfig = this.infinispanClientRuntimeConfig.get(); + + InfinispanClientRuntimeConfig infinispanClientRuntimeConfig = infinispanClientsRuntimeConfigHandle.get() + .getInfinispanClientRuntimeConfig(infinispanClientName); + + // client name not found + if (infinispanClientRuntimeConfig == null) { + return builder; + } if (infinispanClientRuntimeConfig.uri.isPresent()) { properties.put(ConfigurationProperties.URI, infinispanClientRuntimeConfig.uri.get()); @@ -228,15 +187,12 @@ private ConfigurationBuilder builderFromProperties(Properties properties) { if (infinispanClientRuntimeConfig.authRealm.isPresent()) { properties.put(ConfigurationProperties.AUTH_REALM, infinispanClientRuntimeConfig.authRealm.get()); } - if (infinispanClientRuntimeConfig.authServerName.isPresent()) { properties.put(ConfigurationProperties.AUTH_SERVER_NAME, infinispanClientRuntimeConfig.authServerName.get()); } - if (infinispanClientRuntimeConfig.authClientSubject.isPresent()) { properties.put(ConfigurationProperties.AUTH_CLIENT_SUBJECT, infinispanClientRuntimeConfig.authClientSubject.get()); } - if (infinispanClientRuntimeConfig.authCallbackHandler.isPresent()) { properties.put(ConfigurationProperties.AUTH_CALLBACK_HANDLER, infinispanClientRuntimeConfig.authCallbackHandler.get()); @@ -276,11 +232,14 @@ private ConfigurationBuilder builderFromProperties(Properties properties) { String cacheName = cache.getKey(); InfinispanClientRuntimeConfig.RemoteCacheConfig remoteCacheConfig = cache.getValue(); if (remoteCacheConfig.configurationUri.isPresent()) { + String cacheConfigUri = remoteCacheConfig.configurationUri.get(); + log.infof("Configuration URI for cache %s found: %s", cacheName, cacheConfigUri); URL configFile = Thread.currentThread().getContextClassLoader() - .getResource(remoteCacheConfig.configurationUri.get()); + .getResource(cacheConfigUri); try { builder.remoteCache(cacheName).configurationURI(configFile.toURI()); } catch (Exception e) { + throw new RuntimeException(e); } } else if (remoteCacheConfig.configuration.isPresent()) { @@ -305,7 +264,7 @@ private static void handleProtoStreamMarshaller(ProtoStreamMarshaller marshaller SerializationContext serializationContext = marshaller.getSerializationContext(); Set initializers = (Set) properties - .get(InfinispanClientProducer.PROTOBUF_INITIALIZERS); + .get(PROTOBUF_INITIALIZERS); if (initializers != null) { for (SerializationContextInitializer initializer : initializers) { initializer.registerSchema(serializationContext); @@ -318,8 +277,8 @@ private static void handleProtoStreamMarshaller(ProtoStreamMarshaller marshaller Object key = property.getKey(); if (key instanceof String) { String keyString = (String) key; - if (keyString.startsWith(InfinispanClientProducer.PROTOBUF_FILE_PREFIX)) { - String fileName = keyString.substring(InfinispanClientProducer.PROTOBUF_FILE_PREFIX.length()); + if (keyString.startsWith(PROTOBUF_FILE_PREFIX)) { + String fileName = keyString.substring(PROTOBUF_FILE_PREFIX.length()); String fileContents = (String) property.getValue(); if (fileDescriptorSource == null) { fileDescriptorSource = new FileDescriptorSource(); @@ -356,24 +315,24 @@ private static void handleProtoStreamMarshaller(ProtoStreamMarshaller marshaller @PreDestroy public void destroy() { - if (cacheManager != null) { - cacheManager.stop(); - } + remoteCacheManagers.values().forEach(rcm -> rcm.stop()); } - @io.quarkus.infinispan.client.Remote - @Produces - public RemoteCache getRemoteCache(InjectionPoint injectionPoint, RemoteCacheManager cacheManager) { - Set annotationSet = injectionPoint.getQualifiers(); - - final io.quarkus.infinispan.client.Remote remote = getRemoteAnnotation(annotationSet); + public RemoteCache getRemoteCache(String clientName, String cacheName) { + RemoteCacheManager cacheManager; + if (InfinispanClientUtil.isDefault(clientName)) { + cacheManager = Arc.container().instance(RemoteCacheManager.class, Default.Literal.INSTANCE).get(); + } else { + cacheManager = Arc.container().instance(RemoteCacheManager.class, NamedLiteral.of(clientName)) + .get(); + } - if (cacheManager != null && remote != null && !remote.value().isEmpty()) { - RemoteCache cache = cacheManager.getCache(remote.value()); + if (cacheManager != null && cacheName != null && !cacheName.isEmpty()) { + RemoteCache cache = cacheManager.getCache(cacheName); if (cache == null) { log.warn("Attempt to create cache using minimal default config"); return cacheManager.administration() - .getOrCreateCache(remote.value(), new XMLStringConfiguration(DEFAULT_CONFIG)); + .getOrCreateCache(cacheName, DEFAULT_CONFIG); } return cache; } @@ -383,50 +342,32 @@ public RemoteCache getRemoteCache(InjectionPoint injectionPoint, Re if (cache == null) { log.warn("Attempt to create cache using minimal default config"); return cacheManager.administration() - .getOrCreateCache(remote.value(), new XMLStringConfiguration(DEFAULT_CONFIG)); + .getOrCreateCache(cacheName, DEFAULT_CONFIG); } return cache; } - log.error("Unable to produce RemoteCache. RemoteCacheManager is null"); - return null; + log.error("Unable to produce RemoteCache. RemoteCacheManager is null. Client: " + cacheName); + throw new IllegalStateException( + "Unable to produce RemoteCache. RemoteCacheManager is null. Client: " + cacheName); } - @Produces - public CounterManager counterManager(RemoteCacheManager cacheManager) { - if (cacheManager == null) { - log.error("Unable to produce CounterManager. RemoteCacheManager is null"); - return null; - } - return RemoteCounterManagerFactory.asCounterManager(cacheManager); + Map properties; + + public void setProperties(Map properties) { + this.properties = properties; } - @Produces - public synchronized RemoteCacheManager remoteCacheManager() { - //TODO: should this just be application scoped? - if (cacheManager != null) { - return cacheManager; + public RemoteCacheManager getNamedRemoteCacheManager(String clientName) { + if (!remoteCacheManagers.containsKey(clientName)) { + initialize(clientName, properties); } - initialize(); - return cacheManager; + return remoteCacheManagers.get(clientName); } - void configure(Properties properties) { - this.properties = properties; + public CounterManager getNamedCounterManager(String clientName) { + RemoteCacheManager cacheManager = remoteCacheManagers.get(clientName); + return RemoteCounterManagerFactory.asCounterManager(cacheManager); } - /** - * Retrieves the deprecated {@link io.quarkus.infinispan.client.Remote} annotation instance from the set - * - * @param annotationSet the annotation set. - * @return the {@link io.quarkus.infinispan.client.Remote} annotation instance or {@code null} if not found. - */ - private io.quarkus.infinispan.client.Remote getRemoteAnnotation(Set annotationSet) { - for (Annotation annotation : annotationSet) { - if (annotation instanceof io.quarkus.infinispan.client.Remote) { - return (io.quarkus.infinispan.client.Remote) annotation; - } - } - return null; - } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java index 090a599424eab4..beed9c042b4a97 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java @@ -13,13 +13,11 @@ import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; /** * @author Katia Aresti */ -@ConfigRoot(name = "infinispan-client", phase = ConfigPhase.RUN_TIME) +@ConfigGroup public class InfinispanClientRuntimeConfig { // @formatter:off @@ -49,16 +47,6 @@ public class InfinispanClientRuntimeConfig { @Deprecated public Optional serverList; - // @formatter:off - /** - * Enables or disables Protobuf generated schemas upload to the server. - * Set it to 'false' when you need to handle the lifecycle of the Protobuf Schemas on Server side yourself. - * Default is 'true'. - */ - // @formatter:on - @ConfigItem(defaultValue = "true") - Optional useSchemaRegistration; - // @formatter:off /** * Sets client intelligence used by authentication @@ -263,7 +251,7 @@ public static class RemoteCacheConfig { @Override public String toString() { return "InfinispanClientRuntimeConfig{" + - "serverList=" + serverList + + "hosts=" + hosts + '}'; } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientUtil.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientUtil.java new file mode 100644 index 00000000000000..99c6b72c42c694 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientUtil.java @@ -0,0 +1,33 @@ +package io.quarkus.infinispan.client.runtime; + +import java.util.Collection; +import java.util.List; + +public final class InfinispanClientUtil { + + public static final String DEFAULT_INFINISPAN_DEV_SERVICE_NAME = "infinispan"; + public static final String DEFAULT_INFINISPAN_CLIENT_NAME = ""; + public static final String INFINISPAN_CLIENT_CONFIG_ROOT_NAME = "infinispan-client"; + + public static boolean isDefault(String infinispanClientName) { + return DEFAULT_INFINISPAN_CLIENT_NAME.equals(infinispanClientName); + } + + public static boolean hasDefault(Collection infinispanClientNames) { + return infinispanClientNames.contains(DEFAULT_INFINISPAN_CLIENT_NAME); + } + + public static List infinispanClientPropertyKeys(String infinispanClientName, String radical) { + if (infinispanClientName == null || InfinispanClientUtil.isDefault(infinispanClientName)) { + return List.of("quarkus.infinispan-client." + radical); + } else { + // Two possible syntaxes: with or without quotes + return List.of( + "quarkus.infinispan-client.\"" + infinispanClientName + "\"." + radical, + "quarkus.infinispan-client." + infinispanClientName + "." + radical); + } + } + + private InfinispanClientUtil() { + } +} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java new file mode 100644 index 00000000000000..c57a79c711e472 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java @@ -0,0 +1,46 @@ +package io.quarkus.infinispan.client.runtime; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = InfinispanClientUtil.INFINISPAN_CLIENT_CONFIG_ROOT_NAME, phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class InfinispanClientsBuildTimeConfig { + /** + * The default Infinispan Client. + */ + @ConfigItem(name = ConfigItem.PARENT) + public InfinispanClientBuildTimeConfig defaultInfinispanClient; + + /** + * Additional named Infinispan Client. + */ + @ConfigItem(name = ConfigItem.PARENT) + public Map namedInfinispanClients; + + /** + * Whether or not an health check is published in case the smallrye-health extension is present. + *

+ * This is a global setting and is not specific to an Infinispan Client. + */ + @ConfigItem(name = "health.enabled", defaultValue = "true") + public boolean healthEnabled; + + public Set getInfinispanNamedClientConfigNames() { + return Collections.unmodifiableSet(new HashSet<>(namedInfinispanClients.keySet())); + } + + public InfinispanClientBuildTimeConfig getInfinispanClientBuildTimeConfig(String infinispanClientName) { + if (InfinispanClientUtil.isDefault(infinispanClientName)) { + return defaultInfinispanClient; + } + InfinispanClientBuildTimeConfig infinispanClientBuildTimeConfig = namedInfinispanClients.get(infinispanClientName); + return Objects.requireNonNullElseGet(infinispanClientBuildTimeConfig, InfinispanClientBuildTimeConfig::new); + } +} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsRuntimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsRuntimeConfig.java new file mode 100644 index 00000000000000..d5d5031ebe0964 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsRuntimeConfig.java @@ -0,0 +1,43 @@ +package io.quarkus.infinispan.client.runtime; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = InfinispanClientUtil.INFINISPAN_CLIENT_CONFIG_ROOT_NAME, phase = ConfigPhase.RUN_TIME) +public class InfinispanClientsRuntimeConfig { + + /** + * The default Infinispan Client. + */ + @ConfigItem(name = ConfigItem.PARENT) + public InfinispanClientRuntimeConfig defaultInfinispanClient; + + /** + * Additional named Infinispan Client. + */ + @ConfigItem(name = ConfigItem.PARENT) + public Map namedInfinispanClients; + + // @formatter:off + /** + * Enables or disables Protobuf generated schemas upload to the server. + * Set it to 'false' when you need to handle the lifecycle of the Protobuf Schemas on Server side yourself. + * Default is 'true'. + * This is a global setting and is not specific to a Infinispan Client. + */ + // @formatter:on + @ConfigItem(defaultValue = "true") + Optional useSchemaRegistration; + + public InfinispanClientRuntimeConfig getInfinispanClientRuntimeConfig(String infinispanClientName) { + if (InfinispanClientUtil.isDefault(infinispanClientName)) { + return defaultInfinispanClient; + } + return namedInfinispanClients.get(infinispanClientName); + } +} diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServicesConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanDevServicesConfig.java similarity index 90% rename from extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServicesConfig.java rename to extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanDevServicesConfig.java index 1ad58e8e866462..7f6de3acd579f3 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServicesConfig.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanDevServicesConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.infinispan.client.deployment.devservices; +package io.quarkus.infinispan.client.runtime; import java.util.List; import java.util.Objects; @@ -53,7 +53,7 @@ public class InfinispanDevServicesConfig { *

* This property is used when you need multiple shared Infinispan servers. */ - @ConfigItem(defaultValue = "infinispan") + @ConfigItem(defaultValue = InfinispanClientUtil.DEFAULT_INFINISPAN_DEV_SERVICE_NAME) public String serviceName; /** @@ -129,4 +129,12 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(enabled, port, shared, serviceName, imageName, artifacts); } + + @Override + public String toString() { + return "InfinispanDevServicesConfig{" + "enabled=" + enabled + ", port=" + port + ", shared=" + shared + + ", serviceName='" + serviceName + '\'' + ", imageName=" + imageName + ", artifacts=" + artifacts + + ", site=" + site + ", mcastPort=" + mcastPort + ", tracing=" + tracing + ", exporterOtlpEndpoint=" + + exporterOtlpEndpoint + '}'; + } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanRecorder.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanRecorder.java index 5534f9658d78cf..cae1c6197d2577 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanRecorder.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanRecorder.java @@ -1,18 +1,85 @@ package io.quarkus.infinispan.client.runtime; +import java.util.Map; import java.util.Properties; +import java.util.function.Function; +import java.util.function.Supplier; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.literal.NamedLiteral; +import jakarta.enterprise.util.AnnotationLiteral; + +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.counter.api.CounterManager; + +import io.quarkus.arc.Arc; import io.quarkus.arc.runtime.BeanContainerListener; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.annotations.RelaxedValidation; @Recorder public class InfinispanRecorder { - public BeanContainerListener configureInfinispan(@RelaxedValidation Properties properties) { + public BeanContainerListener configureInfinispan(@RelaxedValidation Map properties) { return container -> { InfinispanClientProducer instance = container.beanInstance(InfinispanClientProducer.class); - instance.configure(properties); + instance.setProperties(properties); }; } + + public Supplier infinispanClientSupplier(String clientName) { + return new InfinispanClientSupplier<>(new Function() { + @Override + public RemoteCacheManager apply(InfinispanClientProducer infinispanClientProducer) { + return infinispanClientProducer.getNamedRemoteCacheManager(clientName); + } + }); + } + + public Supplier infinispanCounterManagerSupplier(String clientName) { + return new InfinispanClientSupplier<>(new Function() { + @Override + public CounterManager apply(InfinispanClientProducer infinispanClientProducer) { + return infinispanClientProducer.getNamedCounterManager(clientName); + } + }); + } + + public Supplier> infinispanRemoteCacheClientSupplier(String clientName, String cacheName) { + return new InfinispanClientSupplier<>(new Function>() { + @Override + public RemoteCache apply(InfinispanClientProducer infinispanClientProducer) { + return infinispanClientProducer.getRemoteCache(clientName, cacheName); + } + }); + } + + public RuntimeValue getClient(String name) { + return new RuntimeValue<>(Arc.container().instance(RemoteCacheManager.class, literal(name)).get()); + } + + @SuppressWarnings("rawtypes") + private AnnotationLiteral literal(String name) { + if (name.startsWith(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME)) { + return Default.Literal.INSTANCE; + } + return NamedLiteral.of(name); + } + + /** Helper to lazily create Infinispan clients. */ + static final class InfinispanClientSupplier implements Supplier { + private final Function producer; + + InfinispanClientSupplier(Function producer) { + this.producer = producer; + } + + @Override + public T get() { + InfinispanClientProducer infinispanClientProducer = Arc.container().instance(InfinispanClientProducer.class).get(); + return producer.apply(infinispanClientProducer); + } + } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServerUrlSupplier.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServerUrlSupplier.java deleted file mode 100644 index efceeea562f4ba..00000000000000 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServerUrlSupplier.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.quarkus.infinispan.client.runtime; - -import java.util.function.Supplier; - -import org.infinispan.client.hotrod.RemoteCacheManager; -import org.infinispan.client.hotrod.configuration.ServerConfiguration; - -import io.quarkus.arc.Arc; - -public class InfinispanServerUrlSupplier implements Supplier { - - @Override - public String get() { - RemoteCacheManager cacheManager = cacheManager(); - if (cacheManager == null || cacheManager.getConfiguration().servers().isEmpty()) { - return ""; - } - ServerConfiguration firstServer = cacheManager.getConfiguration().servers().get(0); - - return firstServer.host() + ":" + firstServer.port(); - } - - public static RemoteCacheManager cacheManager() { - return Arc.container().instance(RemoteCacheManager.class).get(); - } -} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInterceptor.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInterceptor.java index 3b4d5826c63ad7..d57cf898fc709c 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInterceptor.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInterceptor.java @@ -8,7 +8,7 @@ import java.util.concurrent.CompletionStage; import java.util.function.Supplier; -import jakarta.inject.Inject; +import jakarta.enterprise.inject.Default; import jakarta.interceptor.Interceptor.Priority; import jakarta.interceptor.InvocationContext; @@ -16,6 +16,7 @@ import org.infinispan.commons.CacheException; import org.jboss.logging.Logger; +import io.quarkus.arc.Arc; import io.quarkus.arc.runtime.InterceptorBindings; import io.smallrye.mutiny.Uni; @@ -26,8 +27,9 @@ public abstract class CacheInterceptor { private static final Logger LOGGER = Logger.getLogger(CacheInterceptor.class); - @Inject - RemoteCacheManager cacheManager; + // These annotations work with the default cache manager only, named connections are not supported. + // The configuration of the named connection will be supported in the infinispan-cache extension + RemoteCacheManager remoteCacheManager = Arc.container().instance(RemoteCacheManager.class, Default.Literal.INSTANCE).get(); /* * The interception is almost always managed by Arc in a Quarkus application. In such a case, we want to retrieve the @@ -47,6 +49,10 @@ public CacheInterceptionContext get() { }); } + protected RemoteCacheManager getRemoteCacheManager() { + return remoteCacheManager; + } + private Optional> getArcCacheInterceptionContext( InvocationContext invocationContext, Class interceptorBindingClass) { Set bindings = InterceptorBindings.getInterceptorBindings(invocationContext); diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateAllInterceptor.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateAllInterceptor.java index 32d53f7aa2af06..48fcf673e08cf7 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateAllInterceptor.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateAllInterceptor.java @@ -75,7 +75,7 @@ private Object invalidateAllBlocking(InvocationContext invocationContext, } private Uni invalidateAll(CacheInvalidateAll binding) { - RemoteCache cache = cacheManager.getCache(binding.cacheName()); + RemoteCache cache = getRemoteCacheManager().getCache(binding.cacheName()); LOGGER.debugf("Invalidating all entries from cache [%s]", binding.cacheName()); return Uni.createFrom().completionStage(new Supplier<>() { @Override diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateInterceptor.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateInterceptor.java index a6325346ebd8c0..34a6fecbd9e505 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateInterceptor.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInvalidateInterceptor.java @@ -78,7 +78,7 @@ private Object invalidateBlocking(InvocationContext invocationContext, } private Uni invalidate(CacheInvalidate binding, Object[] parameters) { - RemoteCache cache = cacheManager.getCache(binding.cacheName()); + RemoteCache cache = getRemoteCacheManager().getCache(binding.cacheName()); Object key = getCacheKey(parameters); LOGGER.debugf("Invalidating entry with key [%s] from cache [%s]", key, binding.cacheName()); return Uni.createFrom().completionStage(new Supplier<>() { diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheResultInterceptor.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheResultInterceptor.java index a420b2684ad261..fcf95f3c7f59fa 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheResultInterceptor.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheResultInterceptor.java @@ -50,7 +50,8 @@ public Object intercept(InvocationContext invocationContext) throws Throwable { } CacheResult binding = interceptionContext.getInterceptorBindings().get(0); - RemoteCache remoteCache = cacheManager.getCache(binding.cacheName()); + RemoteCache remoteCache = getRemoteCacheManager() + .getCache(binding.cacheName()); Object key = getCacheKey(invocationContext.getParameters()); InfinispanGetWrapper cache = new InfinispanGetWrapper(remoteCache, syncronousInfinispanGet.get(remoteCache.getName())); LOGGER.debugf("Loading entry with key [%s] from cache [%s]", key, binding.cacheName()); diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanClientsContainer.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanClientsContainer.java new file mode 100644 index 00000000000000..f2257b4ed64c09 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanClientsContainer.java @@ -0,0 +1,62 @@ +package io.quarkus.infinispan.client.runtime.devconsole; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import jakarta.inject.Singleton; + +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.ServerConfiguration; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.Unremovable; +import io.quarkus.arc.profile.IfBuildProfile; +import io.quarkus.infinispan.client.runtime.InfinispanClientUtil; + +@IfBuildProfile("dev") +@Unremovable +@Singleton +public class InfinispanClientsContainer { + + /** + * Used in Dev UI + * + * @return info about Infinispan clients + */ + public List clientsInfo() { + List> instanceHandles = Arc.container().listAll(RemoteCacheManager.class); + List infinispanClientInfos = new ArrayList<>(); + for (InstanceHandle ih : instanceHandles) { + InjectableBean bean = ih.getBean(); + Set annotationSet = bean.getQualifiers(); + String identifier = InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME; + for (Annotation annotation : annotationSet) { + if (annotation instanceof io.quarkus.infinispan.client.InfinispanClientName) { + // client name found is found + identifier = ((io.quarkus.infinispan.client.InfinispanClientName) annotation).value(); + } + } + List servers = ih.get().getConfiguration().servers(); + if (!servers.isEmpty()) { + ServerConfiguration firstServer = servers.get(0); + infinispanClientInfos.add( + new InfinispanClientInfo(identifier, firstServer.host() + ":" + firstServer.port())); + } + } + return infinispanClientInfos; + } + + public static class InfinispanClientInfo { + public String name; + public String serverUrl; + + public InfinispanClientInfo(String clientName, String serverUrl) { + this.name = clientName; + this.serverUrl = serverUrl; + } + } +} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/health/InfinispanHealthCheck.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/health/InfinispanHealthCheck.java index 5f9772dea44971..782d2f40449ec6 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/health/InfinispanHealthCheck.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/health/InfinispanHealthCheck.java @@ -1,9 +1,17 @@ package io.quarkus.infinispan.client.runtime.health; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Set; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.spi.Bean; import jakarta.inject.Inject; import org.eclipse.microprofile.health.HealthCheck; @@ -12,26 +20,153 @@ import org.eclipse.microprofile.health.Readiness; import org.infinispan.client.hotrod.RemoteCacheManager; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.infinispan.client.InfinispanClientName; +import io.quarkus.infinispan.client.runtime.InfinispanClientRuntimeConfig; +import io.quarkus.infinispan.client.runtime.InfinispanClientUtil; +import io.quarkus.infinispan.client.runtime.InfinispanClientsRuntimeConfig; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.infrastructure.Infrastructure; +import io.smallrye.mutiny.tuples.Tuple2; + @Readiness @ApplicationScoped public class InfinispanHealthCheck implements HealthCheck { + private final List checks = new ArrayList<>(); + @Inject - RemoteCacheManager cacheManager; + public InfinispanHealthCheck(InfinispanClientsRuntimeConfig config) { + configure(config); + } + + public void configure(InfinispanClientsRuntimeConfig config) { + Iterable> handle = Arc.container() + .select(RemoteCacheManager.class, Any.Literal.INSTANCE) + .handles(); + + if (config.defaultInfinispanClient != null) { + RemoteCacheManager client = getClient(handle, null); + if (client != null) { + checks.add(new InfinispanClientCheck(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME, client, + config.defaultInfinispanClient)); + } + } + + config.namedInfinispanClients.entrySet().forEach(new Consumer>() { + @Override + public void accept(Map.Entry namedInfinispanClientConfig) { + RemoteCacheManager client = getClient(handle, namedInfinispanClientConfig.getKey()); + if (client != null) { + checks.add(new InfinispanClientCheck(namedInfinispanClientConfig.getKey(), client, + namedInfinispanClientConfig.getValue())); + } + } + }); + } + + private class HealthInfo { + String state; + String servers; + int cachesCount; + + public HealthInfo(String state, String servers, int cachesCount) { + this.state = state; + this.servers = servers; + this.cachesCount = cachesCount; + } + } + + private class InfinispanClientCheck implements Supplier>> { + final String name; + final RemoteCacheManager remoteCacheManager; + final InfinispanClientRuntimeConfig config; + + InfinispanClientCheck(String name, RemoteCacheManager remoteCacheManager, InfinispanClientRuntimeConfig config) { + this.name = name; + this.remoteCacheManager = remoteCacheManager; + this.config = config; + } + + public Uni> get() { + return Uni.createFrom().item(new Supplier() { + @Override + public HealthInfo get() { + String servers = Arrays.toString(remoteCacheManager.getServers()); + int cacheNamesCount = remoteCacheManager.getCacheNames().size(); + return new HealthInfo("OK", servers, cacheNamesCount); + } + }) + .runSubscriptionOn(Infrastructure.getDefaultExecutor()) + .onItemOrFailure().transform(toResult(name)); + } + } + + private BiFunction> toResult(String name) { + return new BiFunction>() { + @Override + public Tuple2 apply(HealthInfo healthInfo, Throwable failure) { + return Tuple2.of(name, failure == null ? healthInfo : new HealthInfo(failure.getMessage(), null, -1)); + } + }; + } + + private RemoteCacheManager getClient(Iterable> handle, String name) { + for (InstanceHandle client : handle) { + String n = getInfinispanClientName(client.getBean()); + if (name == null && n == null) { + return client.get(); + } + if (name != null && name.equals(n)) { + return client.get(); + } + } + return null; + } + + private String getInfinispanClientName(Bean bean) { + for (Object qualifier : bean.getQualifiers()) { + if (qualifier instanceof InfinispanClientName) { + return ((InfinispanClientName) qualifier).value(); + } + } + return null; + } @Override public HealthCheckResponse call() { - HealthCheckResponseBuilder builder = HealthCheckResponse.named("Infinispan cluster health check"); - try { - if (cacheManager.isStarted()) { - Set cacheNames = cacheManager.getCacheNames(); - builder.up().withData("servers", Arrays.toString(cacheManager.getServers())) - .withData("caches-size", cacheNames.size()); + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Infinispan cluster connection health check").up(); + List>> unis = new ArrayList<>(); + for (InfinispanClientCheck clientCheck : checks) { + unis.add(clientCheck.get()); + } + + if (unis.isEmpty()) { + return builder.build(); + } + + return Uni.combine().all().unis(unis) + .collectFailures() // We collect all failures to avoid partial responses. + .combinedWith(new Function, HealthCheckResponse>() { + @Override + public HealthCheckResponse apply(List list) { + return InfinispanHealthCheck.this.combine(list, builder); + } + }).await().indefinitely(); // All checks fail after a timeout, so it won't be forever. + } + + @SuppressWarnings("unchecked") + private HealthCheckResponse combine(List results, HealthCheckResponseBuilder builder) { + for (Object result : results) { + Tuple2 tuple = (Tuple2) result; + if ("OK".equalsIgnoreCase(tuple.getItem2().state)) { + builder.up().withData(tuple.getItem1() + ".servers", tuple.getItem2().servers) + .withData(tuple.getItem1() + ".caches-size", tuple.getItem2().cachesCount); } else { - builder.down(); + builder.down() + .withData(tuple.getItem1(), "reason: " + tuple.getItem2().state); } - } catch (Exception e) { - return builder.down().withData("reason", e.getMessage()).build(); } return builder.build(); } diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookStoreService.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookStoreResource.java similarity index 97% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookStoreService.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookStoreResource.java index b5f6ce098d6028..fb602ca34861d0 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookStoreService.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookStoreResource.java @@ -15,7 +15,7 @@ import io.quarkus.infinispan.client.CacheResult; @Path("/books") -public class BookStoreService { +public class BookStoreResource { @GET @Path("{id}") diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/CacheSetup.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/CacheSetup.java index 1e6329b1a8f059..ed01e21347f4d7 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/CacheSetup.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/CacheSetup.java @@ -6,9 +6,6 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; @@ -31,6 +28,7 @@ import org.infinispan.query.dsl.Query; import org.infinispan.query.dsl.QueryFactory; +import io.quarkus.infinispan.client.InfinispanClientName; import io.quarkus.infinispan.client.Remote; import io.quarkus.runtime.StartupEvent; @@ -47,18 +45,26 @@ public class CacheSetup { @Inject RemoteCacheManager cacheManager; + @Inject + @InfinispanClientName("another") + RemoteCacheManager anotherCacheManager; + @Inject @Remote(AUTHORS_CACHE) RemoteCache authors; - private final Map matches = new ConcurrentHashMap<>(); + @Inject + @InfinispanClientName("another") + @Remote(AUTHORS_CACHE) + RemoteCache authorsSite2; - private CountDownLatch waitUntilStarted = new CountDownLatch(1); + private final Map matches = new ConcurrentHashMap<>(); void onStart(@Observes StartupEvent ev) { RemoteCache defaultCache = cacheManager.getCache(DEFAULT_CACHE); RemoteCache magazineCache = cacheManager.getCache(MAGAZINE_CACHE); RemoteCache booksCache = cacheManager.getCache(BOOKS_CACHE); + RemoteCache anotherBooksCache = anotherCacheManager.getCache(BOOKS_CACHE); defaultCache.addClientListener(new EventPrintListener()); @@ -91,40 +97,43 @@ public void resultUpdated(String key, Book value) { log.info("Added continuous query listener"); Author gMartin = new Author("George", "Martin"); - - defaultCache.put("book1", new Book("Game of Thrones", "Lots of people perish", 2010, - Collections.singleton(gMartin), Type.FANTASY, new BigDecimal("23.99"))); - defaultCache.put("book2", new Book("Game of Thrones Path 2", "They win?", 2023, - Collections.singleton(new Author("Son", "Martin")), Type.FANTASY, new BigDecimal("54.99"))); - - magazineCache.put("first-mad", new Magazine("MAD", YearMonth.of(1952, 10), - Collections.singletonList("Blob named Melvin"))); - magazineCache.put("first-time", new Magazine("TIME", YearMonth.of(1923, 3), + Author sonM = new Author("Son", "Martin"); + Author rowling = new Author("J. K. Rowling", "Rowling"); + + Book hp1Book = new Book("Philosopher's Stone", "Harry Potter and the Philosopher's Stone", 1997, + Collections.singleton(rowling), Type.FANTASY, new BigDecimal("50.99")); + Book hp2Book = new Book("Chamber of Secrets", "Harry Potter and the Chamber of Secrets", 1998, + Collections.singleton(rowling), Type.FANTASY, new BigDecimal("50.99")); + Book hp3Book = new Book("Prisoner of Azkaban", "Harry Potter and the Prisoner of Azkaban", 1999, + Collections.singleton(rowling), Type.FANTASY, new BigDecimal("50.99")); + Book got1Book = new Book("Game of Thrones", "Lots of people perish", 2010, Collections.singleton(gMartin), + Type.FANTASY, new BigDecimal("23.99")); + Book got2Book = new Book("Game of Thrones Path 2", "They win?", 2023, + Collections.singleton(sonM), Type.FANTASY, new BigDecimal("54.99")); + + defaultCache.put("book1", got1Book); + defaultCache.put("book2", got2Book); + + Magazine mag1 = new Magazine("MAD", YearMonth.of(1952, 10), Collections.singletonList("Blob named Melvin")); + Magazine mag2 = new Magazine("TIME", YearMonth.of(1923, 3), Arrays.asList("First helicopter", "Change in divorce law", "Adam's Rib movie released", - "German Reparation Payments"))); - magazineCache.put("popular-time", new Magazine("TIME", YearMonth.of(1997, 4), - Arrays.asList("Yep, I'm gay", "Backlash against HMOS", "False Hope on Breast Cancer?"))); + "German Reparation Payments")); + Magazine map3 = new Magazine("TIME", YearMonth.of(1997, 4), + Arrays.asList("Yep, I'm gay", "Backlash against HMOS", "False Hope on Breast Cancer?")); - authors.put("aut-1", gMartin); + magazineCache.put("first-mad", mag1); + magazineCache.put("first-time", mag2); + magazineCache.put("popular-time", map3); - booksCache.put("hp-1", new Book("Philosopher's Stone", "Harry Potter and the Philosopher's Stone", 1997, - Collections.singleton(new Author("J. K. Rowling", "Rowling")), Type.FANTASY, new BigDecimal("50.99"))); - booksCache.put("hp-2", new Book("Chamber of Secrets", "Harry Potter and the Chamber of Secrets", 1998, - Collections.singleton(new Author("J. K. Rowling", "Rowling")), Type.FANTASY, new BigDecimal("50.99"))); - booksCache.put("hp-3", new Book("Prisoner of Azkaban", "Harry Potter and the Prisoner of Azkaban", 1999, - Collections.singleton(new Author("J. K. Rowling", "Rowling")), Type.FANTASY, new BigDecimal("50.99"))); + authors.put("aut-1", gMartin); + authors.put("aut-2", sonM); + authorsSite2.put("aut-3", rowling); - waitUntilStarted.countDown(); - } + booksCache.put("hp-1", hp1Book); + booksCache.put("hp-2", hp2Book); + booksCache.put("hp-3", hp3Book); - public void ensureStarted() { - try { - if (!waitUntilStarted.await(10, TimeUnit.SECONDS)) { - throw new RuntimeException(new TimeoutException()); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + anotherBooksCache.put("hp-1", hp1Book); } public Map getMatches() { diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java index 24d2f9f63add01..33624e6f1efea6 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java @@ -1,5 +1,7 @@ package io.quarkus.it.infinispan.client; +import static io.quarkus.it.infinispan.client.CacheSetup.AUTHORS_CACHE; + import java.math.BigDecimal; import java.util.Collections; import java.util.List; @@ -34,6 +36,7 @@ import org.infinispan.query.dsl.Query; import org.infinispan.query.dsl.QueryFactory; +import io.quarkus.infinispan.client.InfinispanClientName; import io.quarkus.infinispan.client.Remote; import io.smallrye.common.annotation.Blocking; @@ -53,8 +56,13 @@ public class TestServlet { RemoteCache magazineCache; @Inject - @Remote(CacheSetup.AUTHORS_CACHE) - RemoteCache authorsCache; + @Remote(AUTHORS_CACHE) + RemoteCache authorsCacheDefault; + + @Inject + @InfinispanClientName("another") + @Remote(AUTHORS_CACHE) + RemoteCache authorsCacheAnother; @Inject CounterManager counterManager; @@ -62,7 +70,6 @@ public class TestServlet { @GET @Produces(MediaType.TEXT_PLAIN) public List getIDs() { - cacheSetup.ensureStarted(); log.info("Retrieving all IDs"); return cache.keySet().stream().sorted().collect(Collectors.toList()); } @@ -71,7 +78,6 @@ public List getIDs() { @GET @Produces(MediaType.TEXT_PLAIN) public String getCachedValue(@PathParam("id") String id) { - cacheSetup.ensureStarted(); Book book = cache.get(id); return book != null ? book.getTitle() : "NULL"; } @@ -80,7 +86,6 @@ public String getCachedValue(@PathParam("id") String id) { @GET @Produces(MediaType.TEXT_PLAIN) public String queryAuthorSurname(@PathParam("id") String name) { - cacheSetup.ensureStarted(); QueryFactory queryFactory = Search.getQueryFactory(cache); Query query = queryFactory.from(Book.class) .having("authors.name").like("%" + name + "%") @@ -102,7 +107,6 @@ public String queryAuthorSurname(@PathParam("id") String name) { @GET @Produces(MediaType.TEXT_PLAIN) public String ickleQueryAuthorSurname(@PathParam("id") String name) { - cacheSetup.ensureStarted(); QueryFactory queryFactory = Search.getQueryFactory(cache); Query query = queryFactory.create("from book_sample.Book b where b.authors.name like '%" + name + "%'"); List list = query.execute().list(); @@ -123,7 +127,6 @@ public String ickleQueryAuthorSurname(@PathParam("id") String name) { @Blocking public boolean defineCounter(@PathParam("id") String id, @QueryParam("type") String type, @QueryParam("storage") String storage) { - cacheSetup.ensureStarted(); CounterConfiguration configuration = counterManager.getConfiguration(id); if (configuration == null) { configuration = CounterConfiguration.builder(CounterType.valueOf(type)).storage(Storage.valueOf(storage)).build(); @@ -137,7 +140,6 @@ public boolean defineCounter(@PathParam("id") String id, @QueryParam("type") Str @Produces(MediaType.TEXT_PLAIN) @Blocking public CompletionStage incrementCounter(@PathParam("id") String id) { - cacheSetup.ensureStarted(); CounterConfiguration configuration = counterManager.getConfiguration(id); if (configuration == null) { return CompletableFuture.completedFuture(0L); @@ -157,7 +159,6 @@ public CompletionStage incrementCounter(@PathParam("id") String id) { @GET @Produces(MediaType.TEXT_PLAIN) public String continuousQuery() { - cacheSetup.ensureStarted(); return cacheSetup.getMatches().values().stream() .mapToInt(Book::getPublicationYear) .mapToObj(Integer::toString) @@ -168,7 +169,6 @@ public String continuousQuery() { @GET @Produces(MediaType.TEXT_PLAIN) public String nearCache() { - cacheSetup.ensureStarted(); RemoteCacheClientStatisticsMXBean stats = cache.clientStatistics(); long nearCacheMisses = stats.getNearCacheMisses(); long nearCacheHits = stats.getNearCacheHits(); @@ -238,7 +238,6 @@ public String nearCache() { @PUT @Consumes(MediaType.TEXT_PLAIN) public Response createItem(String value, @PathParam("id") String id) { - cacheSetup.ensureStarted(); Book book = new Book(id, value, 2019, Collections.emptySet(), Type.PROGRAMMING, new BigDecimal("9.99")); Book previous = cache.putIfAbsent(id, book); if (previous == null) { @@ -255,7 +254,6 @@ public Response createItem(String value, @PathParam("id") String id) { @Path("magazinequery/{id}") @GET public String magazineQuery(@PathParam("id") String name) { - cacheSetup.ensureStarted(); QueryFactory queryFactory = Search.getQueryFactory(magazineCache); Query query = queryFactory.create("from magazine_sample.Magazine m where m.name like '%" + name + "%'"); List list = query.execute().list(); @@ -270,9 +268,11 @@ public String magazineQuery(@PathParam("id") String name) { @Path("create-cache-default-config/authors") @GET public String magazineQuery() { - cacheSetup.ensureStarted(); - return authorsCache.values().stream() - .map(a -> a.getName()) - .collect(Collectors.joining(",", "[", "]")); + List names1 = authorsCacheDefault.values().stream().map(a -> a.getName()).collect(Collectors.toList()); + List names2 = authorsCacheAnother.values().stream().map(a -> a.getName()) + .collect(Collectors.toList()); + + names1.addAll(names2); + return names1.stream().sorted().collect(Collectors.joining(",", "[", "]")); } } diff --git a/integration-tests/infinispan-client/src/main/resources/application.properties b/integration-tests/infinispan-client/src/main/resources/application.properties index c24d2cabbad761..0b08500be50fde 100644 --- a/integration-tests/infinispan-client/src/main/resources/application.properties +++ b/integration-tests/infinispan-client/src/main/resources/application.properties @@ -7,4 +7,17 @@ quarkus.infinispan-client.cache.default.near-cache-use-bloom-filter=false quarkus.infinispan-client.cache.magazine.near-cache-mode=INVALIDATED quarkus.infinispan-client.cache.magazine.near-cache-max-entries=2 quarkus.infinispan-client.cache.magazine.near-cache-use-bloom-filter=false -quarkus.native.resources.includes=cacheConfig.xml,booksIndexedConfig.json \ No newline at end of file +quarkus.native.resources.includes=cacheConfig.xml,booksIndexedConfig.json + +# Dev services configuration +quarkus.infinispan-client.devservices.site=NYC +quarkus.infinispan-client.devservices.mcast-port=46666 +quarkus.infinispan-client.devservices.port=31222 +quarkus.infinispan-client.devservices.service-name=infinispan +quarkus.infinispan-client.another.devservices.site=LON +quarkus.infinispan-client.another.devservices.mcast-port=46667 +quarkus.infinispan-client.another.devservices.port=31223 +quarkus.infinispan-client.another.devservices.service-name=infinispanAnother + +# Have a default cache in another remote infinispan client +quarkus.infinispan-client.another.cache.books.configuration-uri=cacheConfig.xml diff --git a/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/HealthCheckTest.java b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/HealthCheckTest.java index 4a90224de796f4..418ccae3aef61d 100644 --- a/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/HealthCheckTest.java +++ b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/HealthCheckTest.java @@ -20,7 +20,9 @@ public void testHealthCheck() { .header("Content-Type", containsString("charset=UTF-8")) .body("status", is("UP"), "checks.status", containsInAnyOrder("UP"), - "checks.data", containsInAnyOrder(hasKey("servers")), - "checks.data", containsInAnyOrder(hasKey("caches-size"))); + "checks.data", containsInAnyOrder(hasKey(".servers")), + "checks.data", containsInAnyOrder(hasKey(".caches-size")), + "checks.data", containsInAnyOrder(hasKey("another.caches-size")), + "checks.data", containsInAnyOrder(hasKey("another.caches-size"))); } } diff --git a/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java index eaddbd5bafb1c5..6270942742e7f0 100644 --- a/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java +++ b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java @@ -79,7 +79,7 @@ public void testQueryWithCustomMarshaller() { @Test public void testAuthor() { - RestAssured.when().get("/test/create-cache-default-config/authors").then().body(is("[George]")); + RestAssured.when().get("/test/create-cache-default-config/authors").then().body(is("[George,J. K. Rowling,Son]")); } @Test From 66b06bb1c11f66da9912997f7e1b081073c8d4d4 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 19 Apr 2023 14:53:05 +0200 Subject: [PATCH 077/333] Normalize paths for POM Model providers --- .../bootstrap/resolver/maven/workspace/WorkspaceLoader.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/WorkspaceLoader.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/WorkspaceLoader.java index 0cab46d8b25c3a..d3772143e0d8ee 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/WorkspaceLoader.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/WorkspaceLoader.java @@ -153,7 +153,11 @@ private LocalProject loadAndCacheProject(Path pomFile) throws BootstrapMavenExce } private Model rawModel(Path pomFile) throws BootstrapMavenException { - final Path moduleDir = pomFile.getParent(); + var moduleDir = pomFile.getParent(); + if (moduleDir != null) { + // the path might not be normalized, while the modelProvider below would typically recognize normalized absolute paths + moduleDir = moduleDir.normalize().toAbsolutePath(); + } Model rawModel = rawModelCache.get(moduleDir); if (rawModel != null) { return rawModel; From d6bf8f75219010da77306041670ac05a590b5759 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 19 Apr 2023 15:11:55 +0200 Subject: [PATCH 078/333] Removed un-used arguments in some methods of ApplicationArchiveBuildStep --- .../index/ApplicationArchiveBuildStep.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java index e6224f583f0a2d..89f62b23054436 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java @@ -7,7 +7,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -15,7 +14,6 @@ import java.util.Set; import java.util.function.Function; -import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.Index; import org.jboss.jandex.IndexView; import org.jboss.jandex.Indexer; @@ -144,8 +142,7 @@ private List scanForOtherIndexes(QuarkusBuildCloseablesBuild markers.add(marker.endsWith("/") ? marker.substring(0, marker.length() - 1) : marker); } markers.add(IndexingUtil.JANDEX_INDEX); - addMarkerFilePaths(markers, root, curateOutcomeBuildItem, indexedPaths, appArchives, buildCloseables, - indexCache, removedResources); + addMarkerFilePaths(markers, root, indexedPaths, appArchives, indexCache, removedResources); //get paths that are included via index-dependencies addIndexDependencyPaths(indexDependencyBuildItem, root, indexedPaths, appArchives, buildCloseables, @@ -221,8 +218,7 @@ private static ApplicationArchive createApplicationArchive(QuarkusBuildCloseable } private static void addMarkerFilePaths(Set applicationArchiveMarkers, - ArchiveRootBuildItem root, CurateOutcomeBuildItem curateOutcomeBuildItem, Set indexedPaths, - List appArchives, QuarkusBuildCloseablesBuildItem buildCloseables, + ArchiveRootBuildItem root, Set indexedPaths, List appArchives, IndexCache indexCache, Map> removed) throws IOException { final QuarkusClassLoader cl = ((QuarkusClassLoader) Thread.currentThread().getContextClassLoader()); @@ -253,8 +249,7 @@ private static void addMarkerFilePaths(Set applicationArchiveMarkers, Index index = indexCache.cache.get(rootPath); if (index == null) { try { - index = IndexingUtil.indexTree(tree, - dependencyKey == null ? Collections.emptySet() : removed.get(dependencyKey)); + index = IndexingUtil.indexTree(tree, removed.get(dependencyKey)); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } @@ -268,15 +263,13 @@ private static void addMarkerFilePaths(Set applicationArchiveMarkers, if (visit == null || root.isExcludedFromIndexing(visit.getRoot())) { return null; } - final List indexes = new ArrayList<>(tree.getRoots().size()); + final Index index; try { - indexes.add(indexPathTree(tree, removed.get(dependencyKey))); + index = indexPathTree(tree, removed.get(dependencyKey)); } catch (IOException e) { throw new UncheckedIOException(e); } - return new ApplicationArchiveImpl( - indexes.size() == 1 ? indexes.get(0) : CompositeIndex.create(indexes), - tree, dependencyKey); + return new ApplicationArchiveImpl(index, tree, dependencyKey); }); if (archive != null) { appArchives.add(archive); From a1d395b2c716978107059faffae6786383ea80d6 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 19 Apr 2023 15:31:56 +0200 Subject: [PATCH 079/333] Minor cleanup in the LocalProject --- .../maven/workspace/LocalProject.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java index 97a4fd4e4d2f13..8e077fd109b438 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; @@ -13,6 +12,7 @@ import java.util.stream.Collectors; import org.apache.maven.model.Build; +import org.apache.maven.model.BuildBase; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; @@ -117,7 +117,7 @@ public static LocalProject loadWorkspace(BootstrapMavenContext ctx, Function build.getDirectory()), "target") - : Paths.get(modelBuildingResult.getEffectiveModel().getBuild().getDirectory()); + ? resolveRelativeToBaseDir(configuredBuildDir(this, BuildBase::getDirectory), "target") + : Path.of(modelBuildingResult.getEffectiveModel().getBuild().getDirectory()); } public Path getCodeGenOutputDir() { @@ -237,24 +237,24 @@ public Path getCodeGenOutputDir() { public Path getClassesDir() { return modelBuildingResult == null - ? resolveRelativeToBuildDir(configuredBuildDir(this, build -> build.getOutputDirectory()), "classes") - : Paths.get(modelBuildingResult.getEffectiveModel().getBuild().getOutputDirectory()); + ? resolveRelativeToBuildDir(configuredBuildDir(this, Build::getOutputDirectory), "classes") + : Path.of(modelBuildingResult.getEffectiveModel().getBuild().getOutputDirectory()); } public Path getTestClassesDir() { return modelBuildingResult == null - ? resolveRelativeToBuildDir(configuredBuildDir(this, build -> build.getTestOutputDirectory()), "test-classes") - : Paths.get(modelBuildingResult.getEffectiveModel().getBuild().getTestOutputDirectory()); + ? resolveRelativeToBuildDir(configuredBuildDir(this, Build::getTestOutputDirectory), "test-classes") + : Path.of(modelBuildingResult.getEffectiveModel().getBuild().getTestOutputDirectory()); } public Path getSourcesSourcesDir() { return modelBuildingResult == null - ? resolveRelativeToBaseDir(configuredBuildDir(this, build -> build.getSourceDirectory()), "src/main/java") - : Paths.get(modelBuildingResult.getEffectiveModel().getBuild().getSourceDirectory()); + ? resolveRelativeToBaseDir(configuredBuildDir(this, Build::getSourceDirectory), "src/main/java") + : Path.of(modelBuildingResult.getEffectiveModel().getBuild().getSourceDirectory()); } public Path getTestSourcesSourcesDir() { - return resolveRelativeToBaseDir(configuredBuildDir(this, build -> build.getTestSourceDirectory()), "src/test/java"); + return resolveRelativeToBaseDir(configuredBuildDir(this, Build::getTestSourceDirectory), "src/test/java"); } public Path getSourcesDir() { @@ -390,7 +390,7 @@ public WorkspaceModule toWorkspaceModule(BootstrapMavenContext ctx) { } } else if (plugin.getArtifactId().equals("maven-surefire-plugin") && plugin.getConfiguration() != null) { Object config = plugin.getConfiguration(); - if (config == null || !(config instanceof Xpp3Dom)) { + if (!(config instanceof Xpp3Dom)) { continue; } Xpp3Dom dom = (Xpp3Dom) config; @@ -491,7 +491,7 @@ private String resolveElementValue(String elementValue) { } private DefaultArtifactSources processJarPluginExecutionConfig(Object config, boolean test) { - if (config == null || !(config instanceof Xpp3Dom)) { + if (!(config instanceof Xpp3Dom)) { return null; } Xpp3Dom dom = (Xpp3Dom) config; From e6028f595f1e22bc81320f7b09af743b1060d69e Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 19 Apr 2023 15:42:20 +0200 Subject: [PATCH 080/333] Minor clean up in DevMojo --- .../main/java/io/quarkus/maven/DevMojo.java | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 9e7cdfee641654..dcca17357e4f94 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -118,7 +117,7 @@ @Mojo(name = "dev", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) public class DevMojo extends AbstractMojo { - private static final String KOTLIN_MAVEN_PLUGIN_GA = "org.jetbrains.kotlin:kotlin-maven-plugin"; + private static final String PROCESS_CLASSES = "process-classes"; /** * running any one of these phases means the compile phase will have been run, if these have @@ -126,7 +125,7 @@ public class DevMojo extends AbstractMojo { */ private static final List POST_COMPILE_PHASES = List.of( "compile", - "process-classes", + PROCESS_CLASSES, "generate-test-sources", "process-test-sources", "generate-test-resources", @@ -160,6 +159,7 @@ public class DevMojo extends AbstractMojo { "install", "deploy"); + private static final String IO_QUARKUS = "io.quarkus"; private static final String QUARKUS_GENERATE_CODE_GOAL = "generate-code"; private static final String QUARKUS_GENERATE_CODE_TESTS_GOAL = "generate-code-tests"; @@ -174,6 +174,7 @@ public class DevMojo extends AbstractMojo { private static final String IO_SMALLRYE = "io.smallrye"; private static final String ORG_JBOSS_JANDEX = "org.jboss.jandex"; private static final String JANDEX_MAVEN_PLUGIN = "jandex-maven-plugin"; + private static final String JANDEX = "jandex"; /** * The directory for compiled classes. @@ -271,10 +272,10 @@ public class DevMojo extends AbstractMojo { private String argsString; @Parameter - private Map environmentVariables = Collections.emptyMap(); + private Map environmentVariables = Map.of(); @Parameter - private Map systemProperties = Collections.emptyMap(); + private Map systemProperties = Map.of(); @Parameter(defaultValue = "${session}") private MavenSession session; @@ -542,8 +543,8 @@ private void handleAutoCompile() throws MojoExecutionException { boolean prepareNeeded = true; boolean prepareTestsNeeded = true; - String jandexGoalPhase = getGoalPhaseOrNull(IO_SMALLRYE, JANDEX_MAVEN_PLUGIN, "jandex", "process-classes"); - String legacyJandexGoalPhase = getGoalPhaseOrNull(ORG_JBOSS_JANDEX, JANDEX_MAVEN_PLUGIN, "jandex", "process-classes"); + String jandexGoalPhase = getGoalPhaseOrNull(IO_SMALLRYE, JANDEX_MAVEN_PLUGIN, JANDEX, PROCESS_CLASSES); + String legacyJandexGoalPhase = getGoalPhaseOrNull(ORG_JBOSS_JANDEX, JANDEX_MAVEN_PLUGIN, JANDEX, PROCESS_CLASSES); boolean indexClassNeeded = legacyJandexGoalPhase != null || jandexGoalPhase != null; List goals = session.getGoals(); @@ -598,7 +599,7 @@ private void handleAutoCompile() throws MojoExecutionException { } private void initToolchain() throws MojoExecutionException { - executeIfConfigured(ORG_APACHE_MAVEN_PLUGINS, MAVEN_TOOLCHAINS_PLUGIN, "toolchain", Collections.emptyMap()); + executeIfConfigured(ORG_APACHE_MAVEN_PLUGINS, MAVEN_TOOLCHAINS_PLUGIN, "toolchain", Map.of()); } private void triggerPrepare(boolean test) throws MojoExecutionException { @@ -610,9 +611,9 @@ private void triggerPrepare(boolean test) throws MojoExecutionException { private void initClassIndexes(boolean legacyJandex) throws MojoExecutionException { if (legacyJandex) { - executeIfConfigured(ORG_JBOSS_JANDEX, JANDEX_MAVEN_PLUGIN, "jandex", Collections.emptyMap()); + executeIfConfigured(ORG_JBOSS_JANDEX, JANDEX_MAVEN_PLUGIN, JANDEX, Map.of()); } else { - executeIfConfigured(IO_SMALLRYE, JANDEX_MAVEN_PLUGIN, "jandex", Collections.emptyMap()); + executeIfConfigured(IO_SMALLRYE, JANDEX_MAVEN_PLUGIN, JANDEX, Map.of()); } } @@ -628,12 +629,10 @@ private void triggerCompile(boolean test, boolean prepareNeeded) throws MojoExec } // compile the Kotlin sources if needed - executeIfConfigured(ORG_JETBRAINS_KOTLIN, KOTLIN_MAVEN_PLUGIN, test ? "test-compile" : "compile", - Collections.emptyMap()); + executeIfConfigured(ORG_JETBRAINS_KOTLIN, KOTLIN_MAVEN_PLUGIN, test ? "test-compile" : "compile", Map.of()); // Compile the Java sources if needed - executeIfConfigured(ORG_APACHE_MAVEN_PLUGINS, MAVEN_COMPILER_PLUGIN, test ? "testCompile" : "compile", - Collections.emptyMap()); + executeIfConfigured(ORG_APACHE_MAVEN_PLUGINS, MAVEN_COMPILER_PLUGIN, test ? "testCompile" : "compile", Map.of()); } /** @@ -644,8 +643,7 @@ private void handleResources(boolean test) throws MojoExecutionException { if (resources.isEmpty()) { return; } - executeIfConfigured(ORG_APACHE_MAVEN_PLUGINS, MAVEN_RESOURCES_PLUGIN, test ? "testResources" : "resources", - Collections.emptyMap()); + executeIfConfigured(ORG_APACHE_MAVEN_PLUGINS, MAVEN_RESOURCES_PLUGIN, test ? "testResources" : "resources", Map.of()); } private void executeIfConfigured(String pluginGroupId, String pluginArtifactId, String goal, Map params) @@ -787,7 +785,7 @@ private void addProject(MavenDevModeLauncher.Builder builder, ResolvedDependency Set testSourcePaths; String testClassesPath = null; Set testResourcePaths; - List activeProfiles = Collections.emptyList(); + List activeProfiles = List.of(); MavenProject mavenProject = null; if (module.getClassifier().isEmpty()) { @@ -1150,13 +1148,13 @@ private QuarkusDevModeLauncher newLauncher(Boolean debugPortOk) throws Exception if (noDeps) { addProject(builder, appModel.getAppArtifact(), true); - appModel.getApplicationModule().getBuildFiles().forEach(p -> builder.watchedBuildFile(p)); + appModel.getApplicationModule().getBuildFiles().forEach(builder::watchedBuildFile); builder.localArtifact( ArtifactKey.of(project.getGroupId(), project.getArtifactId(), null, ArtifactCoords.TYPE_JAR)); } else { for (ResolvedDependency project : DependenciesFilter.getReloadableModules(appModel)) { addProject(builder, project, project == appModel.getAppArtifact()); - project.getWorkspaceModule().getBuildFiles().forEach(p -> builder.watchedBuildFile(p)); + project.getWorkspaceModule().getBuildFiles().forEach(builder::watchedBuildFile); builder.localArtifact(project.getKey()); } } @@ -1234,7 +1232,7 @@ private void addQuarkusDevModeDeps(MavenDevModeLauncher.Builder builder, Applica ResolvedDependency coreDeployment = null; for (ResolvedDependency d : appModel.getDependencies()) { if (d.isDeploymentCp() && d.getArtifactId().equals("quarkus-core-deployment") - && d.getGroupId().equals("io.quarkus")) { + && d.getGroupId().equals(IO_QUARKUS)) { coreDeployment = d; break; } @@ -1290,7 +1288,7 @@ private void addQuarkusDevModeDeps(MavenDevModeLauncher.Builder builder, Applica .setCollectRequest( new CollectRequest() // it doesn't matter what the root artifact is, it's an alias - .setRootArtifact(new DefaultArtifact("io.quarkus", "quarkus-devmode-alias", + .setRootArtifact(new DefaultArtifact(IO_QUARKUS, "quarkus-devmode-alias", ArtifactCoords.TYPE_JAR, "1.0")) .setManagedDependencies(managed) .setDependencies(List.of( @@ -1305,9 +1303,9 @@ private void addQuarkusDevModeDeps(MavenDevModeLauncher.Builder builder, Applica //we only use the launcher for launching from the IDE, we need to exclude it final org.eclipse.aether.artifact.Artifact a = appDep.getArtifact(); if (!(a.getArtifactId().equals("quarkus-ide-launcher") - && a.getGroupId().equals("io.quarkus"))) { + && a.getGroupId().equals(IO_QUARKUS))) { if (a.getArtifactId().equals("quarkus-class-change-agent") - && a.getGroupId().equals("io.quarkus")) { + && a.getGroupId().equals(IO_QUARKUS)) { builder.jvmArgs("-javaagent:" + a.getFile().getAbsolutePath()); } else { builder.classpathEntry( @@ -1321,7 +1319,7 @@ private void addQuarkusDevModeDeps(MavenDevModeLauncher.Builder builder, Applica private void setKotlinSpecificFlags(MavenDevModeLauncher.Builder builder) { Plugin kotlinMavenPlugin = null; for (Plugin plugin : project.getBuildPlugins()) { - if (plugin.getKey().equals(KOTLIN_MAVEN_PLUGIN_GA)) { + if (plugin.getArtifactId().equals(KOTLIN_MAVEN_PLUGIN) && plugin.getGroupId().equals(ORG_JETBRAINS_KOTLIN)) { kotlinMavenPlugin = plugin; break; } @@ -1370,12 +1368,11 @@ protected void modifyDevModeContext(MavenDevModeLauncher.Builder builder) { private Optional findCompilerPluginConfiguration() { for (final Plugin plugin : project.getBuildPlugins()) { - if (!plugin.getKey().equals("org.apache.maven.plugins:maven-compiler-plugin")) { - continue; - } - final Xpp3Dom compilerPluginConfiguration = (Xpp3Dom) plugin.getConfiguration(); - if (compilerPluginConfiguration != null) { - return Optional.of(compilerPluginConfiguration); + if (plugin.getArtifactId().equals(MAVEN_COMPILER_PLUGIN) && plugin.getGroupId().equals(ORG_APACHE_MAVEN_PLUGINS)) { + final Xpp3Dom compilerPluginConfiguration = (Xpp3Dom) plugin.getConfiguration(); + if (compilerPluginConfiguration != null) { + return Optional.of(compilerPluginConfiguration); + } } } return Optional.empty(); From 3182f11d07ed4ba41460959a12fdcac55baa0901 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Wed, 19 Apr 2023 17:09:18 +0300 Subject: [PATCH 081/333] Update Gradle wrapper in integration-tests/gradle Signed-off-by: Konstantin Gribov --- integration-tests/gradle/build.gradle.kts | 5 + .../gradle/gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 62076 bytes .../gradle/wrapper/gradle-wrapper.properties | 1 - integration-tests/gradle/gradlew | 276 +++++++++++------- integration-tests/gradle/gradlew.bat | 15 +- integration-tests/gradle/settings.gradle.kts | 1 + 6 files changed, 183 insertions(+), 115 deletions(-) create mode 100644 integration-tests/gradle/build.gradle.kts create mode 100644 integration-tests/gradle/settings.gradle.kts diff --git a/integration-tests/gradle/build.gradle.kts b/integration-tests/gradle/build.gradle.kts new file mode 100644 index 00000000000000..6ffe25f29b6e04 --- /dev/null +++ b/integration-tests/gradle/build.gradle.kts @@ -0,0 +1,5 @@ +tasks.wrapper { + // not sure if it's still required: IntelliJ works fine with `-bin` distribution + // after indexing Gradle API jars + distributionType = Wrapper.DistributionType.ALL +} diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.jar b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 13895 zcmZ8|Wmp``)-~=Hdu)0n3Y-8OvyK$p9^s9MM|Aj$miotNhy-{udLczZyd9uWtD)X_{|!LhIEF9y8(e*Z zW>^w$u&x|i9OjL=#6Nl~*ERulzX>8C-}o;iSMRYdfCU5d`~U{V4>HCg0HG4Xg2uP;fn!>S9+>LbuWbc0bETMQfo9~h}yI*TSv;Oikl~t-+xqI-`P$Rj@yi{mr2zC~s1snMT3!OPBdJ%IDnPXq+pl*Z>=+?qo${lkCSKmwTlVjfb3thU6B8yFjr!tphOs*G6 zwL`RyVAUXj4p=9&@PpWK)m+REuvHaq838TEhY^7W+JAp$ zZ^y;8`Z*@VqJ{sFFj?<|7SKS@G`$Yi)gx%nOi@Lr zCv0IJlFz0bP(eDIW(uWNq?;8zEAb+uGgnkLk;y!4XhA6=Eoa<`+|;6mOq>z`%ir@z$4)Mkd3 zF=hFo zyd{*bRQ4YUe^bU*Y`__)Uhu5NIjVJ~a}{lHp-_7wI?#EB11XcqmdY>pk`JJ) zW9Rt!tK=N>fZ!UDomwMnb`0EOvTjcNl=yW@$c!OAg*=l()GjZwSyJ+o^;Zi#I5*uP z$6qeih8&g8E(pNSneK>93A(8*%gvwv!0V|SqGcj55Y7`=N*@pJx_ig3uVuf-G~LJbm`7nxNcZ>Jgqy(LTHu_C2e>STp^Pm{}f&^)XU}vzuU`UV&>e& zqsXNXSs;Wri|?NhCq0vXC5$>9Cag$adyWz^x@NCiy2${9Dc)Y;J8k1Z933W$3$H}g zCQFU1XwzGm_WUheXvnDisH_%BdzMgNwk2^mHcQu*x>U%iN*B^8U(eVz1~(%`kV1Vb z=9T0xmN?bQMyrrd?u}jer}zV&sCK6zSm!zV8A8dP6THF=4*V{_K*E*K<)I(Q^(eV!m!vu##-2g|G z{RB;{gJB_X-w{ANq?ft_n!@=O8_gj6FxW&zO$7L3@NjWt@R{NxMbpHLk6;=2$0P5P=kKc1_85inX z#s$&s0zhV1cz>nRb#|D#N8Z-=Tphm)sGH>9cz3K3I)6XpimJW0(6$GtLzN(YPu9%R zdFXG9|30AZME4r@joC0IdvBBe08mF@+5Dd97p$h=n|pi80Cn2n{ev!S$llPGLqHva zZ3*OmW%!Qj>C$F!Ffafl7#I_1(gz!aa)b{ebU*=yH%^kr=~N?|2&2Df2o9X=2B?U!#R#+Cj45=f@=EcQx+9J z=X3~A=zbX29Fqn23m3dm}0Voj^Q9BjI=MiG+NZ)YCYn@r^qv(xE3=)&i z=(ML301=rNTptvUt2tnsPb1~G*DWFWoZfv)wV|uNW%?!)jju`jN(K-0$JYi!ofNup z9K%_ucHwutbZsl~vDQ!Jtj8uI6WA6K--@?8+_=t>g|kgUeC=w`IP9m&*fuoO3#A;t z&3@=3;J0>yjM89?h5MG$S`wW+=vyYOWQGhIP`^vScM8^JL{mGan5uTJPvAg$0z}8; z zhMi+S${H#^wF;eU-0UHJDo$QwXDjm{ns>^ltubKXtd>6Bq-=ByF%bHu>2&e&uZj2X zgWIq(l^;Ab7#I@h%#j1AtBIkB`GO*y!i;1K+_SZ-p}4jmP7#%E-=>{ zK(3*ObyAgDLnbBLObTWJWNO7<60SK6*!dD~_7JOWTB*}(*X)ox0{lq5ac$ABkcL~0 z9qCHT8^`QIe_4-BW&mIe*&0VT6w|oJ9hnOO&oZUe!rP+gStQ)h5ZPhBprHZI;So+g5}&;adp<|7#r@DG!wXmtwdwy=7i>a`x1D4 z_N$0`Q)>zTVUU%@RzlG=4Nk1hE=_klWj|6aj`KJ@S`y^%bifkdX`s!A#|mpM-x;SF zg;bju5cA0?a}%hk=3AL^#2B>5X(TSne6PDWY5gRVvn6nKl;vg?SIbv^Uz=+4aPUft z-$}QR)+_U?eX*p)V0%#0@S46_6c($OJL^bPj0Ij}up8}In#GQa&Cp<#%ZPjx(^97{ z8AfEgrNRTg-l9WJrNJzHx1EkI<|n(P3VIwFlTvMxfe=V&NL)4MubdHqZF)&Eq4`+% z7z;>s(sjUsebUfFF;~)_%@3BDl8i085o$H!*yBv%Z27d~)|jfg4DhJ&nMb((B#4hOfeBhL)g+r)f%2be?s2ox zT3j0k+Va^9`gqO)FoUV@F|((*vGxN>?5IlvC!BzW-8cyCy_)Fl8W+eg<&Lz^s>dJx zkly@2Xzzi9Uf%|1pF_Nz-3SgOx*+ShK(x=XUlP?;EfoDqAkkwyR*yjIcD#7-@=|Um z{T+V}q`6)wnSO#*N#Hp8QT7^>6R+H^_o4LBc}$aD^@(1!+Y54YF3@A|Cupsfz@Wt8 z!KwmSb9}3l)u^Y+V6W6(bL3hk;XTY4FNy3hKhID#Ep#xLM88?`xT=lw3xsgN;gKK@ zqpElV*j#e;{w`OPYcb1_szKUtRLygjq2ldhGJ$8ksyH(hF%^w`&FH|zlDK`DfuZ_g zs}!{hMk^~48&b=jWqG2*^m8?ERreHIw8dgR`Ugj*t4Uo`^U*56MmU<^ zNxcuRh+Kc2>W~lzD8S6}Xho3s9f}{o4@tIc)G;lKXi(HJhZV{qSH1-xj>P2$NHEK2 z)TjOy%>(9Ot_zPO)^tp@AsSNd+`R?}_2Vd>=eT{G&TfITkeW@p{F+FTJf(n87##z& z!%w+6-!NJ*?9Z(hbZv^BG$Y1`BOo~*k7jaZ)9%@;H6F+W!Q%IV4qSM85; z0%xWZi_wc=CCc>2rd3Rk3C79_rJH1uG?yFIm4f6Fdmts<41T*;3ek&p z3(NaDK3iIDa)MaUD{_;~fMV6obrT6_K$c+eeRBJ7jd)c%0jldoJX`EWz8M$b1s|DS z)cr6)em!+P%GjM6uQb6CQ!FvUb%_>qbKn=gHl=@K-Z*6_VaD=;!?P9pr$Z?6NrB%a zb_G4M-UkkhI>H@+kP;eS4p->q_f+&(R^7hyRsS9Xl94vA^AYlM%tdNdHQz zFQu?Rau!C@&&Dn;i5iEhn3`y>{O-m^_*h+Jp6C?D+5yn9Vq5XVQoUe#BP3}lqvHa} z@x~UctaNE9PwnRg6+15NJ5k(PC0dETm#QxXY6&uTqupm)GVrsvKC9o)&*mLo9?$Ot z!SFjh+!mr{kYE5A#urFIBv?<(6-HtqfprK#3H4dylz5j`Uc)Hz@1}A9OXe=4gf3_- z$P|^SpeQ89xlL`pftC^4tO3N)JXTqmkbruGAsraU5Y$fyMd~L3r3t8-SfkX{n4<`@ zhBKAeBP_1Rd8q`<3^dio2W9^9iYW?#m-!IKDO7ge{vC%1Y>dWLslyLNrm-!*YU3Dy ze|qm9gwdCJKZlwcvaoV%S_%X-k_?QIf2zuAG&32WtJ6NDr0i+<{w;CG_St&I_7HtR zTiR;!)_1iw&#FKwAGFuBze6(_%DLu?>|K(H5bf{br_f5|#qa zNOuJQhSU1PGQ+dltC{ik3sA?PcKcDJg;_^-LCcLGo+|3VsWx0vMNOpKz3*U1wGG0{Z@O=3gt1Ay|67ZJC zGe%Q2bP}rYtE^Lc+ybPES@Snxwlh7Ydq$c{H?d&8e>!Dvt=dFxeS0fvt=u3$KHuU; zKHr9fCbGGQBeJ~@{wdgJi6Ah40fcT>yGRWEe)%=j!AaG~XDaHNdzsU6*ZJ2XC5>lv z=IT$K4yEi0xt7i<^=rn-$1nOKKRQZ$7df4uU#`?ddlH+Oo~+H_Zq!-}6VK;|?PGiI zhbt$ffNJ|--Bn6(L{pZ#!&ykjgBXEs%hmxg3vB~;GMKcAfeq~#2~f9vw7{>?pTu{T zcxLiHNCP}pJ_fYl3^gBy_}h~U`lx1^?)q|U1cti6s?Nt*RvSgF6WD8U%3uk zwC7lEPg``Bjt5YXNFE!^nq zJC-z}n^zNvd{jVhiv9aKNd}lH0$n97EBjb`Fh+7~amqAtrK{@Sn3QZO3BBiUIo^n$ zsiS{+L+8B0e&`mFnEqM!LCLnzlclx?UwZ(L6!FZ$b53#xA2caP^zn&!GVtipn{W`U zvN9yG-?@6)3`HYt>E;wO*N_UGd``TDMJ+e<*WUe$SGeaBU)dJHbvUp$J?}caKfP>U znZQtJY@$~+#6FOn9R6m86Sq3iiaaWa3kiz1k>ntIk2*6R+6gchFxKLcBi9EMRVQrl zP~vO=WAFX7o6BB76*mwH?R^-5HX?KAu`a^Eplkmc zSXpmBvQ4t(kVfyQIR#|Wi7PYcy+x;(5j|LOp3()IiR>2j9**}<*nO2NiED?Z;)iGh&PH4nB*kN{VVt!lYX*(jAlnZkabB{Fa7)iF?pBFk(T+)xyg(Y5TUd;DX&MX&_}`_=Z_KcQ9;Ok=&YEqPyVul9sRG%P!*byO8nRS# zGwOm?IyLaeqMf=7AGF{L7v%GKmeM+;#U;vPs0=0R1WAo2JIq8N`PGDe}Q zt6VP!Fqln^U#5ZJFp?b?d*Q}Ynd3Q)jTU;{RwiqDncXA=DXTWhkWhiR{XF9aobJH{ zEYYt-`Hdwp@ZQ5$_i&f`=DA1D>lgJ>_PkLE6#)L#3R1Giq@XA zCLtGAgOI35<3Y-&55pCx#&@_R?w|x@%3$Q-X|@=Zhuo`C@cOG0@M*&sW@uXQJz-M; z=ZcUIw+bXwCV+k?WF;Ugyrm6gy8KjZmaobl;Omt^`!m*(!@&}j)uCT=+}RbLo7WiC zM*7VJG5hnkugII&>R-Jyx<}$pNBtEizA`Gn{GbTy^WPi*o!^5_gH8ME&+{<}nBbSA*p<6A z{c--0SNgk{iH@g2s&K3L#wl5fR-H5$YrMAEA$gwfPC&GdtAb=bUk$?Md6^mdF&^vj z+iAp=tz8ZK>*?)QgEVBG?CnAb`($wf9*1w->8@)hg(hpH^%IFjGqTs7<*jz0J-*C! zs)=j2cA@=KgS0+*LX^Qe*))69yFm;(i`r6`?_p2Dfi!AQh43;ix#Kv8_*W|IsGg;f zJ=0%L||IPz~u^1P?ZkuO7VD7>GEfT=K*2JP!?hLF1f0rSkXpoIojW`}iLv zt$qt5Kd$Ty5UwS~N|w!IW4-TDG6g9!ecEoE+JUM(=T{d4yASY8>tlDG_XdEUinvXN zl>XB_*;iM^53IG90-1uxg#z{ov9M-y`(|4~g#J?dVQ&7tJ+a=N9npjr(_lb@G$v24 zPeA4UfgSFXLSe$Ghn!^hh)2|+YuV|~a}U+Y9iy?b*TKn*`y{ADmlq%d|HzJn0mW<0 z5McIquX})(09`s?@%4OLy)I^TdiKP=%}XfT`s{oX5eauP0FS#ZH3$bT&E#E)1%_v48Kc&JbnK@KR+fCJ+WWg`;cXecj9ij8zP$MV%S9InmL z#D$p6%KIKx&U;|#5fPg~KlH~fC7Sh-(Ut}5+tSSriumK>DDF&sl2pa_A|~tu_*8aY z(*Ud4=(+k5;ke&7V(y`$@j|FGqk0(WA5Wc(N${j@=7U}Xs^XNgK(<|>qug3-b1T3( z0=#Hgj}+TLlDhVm<>&!j$jvWXm6SLkMW&2k+;_u9Tq#<8uKtToJ3Q^==VQ0eV{+r6 zQn5p9xfHk@%P_FbqYM3DFnxUSXF^sk#Ms{)T4quYP`fK;T+Tj&gRl6sm|74UbHHrF z7h!QzEST^cpRO6L8_~zXNp!niGl&79$k_8RSj0W{xMrR)D4`>~tNrK~*s0gkO-PC^ zu^*~aOBQF>qG>`%KGd+7W{nGqd5lc0%E_*&rn?MObfYvgPvJ%vawv{il#Km=$-hF* z1V^<{OA_t~X|u>{5ljynGhf844dJ#q31&xuibhPgP;6z{C2qw67U617_1*$=(_{mu z@T$|cK0GIz9sS4`1VcT=#Rqfsfiwbly-A61ih$VWK@T{K(t%VCA4=VJ4(eT` zLP`DnbAKO!X02C>qoh6kk2SEE|nQ8^J~0S)XyHMI1`BA+8Q-{{y-|Sc=j6N9xVnV z3^giq-U}tR!`_$ty{geQQ}xVo!CwzlXx}-}k2&VU3u7n@(1G0xP$36j1GKVJtLydS zm|^pz&9wE!Q>OWGMLY+Y?=$lIM$IKdF`8Pw)uhzhmFGtIyWl(qh0C@9BbzwDR>rEa z2gc62w3u1cW+De8tCw(3SQ8EK+t9l|ef|)GLRlRJz>SleVh^o zSq>XS(iJr>IQL-5^9LMn-MBxnO*FN{K2{7JVUpW5nZ{sz&_Z(dXDW?G7lmn%1nU|B zqC_R`=83Y=g^uel37AnfplTx)W_%O1pY@^^#~MgJg`0^G07b7RHOA>7K6Vzom_M3= zbD)3(BXXoqR6UFGHM9a3uK)SxX-0%jvKG23)#s6{vbq>#o$1tZMI5hU1c`fGME7#Ij+u%*rdsnO7yaltUc zz)OZMW*a=_Q|k2CFQ+lR%Md1Kd~``A8LX7vMtOupY7HV^E*;7o5$|Yq;EZjl%s-BLWa)nM| zOY1bfH5&%ed5t0h_`z*>GNiXhoMBw9+W7 z4U!O;)Tz3n;x64wHcYoivoslIkj9IN05|H7X~GWEx-k619Z-KjWv%8@$1wbIvAFfI z0=AQoH{3yl1z|`pSg$(!>x0)nU|wT@4i`lCchm_nrU@Y;XR$D^5wA!Ftl}*9OwXFZ zai&Zh_YNnlz=LEccY_eUXOEY1;q&Pd;dLtf$RffP4%P#4ZyIjV&0;_13^ zIVGMUzx+5jLyq55_Qz0jPBx~-{DfuUW)hKduk1gv0et-e(ZN8;IIdhtV$3N9Bg((Q zw5eHG)FFs=ewUwfdHfvHb$&&i=h{#epIdWr+=YE9)%453DlIOHLFX;%dv2LDNMrMZ zEWU|CvEYY*(2SE$Y{jAd$QU-wd*Hbe5yO+Lu6Ux|(Y>L}E_jNPR+TX@Ch(#orbP8g zv+Z(oKz1gylHHGKB*FbdpSh7VBM2KVmx2oj>?q8|s72`}5s)jT=s4;lbRw$cKh+N{ zVTxW`s~QW~rRB;e|7pxFoJ_Vm^eVjcddUh0Xp(NhCBZ@Uya;(x_wkvyH*^ds{2_H? zs*PV?33(>MyJC_<)JC=|9II5@I`QnNGgZr z5AfQVuy5}nzXlGQGV~eESn9UcL_U$gw(QjDVEW4b-o=BQGBT*a$1Fk+4bm2n^6m6w z_hn7X46IDL7iQZ8s+_(8yX!fXqM9htq_Ts}08b%snTZMmP}{6(anfizqhpR1cR61k z=sfzRN*!0HP{Z76PDg%PUY)rjwhuy71^5D3f^bR;(fQe>3U#zrWwe0OSYjHZ-eSJV zuKnE7`~*u%-HShx%*b9ZPU~(Rg=`lQI$;iBY#2k^6{Ef6e9D&EK^irorXEpE!h=>^ zVxH#pyrndMgk)Ff-ke*RFsPY@B3AM_;Kj`PIJU@EH^QsIUo1wdl_wfqd48O^9?06@ zt*>img{+gG%WiGU+&V)`jeJUPSDDLhd#nVrUr~dURh(&O#gMnA0dEg-#?fg0Wnp#P z;4QjL{Fv?Unq!!)POdN%ZI&vU*Ww};bqd3@5fb_<7mIa_w@U?X&ed5f1FCQ@57aR@ z)TUphLPht{?j%;+T}Sfla?uiG26R^?7=x!#CUXw+$_TQx_%vLhgg8LVJz@{QVxH;M zGcV^6&Z%`yWalhb>$VS`{^Ex`w@cldtZ8t!!exC zu+Msuk)M-ylAjAz8{yA&TjgR`O%H1H0T&$<*+K{2-<~=1E0~C+w@CzUg>GyIegmx$ z$vp-I6CygcS8Jm9rR{Wt@W?<)IdIk##3DUE741Dg@lQ~Lskm-7=|2%)&XCF_8|780 z9d-AgO*4e1uf}M3*FGo&%&eG;OB^Vm_x8i73V3P?d^qdJMvO&{H(jgc?n6UYZ>-FU zeO%|qJ%xvB;o+$e+CHm+Ot1UgzOrX7_G!pZrt%?TaOs9ZPg>i>-gg^Vuu6p>LEd99 zGlCZbE5(oNfEP{~x>KfOZv6XWA8zfk0@R+{;r7WV?(wWFRaGkg&mR3j$wJa7CBWz= znwfnWiE^@dC=n6jrAY4vvH*;b5{E#wK8AoUW`vT3W+8gyt9<*hPl1ID>F3bkLniI?`*u@J2zcd_cAH2?L5O|qzu1jQs$J^g9=beD zYoEgyA^AIv!P%D3;3T_C#zm7j6=+ACjtf5->)lXATb2p>g%qD7L1EbTMh(z$4oMY) zSZft;+pfN?a7x#%4}(P3Q)Gvt1F^8eu9}_PDW&}_2hhqjF#&SGUnz^`=V(U{;B;`G zt7FmRinElmq%KVXaBZL$+hD> zLe`*wO^B_i5W9q8#>l8J4;5{XbZg#@Z9|D|{gN8}jF1XBNzpi*9R3+-F)w8EbJ~In zEdim4jC?)`IzcZ1_`5oBWd#yPJNc%ajkte>^q1KY$#LzK)`jz_7$%1`N1_tdhr^wG zp92GvW>iDG)!1`I3*Y3;C)Jz7**nV;DaO_d19A_8qX%OCf-KY-GEZ#Nv;2CZQ*ht5 zY`vXc7yAb|?h#Z_dEKDC)Wp}g7hJDlI>P+ctKoq`U4!4az+ECGUSGmfHRpW&m_%7? z(o7gajY+w(Le-L(_Al|yQIvl1gk&lX-5BMZn=+~n-N}$`J#2x5x&B1EG{drVp+i;- zucW)%=6bqw%wNB|=k!-_k($v{gQB1ZX`dn0tu@(Z7b0$g5k88nHYIEE zT{wBh?|8X1yS1ITl!hS_>>{cobd%i3<#)=amBnHn>p;m6f%!T!BSP{_9DL_Wmv{PtyL9hoTep$i_uAr>^@7u^a($-HJh2k0xNsYVmt|v+kCWusAE%8~f zgZeq1{C!DL z7|_)gsX-J$DBwOYs|TpK6>I&l2*#dm_B%7y(JCJ?jaOVZJg!;eleEd~bT^pJkrk>q zB4)r!XRL!mow*tX6z6JA){(LgKapsISwxE@P|Hy&;*5I17ktf2EQSu$>0G&bDc^|D zoB?VpoqIQzg72DO!zOL#jXEsFWVZoyX*Q+>cyNC5+bi$(-R z2PXnAH)~j-X7q#KV*r7K0Tj#Pt=_Ix!xQizqfxG}vfg*swPul)E%ElLW)2B0BOb4U z$5{w|1BT44k;f7uS&T@0UH_mBvgr?Q_m;tun8!5sqbDu3_a@H76e`xzggnje$~Vo7 za$jN9vO%&+?c(NFBWd(HH(c*Tf3txzhrnp4X1859WXnbk!aVPy#xl`hJYOb;9$6q{ zkbx6NHJ;r$;+CoL5@BT|)P$#Nd4mLhJ?! z#V8L2#1$FDnc_k5#=YeMy9&SHkG_wJOT1g%-w$u1eta|QD44f{Y&WqiWW218tS?qy z$ZDkAwNCgrzLY?-u2WO8%SB`AO_vLdwg{s)2>YT(Vp}$u)h6yDPl(o)wFGQ6GTv9!92`>rC_Xgn9)BKfMk>B0lFK$_ux zk^my^G@g^?|Ds?LnEwzyJ7qzahke+uzE$SE-IhBwTL zCnKg33>Lk_tsV;Q?3Nd07IG)>PA43Q@@bD_XViZuJnF+-SR9eSm-b^YbLCU7PG6GQ zJKkO|*b;^O^%Ehg6e-0+bze&Un{k(1?Aom@b7Sm z?b{}WJ!Zfj23oRMKPiLEh^qy6lZ(sff1?M#aP;~C;P0@AuUam$iHH$i(Zc-_8++)) zGiB*fRHaTE_*K_lAl+<$IklN{WiruTjZ?Ir>rocinb-6%~rZb)Z@l>WsZ%cVnF`u(k z3MC-R0(^u8vlUE{9TX~VYef_B+y~v-T`n!_ zJXHL4N_pJy{bQGCGEJ2vO`^5M=(MU>=QoaiN4n$ZmlEhRRC09~b|CV#QExkR{!cxv z-Ih(Yq);JB({7Iv5SqD14A&CD>{9d#mQfp_-1nX*824hiHi&jI!rbzk3^mafyBi2I zXwJzh@J~^n^Qq+Rev`}V%T)Pds`2QDUxGv4pkJOaJP+l=87o}7L-RV1V*p70%Q?kQJ!b+v(*=vXQsHF z#w&NkJNb4_Kvu6hrx0e1Q_pLru87EM%Rez`mTlk~vCAr;IKZqQ$#>gK{ZQNJ$F@r9 z17m<_yD6oKG?O@e`O;WsIhdWwE)Z7*SyABxHvKJ!x|y(wVq*Eg`D2Q%Q#&zSm8c_X zY`zJhB88q%6!2%9%}+RQMhWH=sbw#8{a(embAwu zeRHhkOtBY=U&ubKu7vS#2DPzJ+WbaUn%Eu`p1cjDEU*&qFGKE(o%RZ13w1x?o_-#{ zj3y3uOaJI8nlJ`Rt11>dUer4~gzlg1qwk_n+`w_Q&I230F}#e<84l6$Ub}ga5BLCy z$uT-aXsHnb5x(Q2(qiSxMHMrLS5E#p#t6L)COeA@Vy#t82W3I7zxNN*jGG$^^A3V~ zTr=^dD(liTi!S&uFU(~grGKHPJ3#7Wm91!jh!*X-6-6}Q?cA`2ld(6Q{A_nw+16`p zBq**{Pk_!LEyI8)FurdbBN-IqyhFR52Y9f)rE-#p}V=M?A%c$M#J3kjR;+GEA#vBv7ig$61YKjN2FsuXxl6YE;g-oLfc3d7ixb z(~0wjUXzRlz7@}MhgnS+FRey=b`F|l<3w;qodOa{(-yU^k{7Owq0>0sq7~my3O9?# z;MqUiGm}Q%_f`tMUWXlWG>uF0_?>-d_6ru!DNoiMD&X~fg!7a0H9Z%=3kwQs-Q1{g zxIsDbEXG9ly4o5M4LODy_vvf8k1Dey9QW4T^up55&l zkpg05cG;FhOyo7R#xy!3{&xPzXTpzSZpRkB&$uR(?99to5LDHD?ak+~^R*OGg2wFv zUjX`1J0_eHXV^8UJXLSFxSNPlDSRKCJ@A^Jrtp08!98KQXBT1L%avWTv-8l?va+Jq zHqd)|JwByFcmK%afGyJ=rb@ELtB7tehaH#)iRz5v6?C;mDxZj)`upc|y>)S)VveGb zj?RG?$-D;ms{Mi9UTajprUthRTIksl=OfjZ8iD{zhh{YOLQV$~PKQE~HHn!A-`+on zR*Vi4Qpbff5whUZ9dr@0UMy^6)_zH48Tiz-RM+T2vk9}rr*_Wy-CfoxGjcedo-{zF zI=^!G@*UT_@;VTiU+I>Ht{NTo^Dj&T`?{QK>&9s}PXt=TxQbmKUDW->h6Eh)@|}uY zfxqy8(^9cw%+k#m9NNz`x+UB*DrrBVuFm%-eo5kp!74OI^qtOcOgmD z8KADRYxrHr>DeRsuJG&}MumPmOimcRYf)HcNZ@n+9Z>VwI;H|{kuzD-~H{S8;hQ?c2 zjtv0GZ}PmMOMCz*ca!f8t!=)0eIWsWjJ71-P|23{TZz8yg7Kf_uYY%rfKs-#-mI6~ zWDtv=K%3NLAnu*Falh$e$sp$0L0w!lpwgZ9QTM+QD_m~`Hwd`>zEy>8mki>B7c|Ao z1M1j$C*t3TL;k-)g!W*N|5no|$$~>*LSlkyga9DKJp_ntp?@6S+sqXOyh(8W{uKnw zfCBb--`KW2G6-skzsABWLHJMO%+dg)|G1h+znMw@zb^du$snNhKu5aNu>aTVhA9Aa zypI5ZZuUl#f&d5a@?81@G6)V!kn(}ZTjkqZ1;HA0Zp8~i*?9jK@7DzF5Cwb{M0EJJ zdFQYCg$>j{ouh%B3M1Qs3=ZGV(U(Iq2#NQ~M^NV>2IYUw?*FKE|8LZ9$ASPj2hfxc z)|-fz^uOHyRf8gcfie7#JF3$^?wBCp5zhlK2f^T{`>T=fi_P#-dNmI zGKjp)zxq`<#rm&d{*P?xe});I^_TmbiV9SEit=9}|1ST-{Qv(9yx`vu!D0;he=gX+ z0@?prp8cP``iuSvME>_G8=t*R-p;@1^t1OXT=hnT^!!D1c2WH6hj~s0Vcqu+jSSK~ ze?K{$!~Z?8YDWJup9~X#I?msx!{h`2w0@2N(KYpMNVp(=<47*ZAV}x_uET;%E(l>n J*WbtZ{{Z#P!zlm& delta 13442 zcmY*=Wk4Lu)-CStgS)!~5AMM=xVr|o3>Mr6cNpB=LvVsyAXspBcgQ1o=R5aaest|x zYp|Ud;3g1aLn46!*8mAJI&Z-nf(`=#0paw?iVYg# zKUs^o|DOcGK$5&gPV0aMK}b!cw=e}1HdMgiC8Pg8*>1^32Z5FfsER!G3mZ%qKjJOpfesiQ2!1wa9roW6I&DK_t$shg|m=c2cE{QdM|NtSH0rXoXzvmNP+5U2LV{^QbB?sv0VKm95!eQeL4~+?=ho^^MZI zi4QY0fsKBbqrOh39Z!#mM!z2}i6F-BHKbV_Q&qzRsaF`l1Vjpm1sC-ZseEjRhHlco zfXoyCv0NC5K}!1s)zB(Gd8sKQIBYyB)bFK(2G2GM&K4S`>_HR&4tr1?iRab0FsEbp z*Jv*zm^-fRK+ctLcyDjn-afw<1S1jM(4q5ykfHQzL_}qIFL}{AIQ>4(4ufTO5LOPw z_jW{#M|)nyUycekv0yq3ALu*Gjx4MO>bHe*!#3>nE^vCCDgcN>sA^k$Zux742g7MRGS5YWh9J!2T zS<0JF@`%w;58G&U(_V6*RvcGc?)SP#I!b=^l;;8|2L56hb1X6;bd2imS_1e~0c%T; z1T8HGf8HR3ELFmM^n?Su6+Q7D+$t^=tIK-pWi`W;i!lHwI+jG7m{1RRjBU0~dzp zhN*kX9bAON4=>l-DWvYo*J$Q4Xp~|yYTaabShU@ns@lubZE3xU%6MYv&e|3AuK8?k zu?#J5JQ%%TJ7Bb$Gs;&*)*UAk%Oo-5q=+2(Jm zIuppiu)ZJ9p`Q{Ox6P5{rbDkZk#-Qv`%KHjq9XiNOUl8kb7aZj*E~>vv^dbHH4oOd zczWr1LJT!^o_(O*2>j}6lOtE3Z)Pht?L5pyzPpntJ|r!%j z5uggS6oZWkpVt^698p3fEKA&|+deWq)ldqZGKG?a|~=1V2xdW$8-mayFlC zJWmagu;BBJC#|ZHrUXfE&`4P20AGgWC5=H0HjYm~^E~OwgAnMps?;#CY=ahb7%?H$ ziejQ`%0Proz9+myGwpEQf^)-=KkUK?uyDVM9dcP_xwRPl?asXN_w$2*H zua=Dr(GFqiFLl870&u+1P>>n@QI(3gk(rj0%e8Ar$G7fdFyGel0{sZrPuEX12l`k< z5>lA+*xaiLY{Vo_72dq>E!s&D_ z0I)&YzOCXkxi;^DvcHbfU{x!;>3?+f!px_0&rPIW~iPmIG@n7rmiC;XiLC?f3vTJUz`Gg=p9 zK8)mv-V6dl|9;(R_$VaJ&lBtE0aw!=g-iJ(;|-J>nsF(42in0{Gp)Wy}WNr3llis^vYk0y2t{zC9G7SQW8GEvz>ZPi09E9wH*yE=+9`RdARy$??) z&b{^h_aIn=A*FNBQ7ATjvh&tjsQ~1FV3r;lW1~f8kh24Aagu#Jxb89ZAs>t(Qw(FD zS|S=1m#oMS;Dwi>0@KkG0*-OHaJb4?~;#3j^WrKgCx}3YozM}uF#0{&QFMled>Mo$+hUe%lY}nvK|5GwA1fTy@ z(^KJxKj6OT*`H=XLgP=vBF+Dn0wO;EGz7>+V7(zo`X~r*4Zb>n+<&CFW^ zx;O-Yo^0{nqPJTC5S<;>8>L{^1C9Ql@|#RETigaBa*_pJOL-@W8p+w%^}Gv*)l3j& zWma|3USri z5Z(cKy3rMvzZlR?nR7E6wO%( zDf&3(AqN7_lQ~96t?KD<`i5K_pH$aIxYeiWm}ICd!1&&$NJHxywzKXt0v0W~ZuFwG z5rq7KRa$-&A|tYU(+b&T6VxMx2Qmg$O$VM!XY^ciTE+)P^vMMLl^U-ySP1P83$*2u zNcQ@)+ok4pN7x{9Z?XBZPr*Vr7wr91_FvBH=xc%RZ4TH$W+0R#VWB0Ua`8O;-2Pnqo5QG!{#(=RmvtM({fuA>4ai&IW$2`P<|D!v-qs^RSsZ z2+y{qc6(Io-Ywwf<$c?(7ay7Q&wZ)JAdk<#iTYCy`PaXy(4aeKd-6d}u}-UT9jad< zPB+QbuZWqQGTG)@?W;;TDUqxD9Q+ao``pz(B`&cPTFR3|P6fz8&WRjU<4 zKLyJI>Cm{uI!saN=y6~Pp0Yiw`YLo6*z$^aOS8b)G@I&C3g&BsS$8cSG8QK(iy>kZ`195!*f-ndgPIM}p9?J=GYwFDqRYmdSymmgW9=>uiSN z{#DAsx#ke6UQ;6!o#~HR_BN1VnmUn=c$;LY0ajlu+#0J~E8a8UlvxiJ7^)K-FrJE% z<2gebNA1Z==jc$B(7~TXXM6&Q)3pToSPkWWSOl$HC)oA zgNe5(5xkR+BQco*Qiy6ns0vv|LP>(bx@_3vrzwIU;zwexl)cvpL>(yu=LHEOokp5L zRA9~H_ysBBuJrkjur_&)92IMj*o{ClU=^%$`6*Q~>ISJTt7*aljn)-ljW+BK3w>s| zLN#{_x{$hhj7jvX2)Uy)P$0MUVAnPRgU&7jijQ%_?AODC$j+(yrkEJnuiw`IZ7!R2 zPB4GAo_x+e`MWBlrj}-+i-p zjlo(;u36|+c@du3o(ChHTb!CNG1uvA!k!ACwEt{gFz)!#yl79^=yNgIS(ucgbSZVj zR+{Nqx!hUAVk>-}*j$=WTI$Wgh61lQum5C;c&WKWY;gwydc@?bv+*)FqXm13fAnj~ z7*E%gV-~u|mTx|mAw-ZO`Bi*+jS3ZWr4V0~ zh0jG$(j(1RVT&D>u$wVNqIc}P&MlcPYg z_5|^fraxyhG$cMGT+&0SEe)_*oGW>KQZ~0~Rq(Ly?T1~r;_P(>cUwlKd0k}|K>BjD zPqf(ox&pVUNt_0FAu<5Ry?hfTydm-bPTF3CYZH!1pu(4}QAR&!8!uXdc*_CBC>{%1 zA#ZnKhO=T2`m_g!lt@+#fsRc8DFky1Glal5Y`)UPr+ffyzIo=U{^j>S8)Iva%|F%A zGycyWb;bAUPc@wa68+gwA19vu!9Z~EZ_QRl-&-LDp`8Ih-Pu$4|EZ)baFvDzZ+qHA zEC>in&_*!{DEABjn62&YhoepMyX%-^)Evr&KA*^%h@n}5{G)gq78)|*fHeX)qcQ9U*FEo?pAZ2&Lq&Gb-n;6#E_Xu)r30J;4{Oxf#|W(TISTm37EaLAz)5( zb1#?ZZ;q%NG(z8!JPil?M!oqa`W!eDy}m>{b|!``@2#VCMt(D7+2Uyh$(<&;@EQ{J z9;IF1P;>@bd{rIHJhxo+R-ifU(Mvyf==AfYG4+z6+4Q1Ar=nOHUA`Ok!e3Kj@w~@yTV|fh zG~45!>b!@cwCpXeD#8WQ?o1;`s8Gotuz$`fbvPoAP1e|d71`QPX&ZV+oBm-u;`HE@ zym&N?*)l!sMsiRqUCH=ki3ME&qFxMUJEEzrkRkAmSMOkwUCrLg(Ig%_Sr!ztKfZ&I&V|;hkBz1&x)60kft|N;0kXv~YbhB+EPM4N&!QS#}gP3tLBgQpm6pCr<>GQPu|KzFkk@ zOl|mn?>(D2)rZDbhsv1rnmK?{HP{lsAt^U^B+7vBxyOSavbz-KuGLmVO-nU=o z6S)#sswKHb>egmHw;{EM^SRV1M`pAk%gw4o7vPVDDKws)dfEG=5Opk4ayvRjWd%MK zXYcoEj?$jD=(Zg5!X+}wY2~0gxnC&q#zc-9wV0VW_PZP2tztcR_L@_n9AKCBu2fRHnbjeyv<*yJx~og`}k@A0HvO@R|K|$hBMLQ=WrVx>{$Ar3jVpsHmuC z$t3qeB>3$4EYSl>!zj&+H1r&FyDogkkYpysdb~}}mQ$u9=gVLTQ=Ns$4fWH&Gy=E_ z%CR%}(Hu1zm@)A~It;A3Re$W4q#uP;pyBCK6ta|7RTit)0mWh==&(r2UnTNDxk6om zmC>MJQS((G-uhP&ZPN^6Ry(Rrvz$XAhg$K8((*`87J)?Ujsv1THp9U~zMz*LJ2W|s(*ZTJ+2yv_eH*%dgVNuT(K!EpdvA^glL-!ujzY3Y z`KD{RAk{+dBc8b1NkgVVuh7c{#ta>ikwf9R&>BXBG@;6@!IJ8s!{^!TOSnoiXhJKq z?$^tc4t>w-N4X8((semr5<}q8VoD}!Pl|ZIk^JZ=leGyf(d(I2BU2>tl34u@7+jql z4N!&y&O_{Zbr!2bT8oPEH#c3eTM8Y6ab=2t-SM_`QpwW~PL!U-RtbW$9TA_Y9`}KQ zIm#;}*G*)&@z!0tS3P?A^WhYQLr zSy4ZZ5rI9~P9E!9?O~2mtyH;!ESE4k4@kzyhIRzCqRn~`#JT5k1Y*8$8zo4k?H~CF z=kwf&U*-m^wM5Lnx-bI|b%lcR0g5_8HsTc`$CD9QTdkZjx~{mG+?Fmpm=>yMB=5rp z!d|Ru`@?G2Kpu)ttD7#&4(`giOjCpi@DuC0ftdE2HAgVQY!X#HSTvYwSZIlvIXwJQ z8|!>2H#uIGlyv;@QWAKhAIV;3HzHTWzLYdyz@Rn3$xF(}6y`f2O2*-W=5m1`Ts3JXDuiYr z6d`uOh7w_AtN~-(cK;qFotu@Cr2}!C4)Mmfbmo~F$bUPd9bZU7p8bTd6>_dmBH53< z4^|H}aUq*qgxnNnJ?$CS$bK(GbLfnWmY8&GM)SB4&z#XOi3IpYi84+{|@ngymx$~Rj(n;X6$p3B%0|6q}h`vw| z5P-LTue1EUBRM<61|}yNC}WG^gs$1N7_|QquUfm;ERxkj(nHF?7$A@fr^X(L0Yd+JlyIbivAQ_WnVN+;*y|^d-o0gj@Sj0@Ll9H0=1@hE$Hta zR2PzZH0j!kKBea;ePh?Jrz9Ko7nOq28iGI}i($3?7&Jc!m;GLB*io;%#<2JUVUyNS z!x!dd5#uN<(@nza%(Q+QY+5y16l%qlK@t)s6jyvV^GzU}5{h^k#n=pC00#k<0GqHun4N7jH*p5NKxwY-`-poyrq98zAIn(Pqelhp@wBZS z;VPUpIZzh2>BSRb$Z?b~p?EPDjb#@KnB}){l5^=Naz&X^lrUaq`pipVbPx&kM1xpN z6F(xQqnZQL23bVMsk6$`?ca%u_*|N#<8zPrmThWVf6KSa&6A2d5O?dgv*@;Cgjp*B zq9km)rsQ-BmlK{>#^X~h*KOtJG(cw&oGPG2kQwhrr;VYA)J|^_Tgrrk@v%jYPrQtt zNfNI58EA5j9B%W{vgy!n`D;ueZJM60hba*peuxnK?;^EQuvlBbfq($AfL4p?fFBY4 zH0I_+=o&hQ&ljK|L&sGS&1sHDVe%tu)bbFl9j zT><}db*{&yjtx=~fNtE&hISi_2$bbgHKcne3!$?U8jyO9f`8uLE93M`HT*Vz6ZRT1~`1F?D!-$WNc;<&((Ib08Ag&yg|t zgjctZts}}?Z4*NkMIsVgJ|ZmJJcPXWHXI8k&Q;t;h5YLKm8n%R?^nsGhnP=8*y={8CBq{b z{Z1z2l0k`Rey6&pI09&?tw5cO;>4>RN@eM;5S9L+n!_|Sv1%ql{6v*EAj?yZ53f0e zGuz;q!pFarb_lP-92?X@yK2iBQ;9w_7OK&>_`#l?oq;sGg&;vunv(hKK&)jBGjxwu z@Kdut>cI;O;%x00?ndE2=bbq|pIxuF6kh^vxsjCt#~RjYlIH>zABUiYp4!%AA4{6OoRsk@aiB5-scca{ zgAc*xCz9H^EL)%*w$84D!Nm3-fZNkzve)G0*kYJ`?d zIpjut2dLm)=AZ34RwGb!v*GfMJf3||p%&~r!JRCSvmq2}EZT|TU?LW<#WEpSedEKH z9rtUHv@iE7LQ_c-f8H1-Znqi5p#pMe90Z!{VAf*dI)stltyRxJvofFk(yti0 zx|9WUkxLZkVJ0Wam1udF5}C2ce5Qug{)O+Ie*AF8Rv1#EQjKet91DYB#y(b#(fqxD z=vSK6#ca?)n&qt?EibeHleq-0r6&V>JLM+Sw|sprhxy8nA5LOrEOzx@et+=rHfShJ zXBp4>%&;4QGXd`*jU>amD8M9P-G!n1X*1*#@TeB03U;X2eat>Nze&YfGYg@L?*?Yu(P`DMIR42wH#Yo+>sAW0hA$p6f!s92m}jI%+zHV@~WpCT;m8=%^DqO zW|QW@yFWsIEu5wBkt~^=L1}fQ&MWCTUWZ%^n+FxEYE&eo_{k&hvMGy1Ca`awgh#=pynJdeU{rREf6`K z((@f%xEN&nCFyJP#M;K$;j{2-z>T|#ZvC_xM`?+X1vDf{lyKwxeBPPRdLkF-l{ z&(J5~U}ZMBvu8z(iVsZBPqjeE3+mAUt{@d`Hbpx#TlcruF$Zq(v+_Gz*1q%Cg0J$b zMWqv)I_|9_JwTh7s6NVxU@S6fZ5rP*(b;?P6W#M|Q{E%HF!*3aq8ZM8My=ByJRL_H zIB|FJLP+-G0rGRa%}pH--cJA`MaG=)el2nma18yxjp$ePRo^pqHhNFtN}b#Yu-G|j zWV6RBb9UZ16LPOPM<0hNk_U1n)~-O>v$k)+5iV1a3$HQSx&#Nahs319%u@A(zX5fD zSVdp$R9X)pb`6ayC_94ho$fEO{b`m?`*5v73IQ%*^kBH6Af!-`iXg>&@Ti`J!j!CN zqZ=tqJ5I;-t+5^@=@Nk)boU~N=edVvmmizr$_7cy*AqEy`naa4JCM)h0g`Batz z0j|PMD9#>RO=h(8sRzt1$QxCWuK5yEEk0YzBLc*B8CA_|tF=SP-u)Du$}6+$f{C~* zYylAlW#yhgHyzX7HR9N!Egb}*7{*O&+yw|Xt1d<%7LsW`dD@@74_EH5Kn7D(jhyKR ztLMrI5&Z5r*J_k>D73H^;gT!1`&99L?U`qv0JX&t)xEWFsTEV@i260l6x2!x_s>cx ziZADsDqDN*uO#2{u1torx59SQ8WH8~Hp^ryB8iiR!+Snt6CWS5B?UWNNYc|k>`BD{ zYp%%pIdp~ixk4jVw^H3+fmGirFLK>JfB9W`WprPYwrcV-Rp8qQaQ1=cGYL(V8K7uZ z?>ThBDUxb!^P3g3P@%`n16g9n@3O0J_ZHc|Sx$3=765keIKkMTW?fE`?l(j>Q(D}8 zQeP{s1fLD^F80G9W}~+%!&E+771NZeI!*9j#63ozC6Cq{T4Y>PkO61fyoOnrTT}-v zSoG#e@#Eu}MUm9d2MyH=&hpcJ%DzrGwM2r8sOqYyKfE#eabL&ktLQo`!@2;cd(xWh zT21{``ca`~=^|5c0}5Ee+#QZCT2T+zi`WXMPq1hKjYA9vn+#WnXU(^~L0GU&@Ke$; zuTt~8$=y3*MW{$X4^_dI9c3Z@s!?)NF4{|P7ITA@HNmcI8oHsVU7EylK>KEm78ma) zzv=g=vvQ9L2@^f9$dhf5kDAN))XgGt=_S~1uW`j{fa{a>hB?roaklqoO^aeS$|15X zLS2;v%Q5}uW{+H!rYDB1Wv=w3f7W!H_)^wjm%UP9D}{n?@+r64IwvOlE1ZG(sx8 zxP0lDg_&q3k5(_$>3AH4sMfaF!*3Qd9t0-HH}GiCxS9Ovett?pgkD5~Jr9ZE_b~^# z@@px>rOE}(h6WKV{1nvaZ8{*FHdl4yLh$n<_Wajh@-}ws^C?X0{-QP*|;bR&Co=D@zEYi&qyMo2H@C8da2rC z<@+vZn_uzIsT&C$g9%}5R|&KL7ArBuumo$#kTltOM#2?LO==v=9-(-pJiebc&}?(k z9t6WY7a?z(Lk{pcnht7Ix`EcCdu?XDw`B0#G12gftNye$S~LKY0hNgAlLarMO=Ehx z`1I;djAMh-67)+g@uy&|bh}bWe0Q0?Z&vUVv>>J8Yz=WqQlzPp1Fn8I%+*V4eBAE? zusO)vcoH|M(>vwgf~qA&;OuG&DyBc9Ipspa@;(A>ioPZpEy=tV2bq8mrVVHArq5^U z{R@**&ZwMh2Hq3aX}jDDEk$fg2@(l1*)Wd>qPW^Hj)T>0-Wvp`t7X#q2X@I8=19_N zDN}0Z_+Yi^6TDyldcxyD$l_tj=Vm5u7>$nZ z^<)jSSGVaVI!{W~yjC+okMRu{T;rFWkeYJgpw||gr{RuJ0;^l6C%Pt&voP(cJ#rer zN0`58?^on)hG`iEC+jch$#)#US-(T{S(W8AnPcEicN_$zI`%m7daOnY-xs&sY;}FC)Yyrd6u9s{NWom+mGt2+hV(rC8#Pz zcYNK#5?|CF-@ia`@=hIGOQ^U6KdAxRLAODx1`Awqja1}EbJiu&TRiP=4n-ZXe~43c z857Upg}*5HqFOb64SYa2*QwA4-&&6!-w3^fVC^IMs^&E{tKt%1$$rk>oVValmdxEY zLUgBo@R_j#n``I0Hm_N^>3Px-#P}GMsK!)hE+bh_!N*{{;r?U6WR%UQgCtYjOyUR-fm)Fz1#Q`O$cqA*CQrT4pC-M84+$g04 z$Z<%t#eKQ1(`*GDHvBjAim5>_l;j6PjDe`&FV`43)CWJzn`-jIG)QszRz7u0{hPy{df+b|8lfD)Sq!8;aufj=wu-HojGV53sOYStR| zGb+>GH29hTC&2uply=Fl<31%9N5lD|+wU&~m|sS}yTg)=aW`r=gpT{*9mUnB(&AywS|~%d z(l3)6kI6A#-P*IiYE$@9UHv#IPWEqXFN>S7PP}_G)SXp8r7*v0s=X0dm|B*wdiTXI z%-Tw)^LTL`-G^?m#~g;q8=p<}t0%rr&}x*;zg#GJ zqU~g9JQLJctDdT0VDZ!>q!Jll75s@26bpqw@MqXZQkB~or|urqc7dE6bz>lXRA86} zI~Y#-(bq8WD@NIc=f~QgiIbi%e*OTmtrBVQ4&m3lXp zi(BY@`7@P!13s^Uy1twfSI%{+sfIyBlBT*yeZ*xxTff{{`@IEPz)uB7e%>0oxT9DF z{qRQoI=@wt;QEmY<7?hp-x%rXBZOvN6``+)be&QS=UoA-6L5NnTCWL)q29gC% zd%M(1&m*zE0vYWt86O)s+tNJw+Ez=TVqSaIS78%`9xBw@;k+=;J~Owq#|dm-qw}sa zizvtY1~d<2nvST4eRX z7Oz!)7EL6Pf&bdPq*f2rwwoWet_^TNJx{~JT5%O_>T33*I#laoFmX?+L~9sEtGS?Htoj->OE7d51ez z?s43UVib0q_tavOp?pr3+FrX6LM<_U{S62Ck2kQp;*Z-evTy5;o6m7T=FNEkGQ0pZ zOpe{Y`4d2$Z{gas%pZ>e-5li~=l&mqpV1n{TNJn^_D_FdjrgAkY5mRm_cupko#`!d zTGxI%CLjYq>+8IK832f5L-?PZkPW)GsB**b?TEZ-{dRQQ{1YqS0zk)`f3hm@03eAi zfw$;_7ywG$5_*ePNC2RdE#6J#qRuhOJS80 zkhqHkRlo__pr-<{?fw~q>Mj*j9uH_^mjRT!`)3dvd;sLP*9HFm6b2T7)^|nUP>MY& zs3yU`X-<3iZ@{TA0F<|f1XVBm7i4{p06&7VUY%a#`ck*E~Nf~Py5twAo&3m6qDQ=Knco|gZo$P_6ASrfhhFp|AoH4 zLCa=u5G6>({6AM9XaxWX9wI^gwgkx>iocx^-3Ea2pFz!9gK7@{Ox?vH6;ZM6|9@@6 z>XV7Ny#<@Qn~go&|Bd8rsxbinr-Q(NI1!t-1!W!)ft-&1yndlz2LQz#Awi;pGLG12 z|MR{7b$UX+Jq?0}fMEMq4gpaZIPD0^@56nw4B~(koe)6e$8i58`yXrJ|Hyti|05&( zcjQ6GR8V3bf8o^=1W=X-!oQS)=iA~rMuMXD{FerL(*8@Y_yRzBCrD6DzW>q~et>`J zDIfs!^^GnA{zK!ujr2GX075xMf*MHtS3?fM`&Y990)Xt^=qAu#I{K9MP1A5n1=X4H z7eLSa&xNC%Q9%V{|Al4GaQ|!g|KsZUpW)l){7wIwgUTg9ZNmCL9O;d!f1Zy^)lttY-EmuCD*Ls0=TtpgKnWo-FO+&mW7kxx<=g>fwml$x0zy4h1{{yI$%}4+M diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties index d5e7b2ac5e4170..6ee7ce821e01c7 100644 --- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -# https://gradle.org/release-checksums/ distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip networkTimeout=10000 diff --git a/integration-tests/gradle/gradlew b/integration-tests/gradle/gradlew index 4f906e0c811fc9..aeb74cbb43e393 100755 --- a/integration-tests/gradle/gradlew +++ b/integration-tests/gradle/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,98 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +137,109 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/integration-tests/gradle/gradlew.bat b/integration-tests/gradle/gradlew.bat index ac1b06f93825db..6689b85beecde6 100644 --- a/integration-tests/gradle/gradlew.bat +++ b/integration-tests/gradle/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/integration-tests/gradle/settings.gradle.kts b/integration-tests/gradle/settings.gradle.kts new file mode 100644 index 00000000000000..df5e9c5ae2828c --- /dev/null +++ b/integration-tests/gradle/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "gradle-plugins-integration-tests" From 5b038b2defa459c17dceed068f26417533e48bf9 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Wed, 19 Apr 2023 17:35:19 +0300 Subject: [PATCH 082/333] Revert to using options.release on JavaCompile tasks instead of toolchains Signed-off-by: Konstantin Gribov --- .../kotlin/io.quarkus.devtools.java-library.gradle.kts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts b/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts index d2dc255e9ee13c..5ad88d8e2ade32 100644 --- a/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts +++ b/devtools/gradle/build-logic/src/main/kotlin/io.quarkus.devtools.java-library.gradle.kts @@ -22,13 +22,8 @@ dependencies { testImplementation(libs.getLibrary("quarkus-devtools-testing")) } -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - } -} - tasks.withType().configureEach { + options.release.set(11) options.encoding = "UTF-8" } From f2f1b0c165e4444d0b4361b9c6ae0616a30f36c4 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 19 Apr 2023 16:23:26 +0100 Subject: [PATCH 083/333] Prevent NPE for UserInfo string and boolean properties --- .../runtime/AbstractJsonObjectResponse.java | 4 +-- .../io/quarkus/oidc/runtime/UserInfoTest.java | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java index d8e7361970eb7b..c01494cc941c56 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java @@ -29,11 +29,11 @@ public AbstractJsonObjectResponse(JsonObject json) { } public String getString(String name) { - return json.getString(name); + return contains(name) ? json.getString(name) : null; } public Boolean getBoolean(String name) { - return json.getBoolean(name); + return contains(name) ? json.getBoolean(name) : null; } public Long getLong(String name) { diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java new file mode 100644 index 00000000000000..83d4f6ffba34fe --- /dev/null +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java @@ -0,0 +1,29 @@ +package io.quarkus.oidc.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import io.quarkus.oidc.UserInfo; + +public class UserInfoTest { + UserInfo userInfo = new UserInfo( + "{" + + "\"name\": \"alice\"," + + "\"admin\": true" + + "}"); + + @Test + public void testGetString() { + assertEquals("alice", userInfo.getString("name")); + assertNull(userInfo.getString("names")); + } + + @Test + public void testGetBoolean() { + assertTrue(userInfo.getBoolean("admin")); + assertNull(userInfo.getBoolean("admins")); + } +} From d783feb246f70321c172f81eb8b3839a2c8a91b0 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Wed, 19 Apr 2023 20:51:24 +0200 Subject: [PATCH 084/333] Arc - Replace deprecated isAccessible() with canAccess(Obj) --- .../src/main/java/io/quarkus/arc/impl/Reflections.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java index 79777725506d13..91849d8004e984 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java @@ -126,7 +126,7 @@ public static Constructor findConstructor(Class clazz, Class... paramet public static Object newInstance(Class clazz, Class[] parameterTypes, Object[] args) { Constructor constructor = findConstructor(clazz, parameterTypes); if (constructor != null) { - if (!constructor.isAccessible()) { + if (!constructor.canAccess(null)) { constructor.setAccessible(true); } try { @@ -152,7 +152,7 @@ public static Object newInstance(Class clazz, Class[] parameterTypes, Obje public static Object readField(Class clazz, String name, Object instance) { try { Field field = clazz.getDeclaredField(name); - if (!field.isAccessible()) { + if (!field.canAccess(instance)) { field.setAccessible(true); } return field.get(instance); @@ -164,7 +164,7 @@ public static Object readField(Class clazz, String name, Object instance) { public static void writeField(Class clazz, String name, Object instance, Object value) { try { Field field = clazz.getDeclaredField(name); - if (!field.isAccessible()) { + if (!field.canAccess(instance)) { field.setAccessible(true); } field.set(instance, value); @@ -176,7 +176,7 @@ public static void writeField(Class clazz, String name, Object instance, Obje public static Object invokeMethod(Class clazz, String name, Class[] paramTypes, Object instance, Object[] args) { try { Method method = clazz.getDeclaredMethod(name, paramTypes); - if (!method.isAccessible()) { + if (!method.canAccess(instance)) { method.setAccessible(true); } return method.invoke(instance, args); From a3fab317306035f78770f28a3b400010b7b409a1 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 19 Apr 2023 16:13:06 -0300 Subject: [PATCH 085/333] Bump quarkiverse-parent to 13 --- .../main/java/io/quarkus/devtools/commands/CreateExtension.java | 2 +- .../quarkus-my-quarkiverse-ext_pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java index 8852072eaee868..5e14e7c86af700 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateExtension.java @@ -55,7 +55,7 @@ public enum LayoutType { public static final String DEFAULT_QUARKIVERSE_PARENT_GROUP_ID = "io.quarkiverse"; public static final String DEFAULT_QUARKIVERSE_PARENT_ARTIFACT_ID = "quarkiverse-parent"; - public static final String DEFAULT_QUARKIVERSE_PARENT_VERSION = "12"; + public static final String DEFAULT_QUARKIVERSE_PARENT_VERSION = "13"; public static final String DEFAULT_QUARKIVERSE_NAMESPACE_ID = "quarkus-"; public static final String DEFAULT_QUARKIVERSE_GUIDE_URL = "https://quarkiverse.github.io/quarkiverse-docs/%s/dev/"; diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml index 79ef456dd18baf..f2bcd7fc4f6103 100644 --- a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_pom.xml @@ -5,7 +5,7 @@ io.quarkiverse quarkiverse-parent - 12 + 13 io.quarkiverse.my-quarkiverse-ext quarkus-my-quarkiverse-ext-parent From 34bcc41de2fd81ebb1155c568b1b138afac657ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 19:58:34 +0000 Subject: [PATCH 086/333] Bump io.smallrye.config:smallrye-config-source-yaml in /devtools/gradle Bumps io.smallrye.config:smallrye-config-source-yaml from 2.13.1 to 3.2.1. --- updated-dependencies: - dependency-name: io.smallrye.config:smallrye-config-source-yaml dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- devtools/gradle/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index 85df1c8967dd3c..d4435ecd0a09a2 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -2,7 +2,7 @@ plugin-publish = "1.2.0" kotlin = "1.8.10" -smallrye-config = "2.13.1" +smallrye-config = "3.2.1" junit5 = "5.9.2" assertj = "3.24.2" From 196e0f393c2f069f44cc65ee73db894804805e76 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 20 Apr 2023 06:01:14 +0200 Subject: [PATCH 087/333] Bump dekorate to 3.5.5 Changes: https://github.com/dekorateio/dekorate/releases/tag/3.5.5 --- 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 30c634dd65d85c..0191f3917b304c 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -160,7 +160,7 @@ 1.8.10 1.6.4 1.5.0 - 3.5.4 + 3.5.5 3.2.0 4.2.0 1.1.1 From f9bfadb7df95ca46121876bc84c789a45d014ed4 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 18 Apr 2023 16:50:38 +0200 Subject: [PATCH 088/333] ArC TCK runner - update exlude list - removed AroundConstructOrderTest, AroundInvokeOrderTest and PostConstructOrderTest --- .../cdi-tck-runner/src/test/resources/testng.xml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index de07e790cdc48f..c27324111b1297 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -158,11 +158,6 @@ - - - - - @@ -363,11 +358,6 @@ - - - - - @@ -401,11 +391,6 @@ - - - - - From f181c27d83eaf03c4081840ef7e5bdcaa572e59a Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 19 Apr 2023 12:25:32 +0200 Subject: [PATCH 089/333] ArC: improve javadoc for InvocationContext implementations --- .../io/quarkus/arc/impl/AroundInvokeInvocationContext.java | 2 +- .../java/io/quarkus/arc/impl/InnerInvocationContext.java | 5 +++++ .../quarkus/arc/impl/LifecycleCallbackInvocationContext.java | 4 ++-- .../io/quarkus/arc/impl/SuperclassInvocationContext.java | 2 +- .../arc/impl/TargetAroundInvokeInvocationContext.java | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java index 73b87a7649c597..46739088b3933f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java @@ -11,7 +11,7 @@ import io.quarkus.arc.ArcInvocationContext; /** - * An {@link javax.interceptor.InvocationContext} for {@link javax.interceptor.AroundInvoke} interceptors. + * An {@link jakarta.interceptor.InvocationContext} for {@link jakarta.interceptor.AroundInvoke} interceptors. *

* A new instance is created for the first interceptor in the chain. Furthermore, subsequent interceptors receive a new instance * of {@link NextAroundInvokeInvocationContext}. This does not comply with the spec but allows for "asynchronous continuation" diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java index 26b617dcb1869e..e37c23950c9e67 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InnerInvocationContext.java @@ -11,6 +11,11 @@ import io.quarkus.arc.ArcInvocationContext; +/** + * Invocation context for an "inner" invocation chain, consisting of interceptor methods declared + * in one class and its superclasses. It doesn't proceed to other interceptors in the "outer" invocation + * chain (interceptor methods declared in other classes). + */ abstract class InnerInvocationContext implements ArcInvocationContext { protected final ArcInvocationContext delegate; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java index 3d70c8c910fd70..b84425940266e8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java @@ -6,8 +6,8 @@ import java.util.Set; /** - * A simple stateful {@link javax.interceptor.InvocationContext} implementation used for {@link javax.annotation.PostConstruct} - * and {@link javax.annotation.PreDestroy} callbacks. + * A simple stateful {@link jakarta.interceptor.InvocationContext} implementation used for + * {@link jakarta.annotation.PostConstruct} and {@link jakarta.annotation.PreDestroy} callbacks. *

* All lifecycle callback interceptors of a specific chain must be invoked on the same thread. */ diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java index 77d1f8c1cd48a2..af27620fe31323 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SuperclassInvocationContext.java @@ -7,7 +7,7 @@ import jakarta.interceptor.InvocationContext; /** - * A special {@link javax.interceptor.InvocationContext} that is used if multiple interceptor methods are declared in a + * A special {@link jakarta.interceptor.InvocationContext} that is used if multiple interceptor methods are declared in a * hierarchy of an interceptor class. *

* The interceptor methods defined by the superclasses are invoked before the interceptor method defined by the interceptor diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java index 782f1768b8dfe4..7647744e50a253 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/TargetAroundInvokeInvocationContext.java @@ -6,7 +6,7 @@ import jakarta.interceptor.InvocationContext; /** - * A special {@link javax.interceptor.InvocationContext} that is used for around invoke methods declared in a hierarchy of a + * A special {@link jakarta.interceptor.InvocationContext} that is used for around invoke methods declared in a hierarchy of a * target class. *

* The interceptor methods defined by the superclasses are invoked before the interceptor method defined by the interceptor From 1adf1b7821508380d40eaa96ca9c0beedda203ba Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 19 Apr 2023 14:40:34 +0200 Subject: [PATCH 090/333] ArC: add tests for business method interceptors on target classes and fix some issues Co-authored-by: Martin Kouba --- .../io/quarkus/arc/processor/BeanInfo.java | 7 +- .../java/io/quarkus/arc/processor/Beans.java | 9 +- .../arc/processor/InterceptorInfo.java | 20 +-- .../io/quarkus/arc/processor/Methods.java | 68 ++++++---- .../processor/SubclassSkipPredicateTest.java | 2 +- ...sAndManySuperclassesWithOverridesTest.java | 64 ++++++++++ ...nvokeOnTargetClassAndSuperclassesTest.java | 45 +++++++ ...ClassAndSuperclassesWithOverridesTest.java | 53 ++++++++ .../AroundInvokeOnTargetClassTest.java | 38 ++++++ ...eAndManySuperclassesWithOverridesTest.java | 118 ++++++++++++++++++ ...getClassAndOutsideAndSuperclassesTest.java | 79 ++++++++++++ ...tsideAndSuperclassesWithOverridesTest.java | 80 ++++++++++++ ...oundInvokeOnTargetClassAndOutsideTest.java | 65 ++++++++++ 13 files changed, 604 insertions(+), 44 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index ead53d8a417038..ffa2a7995a2d67 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -619,8 +619,8 @@ private Map initInterceptedMethods(List } } - Set finalMethods = Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(), - candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, aroundInvokes); + Set finalMethods = Methods.addInterceptedMethodCandidates(this, candidates, classLevelBindings, + bytecodeTransformerConsumer, transformUnproxyableClasses); if (!finalMethods.isEmpty()) { String additionalError = ""; if (finalMethods.stream().anyMatch(KotlinUtils::isNoninterceptableKotlinMethod)) { @@ -668,7 +668,8 @@ private Map initDecoratedMethods() { ClassInfo classInfo = target.get().asClass(); addDecoratedMethods(candidates, classInfo, classInfo, bound, new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom, - beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods())); + beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods(), + beanDeployment.getAnnotationStore())); Map decoratedMethods = new HashMap<>(candidates.size()); for (Entry entry : candidates.entrySet()) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 141acb76201844..035ffb1ffd0910 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -650,10 +650,10 @@ static List getCallbacks(ClassInfo beanClass, DotName annotation, In } static List getAroundInvokes(ClassInfo beanClass, BeanDeployment deployment) { - List methods = new ArrayList<>(); AnnotationStore store = deployment.getAnnotationStore(); - Set processed = new HashSet<>(); + List methods = new ArrayList<>(); + List allMethods = new ArrayList<>(); ClassInfo aClass = beanClass; while (aClass != null) { int aroundInvokesFound = 0; @@ -661,13 +661,14 @@ static List getAroundInvokes(ClassInfo beanClass, BeanDeployment dep if (Modifier.isStatic(method.flags())) { continue; } - if (store.hasAnnotation(method, DotNames.AROUND_INVOKE) && !processed.contains(method.name())) { - methods.add(method); + if (store.hasAnnotation(method, DotNames.AROUND_INVOKE)) { + InterceptorInfo.addInterceptorMethod(allMethods, methods, method); if (++aroundInvokesFound > 1) { throw new DefinitionException( "Multiple @AroundInvoke interceptor methods declared on class: " + aClass); } } + allMethods.add(method); } DotName superTypeName = aClass.superName(); aClass = superTypeName == null || DotNames.OBJECT.equals(superTypeName) ? null diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java index 21beeb93509ed9..06eaf3ea49a587 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java @@ -247,28 +247,30 @@ public int compareTo(InterceptorInfo other) { return getTarget().toString().compareTo(other.getTarget().toString()); } - private void addInterceptorMethod(List allMethods, List interceptorMethods, MethodInfo method) { + static void addInterceptorMethod(List allMethods, List interceptorMethods, MethodInfo method) { validateSignature(method); if (!isInterceptorMethodOverriden(allMethods, method)) { interceptorMethods.add(method); } } - private boolean isInterceptorMethodOverriden(Iterable allMethods, MethodInfo method) { + static boolean isInterceptorMethodOverriden(Iterable allMethods, MethodInfo method) { for (MethodInfo m : allMethods) { - if (m.name().equals(method.name()) && m.parametersCount() == 1 - && (m.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT) - || m.parameterType(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { + if (m.name().equals(method.name()) && hasInterceptorMethodParameter(m)) { return true; } } return false; } - private MethodInfo validateSignature(MethodInfo method) { - List parameters = method.parameterTypes(); - if (parameters.size() != 1 || !(parameters.get(0).name().equals(DotNames.INVOCATION_CONTEXT) - || parameters.get(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { + static boolean hasInterceptorMethodParameter(MethodInfo method) { + return method.parametersCount() == 1 + && (method.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT) + || method.parameterType(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT)); + } + + private static MethodInfo validateSignature(MethodInfo method) { + if (!hasInterceptorMethodParameter(method)) { throw new IllegalStateException( "An interceptor method must accept exactly one parameter of type jakarta.interceptor.InvocationContext: " + method + " declared on " + method.declaringClass()); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index 1dc877ce6a2ded..cff90da5a09d2c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -52,6 +52,8 @@ final class Methods { public static final String TO_STRING = "toString"; static final Set IGNORED_METHODS = Set.of(INIT, CLINIT); + static final List OBSERVER_PRODUCER_ANNOTATIONS = List.of(DotNames.OBSERVES, DotNames.OBSERVES_ASYNC, + DotNames.PRODUCES); private Methods() { } @@ -149,15 +151,18 @@ static boolean isObjectToString(MethodInfo method) { return method.declaringClass().name().equals(DotNames.OBJECT) && method.name().equals(TO_STRING); } - static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, + static Set addInterceptedMethodCandidates(BeanInfo bean, Map> candidates, List classLevelBindings, Consumer bytecodeTransformerConsumer, - boolean transformUnproxyableClasses, List aroundInvokes) { + boolean transformUnproxyableClasses) { + BeanDeployment beanDeployment = bean.getDeployment(); + ClassInfo classInfo = bean.getTarget().get().asClass(); return addInterceptedMethodCandidates(beanDeployment, classInfo, classInfo, candidates, Set.copyOf(classLevelBindings), bytecodeTransformerConsumer, transformUnproxyableClasses, new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom, - beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods()), - false, new HashSet<>(), aroundInvokes); + beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods(), + beanDeployment.getAnnotationStore()), + false, new HashSet<>(), bean.hasAroundInvokes()); } private static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, @@ -165,20 +170,21 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea Map> candidates, Set classLevelBindings, Consumer bytecodeTransformerConsumer, boolean transformUnproxyableClasses, SubclassSkipPredicate skipPredicate, boolean ignoreMethodLevelBindings, - Set noClassInterceptorsMethods, List aroundInvokes) { + Set noClassInterceptorsMethods, boolean targetHasAroundInvokes) { Set methodsFromWhichToRemoveFinal = new HashSet<>(); Set finalMethodsFoundAndNotChanged = new HashSet<>(); skipPredicate.startProcessing(classInfo, originalClassInfo); for (MethodInfo method : classInfo.methods()) { - if (aroundInvokes.contains(method)) { - // Around invoke method declared in the target class hierarchy + // Note that we must merge the bindings first + Set bindings = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings, + ignoreMethodLevelBindings, method, noClassInterceptorsMethods); + if (bindings.isEmpty() && !targetHasAroundInvokes) { + // No bindings found and target class does not declare around invoke interceptor methods continue; } - Set merged = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings, - ignoreMethodLevelBindings, method, noClassInterceptorsMethods); - if ((merged.isEmpty() && aroundInvokes.isEmpty()) || skipPredicate.test(method)) { + if (skipPredicate.test(method)) { continue; } boolean addToCandidates = true; @@ -191,7 +197,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea } } if (addToCandidates) { - candidates.computeIfAbsent(new Methods.MethodKey(method), key -> merged); + candidates.computeIfAbsent(new Methods.MethodKey(method), key -> bindings); } } skipPredicate.methodsProcessed(); @@ -208,7 +214,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea finalMethodsFoundAndNotChanged .addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, classInfo, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, skipPredicate, - ignoreMethodLevelBindings, noClassInterceptorsMethods, aroundInvokes)); + ignoreMethodLevelBindings, noClassInterceptorsMethods, targetHasAroundInvokes)); } } @@ -218,7 +224,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea //interfaces can't have final methods addInterceptedMethodCandidates(beanDeployment, interfaceInfo, originalClassInfo, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, - skipPredicate, true, noClassInterceptorsMethods, aroundInvokes); + skipPredicate, true, noClassInterceptorsMethods, targetHasAroundInvokes); } } return finalMethodsFoundAndNotChanged; @@ -274,9 +280,7 @@ private static Set mergeBindings(BeanDeployment beanDeployme } if (Modifier.isPrivate(method.flags()) - && !Annotations.contains(methodAnnotations, DotNames.PRODUCES) - && !Annotations.contains(methodAnnotations, DotNames.OBSERVES) - && !Annotations.contains(methodAnnotations, DotNames.OBSERVES_ASYNC)) { + && !Annotations.containsAny(methodAnnotations, OBSERVER_PRODUCER_ANNOTATIONS)) { String message; if (methodLevelBindings.size() == 1) { message = String.format("%s will have no effect on method %s.%s() because the method is private.", @@ -442,24 +446,28 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str * This stateful predicate can be used to skip methods that should not be added to the generated subclass. *

* Don't forget to call {@link SubclassSkipPredicate#startProcessing(ClassInfo, ClassInfo)} before the methods are processed - * and - * {@link SubclassSkipPredicate#methodsProcessed()} afterwards. + * and {@link SubclassSkipPredicate#methodsProcessed()} afterwards. */ static class SubclassSkipPredicate implements Predicate { + private static final List INTERCEPTOR_ANNOTATIONS = List.of(DotNames.AROUND_INVOKE, DotNames.POST_CONSTRUCT, + DotNames.PRE_DESTROY); + private final BiFunction assignableFromFun; private final IndexView beanArchiveIndex; private final Set producersAndObservers; + private final AnnotationStore annotationStore; private ClassInfo clazz; private ClassInfo originalClazz; private List regularMethods; private Set bridgeMethods = new HashSet<>(); public SubclassSkipPredicate(BiFunction assignableFromFun, IndexView beanArchiveIndex, - Set producersAndObservers) { + Set producersAndObservers, AnnotationStore annotationStore) { this.assignableFromFun = assignableFromFun; this.beanArchiveIndex = beanArchiveIndex; this.producersAndObservers = producersAndObservers; + this.annotationStore = annotationStore; } void startProcessing(ClassInfo clazz, ClassInfo originalClazz) { @@ -487,7 +495,8 @@ public boolean test(MethodInfo method) { // Skip bridge methods that have a corresponding "implementation method" on the same class // The algorithm we use to detect these methods is best effort, i.e. there might be use cases where the detection fails return hasImplementation(method); - } else if (method.isSynthetic()) { + } + if (method.isSynthetic()) { // Skip non-bridge synthetic methods return true; } @@ -495,20 +504,25 @@ public boolean test(MethodInfo method) { // Skip a private method that is not and observer or producer return true; } - if (method.hasAnnotation(DotNames.POST_CONSTRUCT) || method.hasAnnotation(DotNames.PRE_DESTROY)) { - // @PreDestroy and @PostConstruct methods declared on the bean are NOT candidates for around invoke interception + if (Modifier.isStatic(method.flags())) { return true; } - if (isOverridenByBridgeMethod(method)) { + if (IGNORED_METHODS.contains(method.name())) { return true; } - if (Modifier.isStatic(method.flags())) { + if (method.declaringClass().name().equals(DotNames.OBJECT)) { return true; } - if (IGNORED_METHODS.contains(method.name())) { + if (annotationStore.hasAnyAnnotation(method, INTERCEPTOR_ANNOTATIONS)) { + // @AroundInvoke, @PreDestroy and @PostConstruct methods declared on the bean are NOT candidates for around invoke interception return true; } - if (method.declaringClass().name().equals(DotNames.OBJECT)) { + if (InterceptorInfo.hasInterceptorMethodParameter(method) + && InterceptorInfo.isInterceptorMethodOverriden(regularMethods, method)) { + // Has exactly one param InvocationContext/ArcInvocationContext and is overriden + return true; + } + if (isOverridenByBridgeMethod(method)) { return true; } if (Modifier.isInterface(clazz.flags()) && Modifier.isInterface(method.declaringClass().flags()) @@ -527,7 +541,7 @@ public boolean test(MethodInfo method) { } DotName typeName = type.name(); if (type.kind() == Kind.ARRAY) { - Type componentType = type.asArrayType().component(); + Type componentType = type.asArrayType().constituent(); if (componentType.kind() == Kind.PRIMITIVE) { continue; } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java index 5b73e11ba2f313..0035ce54cdb7b1 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java @@ -27,7 +27,7 @@ public void testPredicate() throws IOException { IndexView index = Index.of(Base.class, Submarine.class, Long.class, Number.class); AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index, null); SubclassSkipPredicate predicate = new SubclassSkipPredicate(assignabilityCheck::isAssignableFrom, null, - Collections.emptySet()); + Collections.emptySet(), new AnnotationStore(Collections.emptyList(), null)); ClassInfo submarineClass = index.getClassByName(DotName.createSimple(Submarine.class.getName())); predicate.startProcessing(submarineClass, submarineClass); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..179cff5fcd517d --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java @@ -0,0 +1,64 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-intercepted: intercepted: foobar", bean.doSomething(42)); + } + + static class Alpha { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + static class Bravo extends Alpha { + @AroundInvoke + Object specialIntercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in Charlie"; + } + } + + static class Charlie extends Bravo { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-intercepted: " + ctx.proceed(); + } + + @Override + Object specialIntercept(InvocationContext ctx) { + return "this is not an interceptor method"; + } + } + + @Singleton + static class MyBean extends Charlie { + String doSomething(int param) { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java new file mode 100644 index 00000000000000..9f7e6e786b6010 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java @@ -0,0 +1,45 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndSuperclassesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-intercepted: intercepted: foobar", bean.doSomething()); + } + + static class MyBeanSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-intercepted: " + ctx.proceed(); + } + } + + @Singleton + static class MyBean extends MyBeanSuperclass { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..a1712db87110ae --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java @@ -0,0 +1,53 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-intercepted: intercepted: foobar", bean.doSomething()); + } + + static class Alpha { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + static class Bravo extends Alpha { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-intercepted: " + ctx.proceed(); + } + } + + @Singleton + static class MyBean extends Bravo { + String doSomething() { + return "foobar"; + } + + @Override + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java new file mode 100644 index 00000000000000..89587eab4c3ae5 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("intercepted: foobar", bean.doSomething()); + } + + @Singleton + static class MyBean { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..7dc4a90501ad59 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java @@ -0,0 +1,118 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-outside: outside: super-target: target: foobar", bean.doSomething()); + } + + static class Alpha { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + static class Bravo extends Alpha { + @AroundInvoke + Object specialIntercept(InvocationContext ctx) { + return "this should not be called as the method is overridden in Charlie"; + } + } + + static class Charlie extends Bravo { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-target: " + ctx.proceed(); + } + + @Override + Object specialIntercept(InvocationContext ctx) { + return "this is not an interceptor method"; + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends Charlie { + String doSomething() { + return "foobar"; + } + + @Override + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class Delta { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyInterceptor"; + } + } + + static class Echo extends Delta { + @AroundInvoke + Object specialIntercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in Charlie"; + } + } + + static class Foxtrot extends Echo { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-outside: " + ctx.proceed(); + } + + @Override + Object specialIntercept(InvocationContext ctx) { + return "this is not an interceptor method"; + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends Foxtrot { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java new file mode 100644 index 00000000000000..a0dc9d112270ad --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java @@ -0,0 +1,79 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-outside: outside: super-target: target: foobar", bean.doSomething()); + } + + static class MyBeanSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-target: " + ctx.proceed(); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-outside: " + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends MyInterceptorSuperclass { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..d5b89eb78cb0c7 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java @@ -0,0 +1,80 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-outside: outside: target: foobar", bean.doSomething()); + } + + static class MyBeanSuperclass { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + String doSomething() { + return "foobar"; + } + + @Override + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-outside: " + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends MyInterceptorSuperclass { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java new file mode 100644 index 00000000000000..a21c8471834ab9 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java @@ -0,0 +1,65 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("outside: target: foobar", bean.doSomething()); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} From dcc26c1c8fcb8194a627f5f59e7b1862d7609314 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 10:53:10 +0200 Subject: [PATCH 091/333] Replace deprecated ArrayType.component() with ArrayType.constituent() --- .../deployment/recording/BytecodeRecorderImpl.java | 2 +- .../quarkus/deployment/steps/ReflectiveHierarchyStep.java | 2 +- .../deployment/steps/RegisterForReflectionBuildStep.java | 2 +- .../src/main/java/io/quarkus/deployment/util/AsmUtil.java | 2 +- .../main/java/io/quarkus/deployment/util/JandexUtil.java | 4 ++-- .../java/io/quarkus/arc/deployment/devconsole/Name.java | 2 +- .../hibernate/orm/deployment/JpaJandexScavenger.java | 2 +- .../test/HibernateSearchOutboxPollingClassesTest.java | 2 +- .../validator/deployment/HibernateValidatorProcessor.java | 2 +- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 2 +- .../deployment/MicroProfileRestClientEnricher.java | 2 +- .../security/deployment/PermissionSecurityChecks.java | 2 +- .../data/deployment/generate/AbstractMethodsAdder.java | 2 +- .../vertx/http/deployment/HttpSecurityProcessor.java | 2 +- .../quarkus/arc/processor/AnnotationLiteralGenerator.java | 2 +- .../quarkus/arc/processor/AnnotationLiteralProcessor.java | 2 +- .../java/io/quarkus/arc/processor/BeanResolverImpl.java | 2 +- .../java/io/quarkus/arc/processor/InjectionPointInfo.java | 2 +- .../src/main/java/io/quarkus/arc/processor/Methods.java | 2 +- .../src/main/java/io/quarkus/arc/processor/Types.java | 6 +++--- .../quarkus/arc/processor/bcextensions/ArrayTypeImpl.java | 4 ++-- .../quarkus/qute/generator/ExtensionMethodGenerator.java | 4 ++-- .../io/quarkus/qute/generator/ValueResolverGenerator.java | 4 ++-- .../reactive/common/processor/EndpointIndexer.java | 6 +++--- .../resteasy/reactive/common/processor/JandexUtil.java | 4 ++-- .../processor/scanning/ClassInjectorTransformer.java | 8 ++++---- 26 files changed, 38 insertions(+), 38 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java index 1103e1a2c36d7f..b5695a88b1fda5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java @@ -1935,7 +1935,7 @@ static ResultHandle arrayValue(AnnotationValue value, BytecodeCreator valueMetho static String componentType(MethodInfo method) { ArrayType arrayType = method.returnType().asArrayType(); - return arrayType.component().name().toString(); + return arrayType.constituent().name().toString(); } /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java index 33d26d400177c3..c3dd57bae5e171 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java @@ -160,7 +160,7 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte } } else if (type instanceof ArrayType) { visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, - type.asArrayType().component(), + type.asArrayType().constituent(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } else if (type instanceof ParameterizedType) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java index d12692931434ef..264350bb54b5c2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java @@ -210,7 +210,7 @@ private void registerType(BuildProducer reflective ClassLoader classLoader, Set processedReflectiveHierarchies, boolean methods, ReflectiveHierarchyBuildItem.Builder builder, Type type) { if (type.kind().equals(Kind.ARRAY)) { - type = type.asArrayType().component(); + type = type.asArrayType().constituent(); } if (type.kind() != Kind.PRIMITIVE && !processedReflectiveHierarchies.contains(type.name())) { registerClassDependencies(reflectiveClassHierarchy, classLoader, processedReflectiveHierarchies, diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java index 6a087324497b2a..389122cbbd3b1c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java @@ -222,7 +222,7 @@ private static void toSignature(StringBuilder sb, Type type, Function typeArgumentsFromSupert private static boolean containsTypeParameters(Type type) { switch (type.kind()) { case ARRAY: - return containsTypeParameters(type.asArrayType().component()); + return containsTypeParameters(type.asArrayType().constituent()); case PARAMETERIZED_TYPE: ParameterizedType parameterizedType = type.asParameterizedType(); if (parameterizedType.owner() != null @@ -259,7 +259,7 @@ private static Type mapGenerics(Type type, Map mapping) { switch (type.kind()) { case ARRAY: ArrayType arrayType = type.asArrayType(); - return ArrayType.create(mapGenerics(arrayType.component(), mapping), arrayType.dimensions()); + return ArrayType.create(mapGenerics(arrayType.constituent(), mapping), arrayType.dimensions()); case CLASS: return type; case PARAMETERIZED_TYPE: diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/Name.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/Name.java index 1c35b43503bed5..a014b59a2f594f 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/Name.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/Name.java @@ -52,7 +52,7 @@ static String createSimpleName(Type type) { case PARAMETERIZED_TYPE: return createSimple(type.asParameterizedType()); case ARRAY: - Type component = type.asArrayType().component(); + Type component = type.asArrayType().constituent(); if (component.kind() == Kind.CLASS) { return createSimple(type.toString()); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java index 14584ab4c51fc3..05c97b7fe85e72 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java @@ -497,7 +497,7 @@ private static void collectEmbeddedTypes(Set embeddedTypes, Type indexT } break; case ARRAY: - collectEmbeddedTypes(embeddedTypes, indexType.asArrayType().component()); + collectEmbeddedTypes(embeddedTypes, indexType.asArrayType().constituent()); break; default: // do nothing diff --git a/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java b/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java index be49d54a33287d..db3024409f88e2 100644 --- a/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java +++ b/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java @@ -145,7 +145,7 @@ private static void collectModelClassesRecursively(Index index, Type type, Set types = t.asParameterizedType().arguments(); if (types.size() == 1) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java index ad5dd730eb7ca3..26fe104ec8e630 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java @@ -144,7 +144,7 @@ private static boolean validateConstructor(IndexView index, String permissionCla } private static boolean isStringArray(Type type) { - return type.kind() == Type.Kind.ARRAY && isString(type.asArrayType().component()); + return type.kind() == Type.Kind.ARRAY && isString(type.asArrayType().constituent()); } private static boolean isString(Type type) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java index e34b0139c192f2..3a7dae47c14467 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java @@ -174,7 +174,7 @@ static String defaultValueStaticFieldName(MethodInfo annotationMember) { private static boolean returnsClassOrClassArray(MethodInfo annotationMember) { boolean returnsClass = DotNames.CLASS.equals(annotationMember.returnType().name()); boolean returnsClassArray = annotationMember.returnType().kind() == Type.Kind.ARRAY - && DotNames.CLASS.equals(annotationMember.returnType().asArrayType().component().name()); + && DotNames.CLASS.equals(annotationMember.returnType().asArrayType().constituent().name()); return returnsClass || returnsClassArray; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java index 78c2a429fec3c4..6b5f0b671a7644 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java @@ -355,7 +355,7 @@ private static String componentType(MethodInfo method) { private static DotName componentTypeName(MethodInfo method) { ArrayType arrayType = method.returnType().asArrayType(); - return arrayType.component().name(); + return arrayType.constituent().name(); } private static String generateAnnotationLiteralClassName(DotName annotationName) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java index 23051ce9d52ac0..8d5cf0dcac47bd 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolverImpl.java @@ -145,7 +145,7 @@ boolean matchesNoBoxing(Type requiredType, Type beanType) { if (ARRAY.equals(requiredType.kind())) { if (ARRAY.equals(beanType.kind())) { // Array types are considered to match only if their element types are identical - return matchesNoBoxing(requiredType.asArrayType().component(), beanType.asArrayType().component()); + return matchesNoBoxing(requiredType.asArrayType().constituent(), beanType.asArrayType().constituent()); } } else if (CLASS.equals(requiredType.kind())) { if (CLASS.equals(beanType.kind())) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java index 77fc221e4eb259..c02cb407a70261 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java @@ -339,7 +339,7 @@ private static Type resolveType(Type type, ClassInfo beanClass, BeanDeployment b return ParameterizedType.create(parameterizedType.name(), typeParams, parameterizedType.owner()); } else if (type.kind() == org.jboss.jandex.Type.Kind.ARRAY) { ArrayType arrayType = type.asArrayType(); - Type component = arrayType.component(); + Type component = arrayType.constituent(); if (component.kind() == org.jboss.jandex.Type.Kind.TYPE_VARIABLE || component.kind() == org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE) { component = resolveType(component, beanClass, beanDeployment, resolvedTypeVariables); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index ad6e073eec5318..020495e2423f5e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -523,7 +523,7 @@ public boolean test(MethodInfo method) { } DotName typeName = type.name(); if (type.kind() == Kind.ARRAY) { - Type componentType = type.asArrayType().component(); + Type componentType = type.asArrayType().constituent(); if (componentType.kind() == Kind.PRIMITIVE) { continue; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index aeb52b2e971956..4002918a6815ac 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -197,7 +197,7 @@ private static void getTypeHandle(AssignableResultHandle variable, BytecodeCreat arrayHandle = doLoadClass(creator, array.name().toString(), tccl); } else { // E.g. List[] -> new GenericArrayTypeImpl(new ParameterizedTypeImpl(List.class, String.class)) - Type componentType = type.asArrayType().component(); + Type componentType = type.asArrayType().constituent(); AssignableResultHandle componentTypeHandle = creator.createVariable(Object.class); getTypeHandle(componentTypeHandle, creator, componentType, tccl, cache, typeVariables); arrayHandle = creator.newInstance( @@ -349,9 +349,9 @@ static Type getProviderType(ClassInfo classInfo) { } static Type getArrayElementType(ArrayType array) { - Type elementType = array.component(); + Type elementType = array.constituent(); while (elementType.kind() == Kind.ARRAY) { - elementType = elementType.asArrayType().component(); + elementType = elementType.asArrayType().constituent(); } return elementType; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ArrayTypeImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ArrayTypeImpl.java index e3cb1e0a71665f..88e20c2c9cfe63 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ArrayTypeImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ArrayTypeImpl.java @@ -13,8 +13,8 @@ class ArrayTypeImpl extends TypeImpl implements Arra public Type componentType() { int dimensions = jandexType.dimensions(); org.jboss.jandex.Type componentType = dimensions == 1 - ? jandexType.component() - : org.jboss.jandex.ArrayType.create(jandexType.component(), dimensions - 1); + ? jandexType.constituent() + : org.jboss.jandex.ArrayType.create(jandexType.constituent(), dimensions - 1); return fromJandexType(jandexIndex, annotationOverlays, componentType); } } diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java index 145514ca1dd664..bbc9b8f3cb965b 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java @@ -328,7 +328,7 @@ private void implementResolve(ClassCreator valueResolver, ClassInfo declaringCla // Last param is varargs Type varargsParam = params.get(lastIdx).type; ResultHandle componentType = tryCatch - .loadClass(varargsParam.asArrayType().component().name().toString()); + .loadClass(varargsParam.asArrayType().constituent().name().toString()); ResultHandle varargsResults = tryCatch.invokeVirtualMethod( Descriptors.EVALUATED_PARAMS_GET_VARARGS_RESULTS, evaluatedParamsHandle, tryCatch.load(evaluated.size()), componentType); @@ -579,7 +579,7 @@ public void addMethod(MethodInfo method, String matchName, List matchNam // Last param is varargs Type varargsParam = params.get(lastIdx).type; ResultHandle componentType = tryCatch - .loadClass(varargsParam.asArrayType().component().name().toString()); + .loadClass(varargsParam.asArrayType().constituent().name().toString()); ResultHandle varargsResults = tryCatch.invokeVirtualMethod( Descriptors.EVALUATED_PARAMS_GET_VARARGS_RESULTS, whenEvaluatedParams, tryCatch.load(evaluated.size()), componentType); diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java index 05e62a4f268fef..8d0a792ad82c0f 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java @@ -703,7 +703,7 @@ private void matchMethod(MethodInfo method, ClassInfo clazz, MethodCreator resol // Then we need to create an array for the last argument Type varargsParam = parameterTypes.get(parameterTypes.size() - 1); ResultHandle componentType = tryCatch - .loadClass(varargsParam.asArrayType().component().name().toString()); + .loadClass(varargsParam.asArrayType().constituent().name().toString()); ResultHandle varargsResults = tryCatch.invokeVirtualMethod(Descriptors.EVALUATED_PARAMS_GET_VARARGS_RESULTS, evaluatedParams, tryCatch.load(parameterTypes.size()), componentType); // E.g. String, String, String -> String, String[] @@ -852,7 +852,7 @@ private void matchMethods(String matchName, int matchParamsCount, Collection String, String[] diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index db8480ea24f99b..ffd385eac6eb88 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -1419,7 +1419,7 @@ && isParameterContainerType(paramType.asClassType())) { || convertible) { builder.setSingle(false); } - elementType = toClassName(at.component(), currentClassInfo, actualEndpointInfo, index); + elementType = toClassName(at.constituent(), currentClassInfo, actualEndpointInfo, index); if (convertible) { handleArrayParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); } @@ -1450,8 +1450,8 @@ && isParameterContainerType(paramType.asClassType())) { private boolean isFormParamConvertible(Type paramType) { // let's not call the array converter for byte[] for multipart if (paramType.kind() == Kind.ARRAY - && paramType.asArrayType().component().kind() == Kind.PRIMITIVE - && paramType.asArrayType().component().asPrimitiveType().primitive() == Primitive.BYTE) { + && paramType.asArrayType().constituent().kind() == Kind.PRIMITIVE + && paramType.asArrayType().constituent().asPrimitiveType().primitive() == Primitive.BYTE) { return false; } else { return true; diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java index 1e2f2f6fc29adf..3c37e6de4f305a 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java @@ -248,7 +248,7 @@ private static boolean containsTypeParameters(List typeArgumentsFromSupert private static boolean containsTypeParameters(Type type) { switch (type.kind()) { case ARRAY: - return containsTypeParameters(type.asArrayType().component()); + return containsTypeParameters(type.asArrayType().constituent()); case PARAMETERIZED_TYPE: ParameterizedType parameterizedType = type.asParameterizedType(); if (parameterizedType.owner() != null @@ -274,7 +274,7 @@ private static Type mapGenerics(Type type, Map mapping) { switch (type.kind()) { case ARRAY: ArrayType arrayType = type.asArrayType(); - return ArrayType.create(mapGenerics(arrayType.component(), mapping), arrayType.dimensions()); + return ArrayType.create(mapGenerics(arrayType.constituent(), mapping), arrayType.dimensions()); case CLASS: return type; case PARAMETERIZED_TYPE: diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java index 154ebf66e40b09..f1235df88f3ff8 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java @@ -377,7 +377,7 @@ private void generateMultipartFormStaticInit(MethodVisitor mv, FieldInfo fieldIn if (!extractor.isSingle()) { boolean isArray = type.kind() == org.jboss.jandex.Type.Kind.ARRAY; // it's T[] or List - type = isArray ? type.asArrayType().component() + type = isArray ? type.asArrayType().constituent() : type.asParameterizedType().arguments().get(0); } // type @@ -489,7 +489,7 @@ private void generateConverterInitMethod(FieldInfo fieldInfo, ParameterConverter // [instance, instance, converter] // array converter wants the array instance type if (converter instanceof ArrayConverter.ArraySupplier) { - org.jboss.jandex.Type componentType = fieldInfo.type().asArrayType().component(); + org.jboss.jandex.Type componentType = fieldInfo.type().asArrayType().constituent(); initConverterMethod.visitLdcInsn(componentType.name().toString('.')); // [instance, instance, converter, componentType] initConverterMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, delegatorBinaryName, "", @@ -862,8 +862,8 @@ private MultipartFormParamExtractor.Type getMultipartFormType(ServerIndexedParam } else if (param.getElementType().equals(InputStream.class.getName())) { return MultipartFormParamExtractor.Type.InputStream; } else if (param.getParamType().kind() == Kind.ARRAY - && param.getParamType().asArrayType().component().kind() == Kind.PRIMITIVE - && param.getParamType().asArrayType().component().asPrimitiveType().primitive() == Primitive.BYTE) { + && param.getParamType().asArrayType().constituent().kind() == Kind.PRIMITIVE + && param.getParamType().asArrayType().constituent().asPrimitiveType().primitive() == Primitive.BYTE) { return MultipartFormParamExtractor.Type.ByteArray; } else if (mimeType != null && !mimeType.equals(MediaType.TEXT_PLAIN)) { return MultipartFormParamExtractor.Type.PartType; From 544150d6ddca3e939714ce38cfe2e92e2155a4d3 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 11:07:45 +0200 Subject: [PATCH 092/333] Replace deprecated WildcardType.create with createUpperBound and createLowerBound --- .../io/quarkus/arc/processor/bcextensions/TypesImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/TypesImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/TypesImpl.java index 1e7921dae5db8e..097da7fc562c24 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/TypesImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/TypesImpl.java @@ -135,15 +135,15 @@ private ParameterizedType parameterizedType(DotName genericTypeName, Type... typ @Override public WildcardType wildcardWithUpperBound(Type upperBound) { - org.jboss.jandex.WildcardType jandexType = org.jboss.jandex.WildcardType.create(((TypeImpl) upperBound).jandexType, - true); + org.jboss.jandex.WildcardType jandexType = org.jboss.jandex.WildcardType + .createUpperBound(((TypeImpl) upperBound).jandexType); return new WildcardTypeImpl(jandexIndex, annotationOverlays, jandexType); } @Override public WildcardType wildcardWithLowerBound(Type lowerBound) { - org.jboss.jandex.WildcardType jandexType = org.jboss.jandex.WildcardType.create(((TypeImpl) lowerBound).jandexType, - false); + org.jboss.jandex.WildcardType jandexType = org.jboss.jandex.WildcardType + .createLowerBound(((TypeImpl) lowerBound).jandexType); return new WildcardTypeImpl(jandexIndex, annotationOverlays, jandexType); } From 931a95e259e4efacfe926044f0037173e6d7f14c Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 11:22:40 +0200 Subject: [PATCH 093/333] WithSpan and SpanAttribute deprecated package switch --- docs/src/main/asciidoc/opentelemetry.adoc | 4 ++-- .../deployment/instrumentation/GrpcOpenTelemetryTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index eb330845297b62..c93a3681cd269f 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -447,10 +447,10 @@ The instrumentation documented in this section has been tested with Quarkus and === CDI -Annotating a method in any CDI aware bean with the `io.opentelemetry.extension.annotations.WithSpan` +Annotating a method in any CDI aware bean with the `io.opentelemetry.instrumentation.annotations.WithSpan` annotation will create a new Span and establish any required relationships with the current Trace context. -Method parameters can be annotated with the `io.opentelemetry.extension.annotations.SpanAttribute` annotation to +Method parameters can be annotated with the `io.opentelemetry.instrumentation.annotations.SpanAttribute` annotation to indicate which method parameters should be part of the Trace. Example: diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java index 2bc9d9a8ba6f9d..543f2a2c99aad3 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java @@ -32,7 +32,7 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.instrumentation.annotations.WithSpan; import io.opentelemetry.sdk.trace.data.SpanData; import io.quarkus.grpc.GrpcClient; import io.quarkus.grpc.GrpcService; From ce167d2e32e562b3df0a4d6f7a5a0b99c4243167 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 20 Apr 2023 11:49:08 +0300 Subject: [PATCH 094/333] Add note about using Jacoco with a multi-module project Fixes: #30555 --- docs/src/main/asciidoc/tests-with-coverage.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/main/asciidoc/tests-with-coverage.adoc b/docs/src/main/asciidoc/tests-with-coverage.adoc index 5c6e46bf6f38bd..0cda4d5b29bf33 100644 --- a/docs/src/main/asciidoc/tests-with-coverage.adoc +++ b/docs/src/main/asciidoc/tests-with-coverage.adoc @@ -179,6 +179,11 @@ There are some config options that affect this: include::{generated-dir}/config/quarkus-jacoco-jacoco-config.adoc[opts=optional, leveloffset=+1] +[TIP] +==== +When working with a multi-module project, then for code coverage to work properly, the upstream modules need to be properly xref:cdi-reference.adoc#bean_discovery[indexed]. +==== + == Coverage for tests not using @QuarkusTest The Quarkus automatic JaCoCo config will only work for tests that are annotated with `@QuarkusTest`. If you want to check From 63d6a0a7aa3a7a5270425750d9631d81b30628d4 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 12:18:07 +0200 Subject: [PATCH 095/333] SRye Channel and Emitter leftovers --- docs/src/main/asciidoc/kafka.adoc | 2 +- docs/src/main/asciidoc/resteasy-reactive.adoc | 4 ++-- .../smallrye/reactivemessaging/devmode/HttpFrontend.java | 2 +- .../reactivemessaging/signatures/ProcessorSignatureTest.java | 4 ++-- .../reactivemessaging/signatures/SubscriberSignatureTest.java | 4 ++-- .../signatures/TransformerSignatureTest.java | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 36ed170913a23e..7dda7c2f445c5f 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -177,7 +177,7 @@ Alternatively, your application can inject a `Multi` in your bean and subscribe [source, java] ---- import io.smallrye.mutiny.Multi; -import io.smallrye.reactive.messaging.annotations.Channel; +import org.eclipse.microprofile.reactive.messaging.Channel; import jakarta.inject.Inject; import jakarta.ws.rs.GET; diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index ee06fd43690868..bbc232ca45c4e0 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -956,7 +956,7 @@ import org.jboss.resteasy.reactive.RestStreamElementType; import io.smallrye.mutiny.Multi; -import io.smallrye.reactive.messaging.annotations.Channel; +import org.eclipse.microprofile.reactive.messaging.Channel; @Path("escoffier") public class Endpoint { @@ -995,7 +995,7 @@ import org.jboss.resteasy.reactive.RestStreamElementType; import io.smallrye.mutiny.Multi; -import io.smallrye.reactive.messaging.annotations.Channel; +import org.eclipse.microprofile.reactive.messaging.Channel; @Path("escoffier") public class Endpoint { diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/devmode/HttpFrontend.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/devmode/HttpFrontend.java index 53faabcde3fb1d..2dc18948394702 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/devmode/HttpFrontend.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/devmode/HttpFrontend.java @@ -5,11 +5,11 @@ import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; +import org.eclipse.microprofile.reactive.messaging.Channel; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import io.smallrye.reactive.messaging.annotations.Channel; import io.vertx.core.http.HttpServerResponse; import io.vertx.ext.web.Router; diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/ProcessorSignatureTest.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/ProcessorSignatureTest.java index b65357bf8364cb..96dbf02e3430f3 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/ProcessorSignatureTest.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/ProcessorSignatureTest.java @@ -13,6 +13,8 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Emitter; import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Message; import org.eclipse.microprofile.reactive.messaging.Outgoing; @@ -26,8 +28,6 @@ import org.reactivestreams.Publisher; import io.quarkus.test.QuarkusUnitTest; -import io.smallrye.reactive.messaging.annotations.Channel; -import io.smallrye.reactive.messaging.annotations.Emitter; @SuppressWarnings("unused") public class ProcessorSignatureTest { diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/SubscriberSignatureTest.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/SubscriberSignatureTest.java index b78eb4f3a41597..8db144c416660b 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/SubscriberSignatureTest.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/SubscriberSignatureTest.java @@ -13,6 +13,8 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Emitter; import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Message; import org.junit.jupiter.api.Test; @@ -21,8 +23,6 @@ import org.reactivestreams.Subscription; import io.quarkus.test.QuarkusUnitTest; -import io.smallrye.reactive.messaging.annotations.Channel; -import io.smallrye.reactive.messaging.annotations.Emitter; @SuppressWarnings("unused") public class SubscriberSignatureTest { diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/TransformerSignatureTest.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/TransformerSignatureTest.java index 171563a1d14f3c..cc60ea2e903ae7 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/TransformerSignatureTest.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/signatures/TransformerSignatureTest.java @@ -12,6 +12,8 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Emitter; import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Message; import org.eclipse.microprofile.reactive.messaging.Outgoing; @@ -23,8 +25,6 @@ import org.reactivestreams.Publisher; import io.quarkus.test.QuarkusUnitTest; -import io.smallrye.reactive.messaging.annotations.Channel; -import io.smallrye.reactive.messaging.annotations.Emitter; @SuppressWarnings("unused") public class TransformerSignatureTest { From b207e8f625add9e9a0a5ac3c8df87b8f5df12e22 Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Wed, 19 Apr 2023 15:32:07 +0200 Subject: [PATCH 096/333] Update apicurio registry compatible dependency doc Fixes #32643 --- .../asciidoc/kafka-schema-registry-avro.adoc | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc index 437e49d3564703..f554c6e4fb5a7f 100644 --- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc +++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc @@ -622,7 +622,7 @@ For example, with Apicurio dev service if you set the image name to use version [source,properties] ---- -quarkus.apicurio-registry.devservices.image-name=quay.io/apicurio/apicurio-registry-mem:2.4.2.Final +quarkus.apicurio-registry.devservices.image-name=quay.io/apicurio/apicurio-registry-mem:2.1.5.Final ---- You need to make sure that `apicurio-registry-serdes-avro-serde` dependency @@ -645,6 +645,16 @@ and the REST client `apicurio-common-rest-client-vertx` dependency are set to co + + io.apicurio + apicurio-registry-client + 2.1.5.Final + + + io.apicurio + apicurio-registry-common + 2.1.5.Final + io.apicurio apicurio-registry-serdes-avro-serde @@ -654,6 +664,14 @@ and the REST client `apicurio-common-rest-client-vertx` dependency are set to co io.apicurio apicurio-common-rest-client-jdk + + io.apicurio + apicurio-registry-client + + + io.apicurio + apicurio-registry-common + @@ -674,6 +692,18 @@ dependencies { implementation("io.quarkus:quarkus-apicurio-registry-avro") implementation("io.apicurio:apicurio-registry-serdes-avro-serde") { exclude group: "io.apicurio", module: "apicurio-common-rest-client-jdk" + exclude group: "io.apicurio", module: "apicurio-registry-client" + exclude group: "io.apicurio", module: "apicurio-registry-common" + version { + strictly "2.1.5.Final" + } + } + implementation("io.apicurio:apicurio-registry-client") { + version { + strictly "2.1.5.Final" + } + } + implementation("io.apicurio:apicurio-registry-common") { version { strictly "2.1.5.Final" } @@ -686,6 +716,11 @@ dependencies { } ---- +Known previous compatible versions for `apicurio-registry-client` and `apicurio-common-rest-client-vertx` are the following + +- `apicurio-registry-client` 2.1.5.Final with `apicurio-common-rest-client-vertx` 0.1.5.Final +- `apicurio-registry-client` 2.3.1.Final with `apicurio-common-rest-client-vertx` 0.1.13.Final + [[confluent]] == Using the Confluent Schema Registry From a6f45f39d5667b0bd43202d2e27c6301bbf10f60 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 20 Apr 2023 21:58:11 +1000 Subject: [PATCH 097/333] Dev UI Fix labels not clearing out Signed-off-by: Phillip Kruger --- .../dev-ui/qwc/qwc-extension-link.js | 119 +++++++++++++++--- 1 file changed, 99 insertions(+), 20 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js index 37ff40a0a803e7..b7f8b7ee8834bf 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js @@ -1,4 +1,4 @@ -import { LitElement, html, css} from 'lit'; +import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element'; import { JsonRpc } from 'jsonrpc'; import '@vaadin/icon'; import 'qui-badge'; @@ -6,7 +6,7 @@ import 'qui-badge'; /** * This component adds a custom link on the Extension card */ -export class QwcExtensionLink extends LitElement { +export class QwcExtensionLink extends QwcHotReloadElement { static styles = css` .extensionLink { @@ -49,9 +49,86 @@ export class QwcExtensionLink extends LitElement { _effectiveLabel: {state: true}, _observer: {state: false}, }; + + _staticLabel = null; + _dynamicLabel = null; + _streamingLabel = null; + + set staticLabel(val) { + if(!this._staticLabel || (this._staticLabel && this._staticLabel != val)){ + let oldVal = this._staticLabel; + this._staticLabel = val; + this.requestUpdate('staticLabel', oldVal); + this.hotReload(); + } + } + + get staticLabel() { + return this._staticLabel; + } + + set dynamicLabel(val) { + if(!this._dynamicLabel || (this._dynamicLabel && this._dynamicLabel != val)){ + let oldVal = this._dynamicLabel; + this._dynamicLabel = val; + this.requestUpdate('dynamicLabel', oldVal); + this.hotReload(); + } + } + + get dynamicLabel() { + return this._dynamicLabel; + } + + set streamingLabel(val) { + if(!this._streamingLabel || (this._streamingLabel && this._streamingLabel != val)){ + let oldVal = this._streamingLabel; + this._streamingLabel = val; + this.requestUpdate('streamingLabel', oldVal); + this.hotReload(); + } + } + + get streamingLabel() { + return this._streamingLabel; + } connectedCallback() { super.connectedCallback(); + this.hotReload(); + } + + hotReload(){ + if(this._observer){ + this._observer.cancel(); + } + this._effectiveLabel = null; + if(this.streamingLabel){ + this.jsonRpc = new JsonRpc(this); + this._observer = this.jsonRpc[this.streamingLabel]().onNext(jsonRpcResponse => { + let oldVal = this._effectiveLabel; + this._effectiveLabel = jsonRpcResponse.result; + this.requestUpdate('_effectiveLabel', oldVal); + }); + }else if(this.dynamicLabel){ + this.jsonRpc = new JsonRpc(this); + this.jsonRpc[this.dynamicLabel]().then(jsonRpcResponse => { + let oldVal = this._effectiveLabel; + this._effectiveLabel = jsonRpcResponse.result; + this.requestUpdate('_effectiveLabel', oldVal); + }); + }else if(this.staticLabel){ + let oldVal = this._effectiveLabel; + this._effectiveLabel = this.staticLabel; + this.requestUpdate('_effectiveLabel', oldVal); + }else{ + let oldVal = this._effectiveLabel; + this._effectiveLabel = null; + this.requestUpdate('_effectiveLabel', oldVal); + } + } + + _getEffectiveLabel(){ if(this.streamingLabel){ this.jsonRpc = new JsonRpc(this); this._observer = this.jsonRpc[this.streamingLabel]().onNext(jsonRpcResponse => { @@ -71,28 +148,30 @@ export class QwcExtensionLink extends LitElement { if(this._observer){ this._observer.cancel(); } - super.disconnectedCallback() + super.disconnectedCallback(); } render() { - let routerIgnore = false; - - let p = this.path; - let t = "_self"; - if(!this.embed){ - routerIgnore = true; - p = this.externalUrl; - t = "_blank"; + if(this.path){ + let routerIgnore = false; + + let p = this.path; + let t = "_self"; + if(!this.embed){ + routerIgnore = true; + p = this.externalUrl; + t = "_blank"; + } + return html` + + + + ${this.displayName} + + ${this._renderBadge()} + + `; } - return html` - - - - ${this.displayName} - - ${this._renderBadge()} - - `; } _renderBadge() { From 3f0808358b9394729a1031484e0fd9b55ee7ddbb Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 20 Apr 2023 22:09:05 +1000 Subject: [PATCH 098/333] Dev UI remove console.log Signed-off-by: Phillip Kruger --- .../src/main/resources/dev-ui/qwc-arc-fired-events.js | 4 +--- .../src/main/resources/dev-ui/qwc-arc-invocation-trees.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js index 8185976e9a23bc..5b6e8bb5c8309b 100644 --- a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js +++ b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js @@ -103,21 +103,19 @@ export class QwcArcFiredEvents extends LitElement { } _refresh(){ - console.log("refresh"); this.jsonRpc.getLastEvents().then(events => { this._firedEvents = events.result; }); } _clear(){ - console.log("clear"); this.jsonRpc.clearLastEvents().then(events => { this._firedEvents = events.result; }); } _toggleContext(){ - console.log("context"); + // TODO: } _addToEvents(event){ diff --git a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js index 0e3e18d482aca4..6d5e0e2a97a534 100644 --- a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js +++ b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js @@ -64,21 +64,19 @@ export class QwcArcInvocationTrees extends LitElement { } _refresh(){ - console.log("refresh"); this.jsonRpc.getLastInvocations().then(invocations => { this._invocations = invocations.result; }); } _clear(){ - console.log("clear"); this.jsonRpc.clearLastInvocations().then(invocations => { this._invocations = invocations.result; }); } _toggleFilter(){ - console.log("filter"); + // TODO: } } customElements.define('qwc-arc-invocation-trees', QwcArcInvocationTrees); \ No newline at end of file From 999a102c9bb009e92fda5d6a8b01cd641b4234a8 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 20 Apr 2023 15:03:35 +0300 Subject: [PATCH 099/333] Register Resource Class for reflection when customer Reader or Writer is used This is needed because these Reader and Writer classes expect to be passed the method annotations --- .../JaxrsClientReactiveProcessor.java | 2 +- .../QuarkusServerEndpointIndexer.java | 81 +++++++++++++++++-- .../deployment/ResteasyReactiveProcessor.java | 25 ++++-- .../common/processor/EndpointIndexer.java | 30 +++++-- .../scanning/ResteasyReactiveScanner.java | 6 +- .../processor/scanning/ScannedSerializer.java | 24 +++++- .../io/quarkus/it/envers/InputResource.java | 18 +++++ .../java/io/quarkus/it/envers/Message.java | 21 +++++ .../io/quarkus/it/envers/MessageProvider.java | 45 +++++++++++ .../io/quarkus/it/envers/OutputResource.java | 16 ++++ .../io/quarkus/it/envers/InputResourceIT.java | 7 ++ .../quarkus/it/envers/InputResourceTest.java | 22 +++++ .../quarkus/it/envers/OutputResourceIT.java | 7 ++ .../quarkus/it/envers/OutputResourceTest.java | 23 ++++++ 14 files changed, 301 insertions(+), 26 deletions(-) create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/InputResource.java create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message.java create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java create mode 100644 integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceIT.java create mode 100644 integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceTest.java create mode 100644 integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceIT.java create mode 100644 integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 42fb8d05ef8075..b7aa64fa5d191c 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -313,7 +313,7 @@ public boolean test(Map anns) { }) .setResourceMethodCallback(new Consumer<>() { @Override - public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { + public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { MethodInfo method = entry.getMethodInfo(); String source = JaxrsClientReactiveProcessor.class.getSimpleName() + " > " + method.declaringClass() + "[" + method + "]"; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java index 62181bcb5d5758..b7cfc7052729fa 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java @@ -1,5 +1,8 @@ package io.quarkus.resteasy.reactive.server.deployment; +import static org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames.SERVER_MESSAGE_BODY_READER; + +import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -14,6 +17,8 @@ import org.jboss.jandex.Type; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig; +import org.jboss.resteasy.reactive.common.model.MethodParameter; +import org.jboss.resteasy.reactive.common.model.ParameterType; import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler; import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner; import org.jboss.resteasy.reactive.common.processor.scanning.ScannedSerializer; @@ -24,9 +29,11 @@ import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter; import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory; +import io.quarkus.builder.BuildException; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.util.JandexUtil; import io.quarkus.resteasy.reactive.common.deployment.JsonDefaultProducersHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; @@ -147,16 +154,73 @@ protected void handleAdditionalMethodProcessing(ServerResourceMethod method, Cla warnAboutMissingJsonProviderIfNeeded(method, info); } + @Override + public boolean additionalRegisterClassForReflectionCheck(ResourceMethodCallbackEntry entry) { + return checkBodyParameterMessageBodyReader(entry); + } + + /** + * Check whether the Resource Method has a body parameter for which there exists a matching + * {@link jakarta.ws.rs.ext.MessageBodyReader} + * that is not a {@link org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader}. + * In this case the Resource Class needs to be registered for reflection because the + * {@link jakarta.ws.rs.ext.MessageBodyReader#isReadable(Class, java.lang.reflect.Type, Annotation[], MediaType)} + * method expects to be passed the method annotations. + */ + private boolean checkBodyParameterMessageBodyReader(ResourceMethodCallbackEntry entry) { + MethodParameter[] parameters = entry.getResourceMethod().getParameters(); + if (parameters.length == 0) { + return false; + } + MethodParameter bodyParameter = null; + for (MethodParameter parameter : parameters) { + if (parameter.parameterType == ParameterType.BODY) { + bodyParameter = parameter; + break; + } + } + if (bodyParameter == null) { + return false; + } + String parameterClassName = bodyParameter.getDeclaredType(); + List readers = getSerializerScanningResult().getReaders(); + + for (ScannedSerializer reader : readers) { + if (isSubclassOf(parameterClassName, reader.getHandledClassName()) && !isServerMessageBodyReader( + reader.getClassInfo())) { + return true; + } + } + return false; + } + + private boolean isSubclassOf(String className, String parentName) { + if (className.equals(parentName)) { + return true; + } + ClassInfo classByName = index.getClassByName(className); + if (classByName == null) { + return false; + } + try { + return JandexUtil.isSubclassOf(index, classByName, + DotName.createSimple(parentName)); + } catch (BuildException e) { + return false; + } + } + + private boolean isServerMessageBodyReader(ClassInfo readerClassInfo) { + return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_READER).contains(readerClassInfo); + } + private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, MethodInfo info) { if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.resteasy.reactive.json")) { return; } if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson())) { - if (serializerScanningResult == null) { - serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult); - } - boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(serializerScanningResult.getReaders()); - boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(serializerScanningResult.getWriters()); + boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders()); + boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters()); if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) { LOGGER.warnf("Quarkus detected the use of JSON in JAX-RS method '" + info.declaringClass().name() + "#" + info.name() @@ -165,6 +229,13 @@ private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, M } } + private SerializerScanningResult getSerializerScanningResult() { + if (serializerScanningResult == null) { + serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult); + } + return serializerScanningResult; + } + private boolean appProvidedJsonProviderExists(List providers) { boolean appProvidedJsonReaderExists = false; for (ScannedSerializer provider : providers) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 8f582abc8e2bdd..7d4574abb0d995 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -498,7 +498,7 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem, : Collections.emptyMap()) .setResourceMethodCallback(new Consumer<>() { @Override - public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { + public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { MethodInfo method = entry.getMethodInfo(); resourceMethodEntries.add(new ResteasyReactiveResourceMethodEntriesBuildItem.Entry( @@ -545,18 +545,27 @@ public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { .build()); } if (parameterType.name().equals(FILE)) { - reflectiveClassBuildItemBuildProducer - .produce(ReflectiveClassBuildItem - .builder(entry.getActualEndpointInfo().name().toString()) - .constructors(false).methods().build()); + minimallyRegisterResourceClassForReflection(entry, + reflectiveClassBuildItemBuildProducer); } } if (filtersAccessResourceMethod) { - reflectiveClassBuildItemBuildProducer.produce( - ReflectiveClassBuildItem.builder(entry.getActualEndpointInfo().name().toString()) - .constructors(false).methods().build()); + minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer); } + + if (entry.additionalRegisterClassForReflectionCheck()) { + minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer); + } + } + + private void minimallyRegisterResourceClassForReflection( + EndpointIndexer.ResourceMethodCallbackEntry entry, + BuildProducer reflectiveClassBuildItemBuildProducer) { + reflectiveClassBuildItemBuildProducer + .produce(ReflectiveClassBuildItem + .builder(entry.getActualEndpointInfo().name().toString()) + .constructors(false).methods().build()); } private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName annotation) { diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index db8480ea24f99b..9f5138bc2d1be5 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -219,7 +219,7 @@ public abstract class EndpointIndexer> classLevelExceptionMappers; private final Function> factoryCreator; - private final Consumer resourceMethodCallback; + private final Consumer resourceMethodCallback; private final AnnotationStore annotationStore; protected final ApplicationScanningResult applicationScanningResult; private final Set contextTypes; @@ -773,7 +773,9 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf handleAdditionalMethodProcessing((METHOD) method, currentClassInfo, currentMethodInfo, getAnnotationStore()); if (resourceMethodCallback != null) { resourceMethodCallback.accept( - new ResourceMethodCallbackData(basicResourceClassInfo, actualEndpointInfo, currentMethodInfo, method)); + new ResourceMethodCallbackEntry(this, index, basicResourceClassInfo, actualEndpointInfo, + currentMethodInfo, + method)); } return method; } catch (Exception e) { @@ -923,6 +925,10 @@ protected void handleAdditionalMethodProcessing(METHOD method, ClassInfo current } + public boolean additionalRegisterClassForReflectionCheck(ResourceMethodCallbackEntry entry) { + return false; + } + protected abstract InjectableBean scanInjectableBean(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, Map existingConverters, @@ -1581,7 +1587,7 @@ public static abstract class Builder, B private AdditionalWriters additionalWriters; private boolean hasRuntimeConverters; private Map> classLevelExceptionMappers; - private Consumer resourceMethodCallback; + private Consumer resourceMethodCallback; private Collection annotationsTransformers; private ApplicationScanningResult applicationScanningResult; private final Set contextTypes = new HashSet<>(DEFAULT_CONTEXT_TYPES); @@ -1689,7 +1695,7 @@ public B setClassLevelExceptionMappers(Map> classLe return (B) this; } - public B setResourceMethodCallback(Consumer resourceMethodCallback) { + public B setResourceMethodCallback(Consumer resourceMethodCallback) { this.resourceMethodCallback = resourceMethodCallback; return (B) this; } @@ -1761,14 +1767,22 @@ public String getStreamElementType() { } } - public static class ResourceMethodCallbackData { + @SuppressWarnings("rawtypes") + public static class ResourceMethodCallbackEntry { + + private final EndpointIndexer indexer; + private final IndexView index; private final BasicResourceClassInfo basicResourceClassInfo; private final ClassInfo actualEndpointInfo; private final MethodInfo methodInfo; private final ResourceMethod resourceMethod; - public ResourceMethodCallbackData(BasicResourceClassInfo basicResourceClassInfo, ClassInfo actualEndpointInfo, + public ResourceMethodCallbackEntry(EndpointIndexer indexer, IndexView index, + BasicResourceClassInfo basicResourceClassInfo, + ClassInfo actualEndpointInfo, MethodInfo methodInfo, ResourceMethod resourceMethod) { + this.indexer = indexer; + this.index = index; this.basicResourceClassInfo = basicResourceClassInfo; this.methodInfo = methodInfo; this.actualEndpointInfo = actualEndpointInfo; @@ -1790,6 +1804,10 @@ public ClassInfo getActualEndpointInfo() { public ResourceMethod getResourceMethod() { return resourceMethod; } + + public boolean additionalRegisterClassForReflectionCheck() { + return indexer.additionalRegisterClassForReflectionCheck(this); + } } public static class DeclaredTypes { diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java index 2b757040a0e116..5598c9ef650875 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java @@ -153,7 +153,6 @@ public static SerializerScanningResult scanForSerializers(IndexView index, runtimeType = RuntimeType.SERVER; } List mediaTypeStrings = Collections.emptyList(); - String readerClassName = readerClass.name().toString(); AnnotationInstance consumesAnnotation = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSUMES); if (consumesAnnotation != null) { mediaTypeStrings = Arrays.asList(consumesAnnotation.value().asStringArray()); @@ -167,7 +166,7 @@ public static SerializerScanningResult scanForSerializers(IndexView index, if (priorityInstance != null) { priority = priorityInstance.value().asInt(); } - readerList.add(new ScannedSerializer(readerClassName, + readerList.add(new ScannedSerializer(readerClass, typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority)); } } @@ -192,7 +191,6 @@ public static SerializerScanningResult scanForSerializers(IndexView index, List typeParameters = JandexUtil.resolveTypeParameters(writerClass.name(), ResteasyReactiveDotNames.MESSAGE_BODY_WRITER, index); - String writerClassName = writerClass.name().toString(); AnnotationInstance constrainedToInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); if (constrainedToInstance != null) { runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); @@ -202,7 +200,7 @@ public static SerializerScanningResult scanForSerializers(IndexView index, if (priorityInstance != null) { priority = priorityInstance.value().asInt(); } - writerList.add(new ScannedSerializer(writerClassName, + writerList.add(new ScannedSerializer(writerClass, typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority)); } } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java index 900513163de664..9df827b1531dc1 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java @@ -5,8 +5,11 @@ import jakarta.ws.rs.Priorities; import jakarta.ws.rs.RuntimeType; +import org.jboss.jandex.ClassInfo; + public class ScannedSerializer { + private final ClassInfo classInfo; private final String className; private final String handledClassName; private final List mediaTypeStrings; @@ -14,12 +17,23 @@ public class ScannedSerializer { private final boolean builtin; private final Integer priority; + public ScannedSerializer(ClassInfo classInfo, String handledClassName, List mediaTypeStrings) { + this(classInfo, handledClassName, mediaTypeStrings, null, true, Priorities.USER); + } + + // used only for testing public ScannedSerializer(String className, String handledClassName, List mediaTypeStrings) { - this(className, handledClassName, mediaTypeStrings, null, true, Priorities.USER); + this(null, className, handledClassName, mediaTypeStrings, null, true, Priorities.USER); } - public ScannedSerializer(String className, String handledClassName, List mediaTypeStrings, + public ScannedSerializer(ClassInfo classInfo, String handledClassName, List mediaTypeStrings, RuntimeType runtimeType, boolean builtin, Integer priority) { + this(classInfo, classInfo.name().toString(), handledClassName, mediaTypeStrings, runtimeType, builtin, priority); + } + + private ScannedSerializer(ClassInfo classInfo, String className, String handledClassName, List mediaTypeStrings, + RuntimeType runtimeType, boolean builtin, Integer priority) { + this.classInfo = classInfo; this.className = className; this.handledClassName = handledClassName; this.mediaTypeStrings = mediaTypeStrings; @@ -28,6 +42,12 @@ public ScannedSerializer(String className, String handledClassName, List this.priority = priority; } + // used only for tests + + public ClassInfo getClassInfo() { + return classInfo; + } + public String getClassName() { return className; } diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/InputResource.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/InputResource.java new file mode 100644 index 00000000000000..dcaf7d6677d2f9 --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/InputResource.java @@ -0,0 +1,18 @@ +package io.quarkus.it.envers; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("input") +public class InputResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) + public String in(Message message) { + return message.getData(); + } +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message.java new file mode 100644 index 00000000000000..666d59e77760be --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message.java @@ -0,0 +1,21 @@ +package io.quarkus.it.envers; + +public class Message { + + private String data; + + public Message() { + } + + public Message(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java new file mode 100644 index 00000000000000..f09fbac07220ef --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java @@ -0,0 +1,45 @@ +package io.quarkus.it.envers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; + +@Provider +@Consumes(MediaType.WILDCARD) +@Produces(MediaType.WILDCARD) +public class MessageProvider implements MessageBodyReader, MessageBodyWriter { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Message.class.isAssignableFrom(type); + } + + @Override + public Message readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + return new Message("in"); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Message.class.isAssignableFrom(type); + } + + @Override + public void writeTo(Message event, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { + entityStream.write("{\"data\": \"out\"}".getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java new file mode 100644 index 00000000000000..a3882b22dbc414 --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java @@ -0,0 +1,16 @@ +package io.quarkus.it.envers; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("output") +public class OutputResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Message out() { + return new Message("test"); + } +} diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceIT.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceIT.java new file mode 100644 index 00000000000000..20207a0df11b36 --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.envers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class InputResourceIT extends InputResourceTest { +} diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceTest.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceTest.java new file mode 100644 index 00000000000000..bf543e476363d2 --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/InputResourceTest.java @@ -0,0 +1,22 @@ +package io.quarkus.it.envers; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +class InputResourceTest { + + @Test + void test() { + given().contentType(ContentType.JSON).accept(ContentType.TEXT) + .when().post("/jpa-envers-test/input") + .then() + .statusCode(200) + .body(equalTo("in")); + } +} diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceIT.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceIT.java new file mode 100644 index 00000000000000..cf03bae458b4ac --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.envers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class OutputResourceIT extends OutputResourceTest { +} diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java new file mode 100644 index 00000000000000..a1a5d6efe039bf --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java @@ -0,0 +1,23 @@ +package io.quarkus.it.envers; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +class OutputResourceTest { + + @Test + void test() { + given().accept(ContentType.JSON) + .when() + .get("/jpa-envers-test/output") + .then() + .statusCode(200) + .body("data", equalTo("out")); + } +} From 6fd9fd914978e62e3f747093a00fdd30602cf3f9 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 20 Apr 2023 11:35:38 -0300 Subject: [PATCH 100/333] Remove maven-compiler-plugin from dependabot ignore list --- .../code/quarkiverse/java/.github/dependabot.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/dependabot.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/dependabot.yml index 4b11d34c5ea029..5b063201e48dbb 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/dependabot.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/.github/dependabot.yml @@ -1,13 +1,11 @@ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - - package-ecosystem: "maven" - directory: "/" + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests schedule: interval: "daily" - ignore: - - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" From cc1d756f7cdc66ea49c84deef373bd62f1293bd7 Mon Sep 17 00:00:00 2001 From: Moritz Heine Date: Wed, 19 Apr 2023 13:39:53 +0200 Subject: [PATCH 101/333] Fixed Java migrations from different packages. Fix #32654 --- .../java/db/migration/V1_0_1__Update.java | 18 ++++++++ .../java/db/migration/V1_0_2__Update.java | 39 ++++++++++++++++ ...ndMigrateAtStartWithJavaMigrationTest.java | 20 +++------ ...FlywayExtensionFilesystemResourceTest.java | 44 +------------------ ...ean-and-migrate-at-start-config.properties | 1 + ...t-start-with-fs-resource-config.properties | 2 +- .../runtime/QuarkusPathLocationScanner.java | 12 ++++- 7 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java create mode 100644 extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java diff --git a/extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java new file mode 100644 index 00000000000000..38855b7bd45545 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java @@ -0,0 +1,18 @@ +package db.migration; + +import java.sql.Statement; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +/** + * Migration class for some testcases. + */ +public class V1_0_1__Update extends BaseJavaMigration { + @Override + public void migrate(Context context) throws Exception { + try (Statement statement = context.getConnection().createStatement()) { + statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1001, 'test')"); + } + } +} diff --git a/extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java new file mode 100644 index 00000000000000..3181a849404d5f --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java @@ -0,0 +1,39 @@ +package db.migration; + +import java.sql.Statement; + +import org.flywaydb.core.api.MigrationVersion; +import org.flywaydb.core.api.migration.Context; +import org.flywaydb.core.api.migration.JavaMigration; + +/** + * Migration class for some testcases. + */ +public class V1_0_2__Update implements JavaMigration { + @Override + public MigrationVersion getVersion() { + return MigrationVersion.fromVersion("1.0.2"); + } + + @Override + public String getDescription() { + return getClass().getSimpleName(); + } + + @Override + public Integer getChecksum() { + return null; + } + + @Override + public boolean canExecuteInTransaction() { + return true; + } + + @Override + public void migrate(Context context) throws Exception { + try (Statement statement = context.getConnection().createStatement()) { + statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1002, 'test')"); + } + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java index 8b1f25d96663ce..6c4acbfcf162ce 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java @@ -12,13 +12,14 @@ import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationVersion; -import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.Context; import org.flywaydb.core.api.migration.JavaMigration; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import db.migration.V1_0_1__Update; +import db.migration.V1_0_2__Update; import io.agroal.api.AgroalDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -33,7 +34,7 @@ public class FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(V1_0_1__Update.class, V1_0_2__Update.class) + .addClasses(V1_0_1__Update.class, V1_0_2__Update.class, V9_9_9__Update.class) .addAsResource("db/migration/V1.0.0__Quarkus.sql") .addAsResource("clean-and-migrate-at-start-config.properties", "application.properties")); @@ -53,19 +54,10 @@ public void testFlywayConfigInjection() throws SQLException { assertEquals("1.0.2", currentVersion, "Expected to be 1.0.2 as there is a SQL and two Java migration scripts"); } - public static class V1_0_1__Update extends BaseJavaMigration { - @Override - public void migrate(Context context) throws Exception { - try (Statement statement = context.getConnection().createStatement()) { - statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1001, 'test')"); - } - } - } - - public static class V1_0_2__Update implements JavaMigration { + public static class V9_9_9__Update implements JavaMigration { @Override public MigrationVersion getVersion() { - return MigrationVersion.fromVersion("1.0.2"); + return MigrationVersion.fromVersion("9.9.9"); } @Override @@ -86,7 +78,7 @@ public boolean canExecuteInTransaction() { @Override public void migrate(Context context) throws Exception { try (Statement statement = context.getConnection().createStatement()) { - statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1002, 'test')"); + statement.executeUpdate("INSERT INTO quarked_flyway VALUES (9999, 'should-not-be-added')"); } } } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java index 7d33b3491df1f0..b0ba671cf0e989 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java @@ -12,15 +12,13 @@ import jakarta.inject.Inject; import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.MigrationVersion; -import org.flywaydb.core.api.migration.BaseJavaMigration; -import org.flywaydb.core.api.migration.Context; -import org.flywaydb.core.api.migration.JavaMigration; import org.h2.jdbc.JdbcSQLSyntaxErrorException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import db.migration.V1_0_1__Update; +import db.migration.V1_0_2__Update; import io.agroal.api.AgroalDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -59,42 +57,4 @@ public void testFlywayConfigInjection() throws SQLException { assertEquals("1.0.3", currentVersion, "Expected to be 1.0.3 as there is a SQL and two Java migration scripts"); } - public static class V1_0_1__Update extends BaseJavaMigration { - @Override - public void migrate(Context context) throws Exception { - try (Statement statement = context.getConnection().createStatement()) { - statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1001, 'test')"); - } - } - } - - public static class V1_0_2__Update implements JavaMigration { - @Override - public MigrationVersion getVersion() { - return MigrationVersion.fromVersion("1.0.2"); - } - - @Override - public String getDescription() { - return getClass().getSimpleName(); - } - - @Override - public Integer getChecksum() { - return null; - } - - @Override - public boolean canExecuteInTransaction() { - return true; - } - - @Override - public void migrate(Context context) throws Exception { - try (Statement statement = context.getConnection().createStatement()) { - statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1002, 'test')"); - } - } - } - } diff --git a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties index ef6fab20a9c653..26c07c53ca008b 100644 --- a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties +++ b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties @@ -10,3 +10,4 @@ quarkus.flyway.table=test_flyway_history quarkus.flyway.baseline-on-migrate=false quarkus.flyway.baseline-version=0.0.1 quarkus.flyway.baseline-description=Initial description for test +quarkus.flyway.users.locations=db/migration \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties index 352ca17e521d9b..65afb9365aa52f 100644 --- a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties +++ b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties @@ -10,4 +10,4 @@ quarkus.flyway.table=test_flyway_history quarkus.flyway.baseline-on-migrate=false quarkus.flyway.baseline-version=0.0.1 quarkus.flyway.baseline-description=Initial description for test -quarkus.flyway.locations=filesystem:src/test/resources/db/migration +quarkus.flyway.locations=filesystem:src/test/resources/db/migration,classpath:db/migration diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java index 509145744666be..e3df6ac28286f7 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java @@ -28,17 +28,20 @@ public final class QuarkusPathLocationScanner implements ResourceAndClassScanner private static Map> applicationCallbackClasses = Collections.emptyMap(); // the set default to aid unit tests private final Collection scannedResources; + private final Collection> scannedMigrationClasses; public QuarkusPathLocationScanner(Collection locations) { LOGGER.debugv("Locations: {0}", locations); this.scannedResources = new ArrayList<>(); + this.scannedMigrationClasses = new ArrayList<>(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); FileSystemScanner fileSystemScanner = null; for (String migrationFile : applicationMigrationFiles) { if (isClassPathResource(locations, migrationFile)) { LOGGER.debugf("Loading %s", migrationFile); + scannedResources.add(new ClassPathResource(null, migrationFile, classLoader, StandardCharsets.UTF_8)); } else if (migrationFile.startsWith(Location.FILESYSTEM_PREFIX)) { if (fileSystemScanner == null) { @@ -51,6 +54,13 @@ public QuarkusPathLocationScanner(Collection locations) { } } + // Filter the provided migration classes to match the provided locations. + for (Class migrationClass : applicationMigrationClasses) { + if (isClassPathResource(locations, migrationClass.getCanonicalName().replace('.', '/'))) { + LOGGER.debugf("Loading migration class %s", migrationClass.getCanonicalName()); + scannedMigrationClasses.add(migrationClass); + } + } } public static void setApplicationCallbackClasses(Map> callbackClasses) { @@ -96,7 +106,7 @@ private boolean isClassPathResource(Collection locations, String migra */ @Override public Collection> scanForClasses() { - return applicationMigrationClasses; + return scannedMigrationClasses; } public static void setApplicationMigrationFiles(Collection applicationMigrationFiles) { From 1f270022b3a9aa634d26d3b84aa05a19e445e568 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 20 Apr 2023 17:05:24 +0200 Subject: [PATCH 102/333] ArC - support interception of static methods with repeatable bindings - related to #32786 --- .../InterceptorResolverBuildItem.java | 17 +++- .../InterceptedStaticMethodsProcessor.java | 15 ++-- .../RepeatingBindingStaticMethodTest.java | 85 +++++++++++++++++++ .../quarkus/arc/processor/BeanDeployment.java | 8 +- 4 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java index b9d0770a10f588..abfd5c917a9c65 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java @@ -1,9 +1,11 @@ package io.quarkus.arc.deployment; +import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; +import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -17,13 +19,14 @@ public final class InterceptorResolverBuildItem extends SimpleBuildItem { private final InterceptorResolver resolver; - private final Set interceptorBindings; + private final BeanDeployment beanDeployment; InterceptorResolverBuildItem(BeanDeployment beanDeployment) { this.resolver = beanDeployment.getInterceptorResolver(); this.interceptorBindings = Collections.unmodifiableSet( - beanDeployment.getInterceptorBindings().stream().map(ClassInfo::name).collect(Collectors.toSet())); + beanDeployment.getInterceptorBindings().stream().map(ClassInfo::name).collect(Collectors.toUnmodifiableSet())); + this.beanDeployment = beanDeployment; } public InterceptorResolver get() { @@ -38,4 +41,14 @@ public Set getInterceptorBindings() { return interceptorBindings; } + /** + * + * @param annotation + * @return the collection of interceptor bindings + * @see BeanDeployment#extractInterceptorBindings(AnnotationInstance) + */ + public Collection extractInterceptorBindings(AnnotationInstance annotation) { + return beanDeployment.extractInterceptorBindings(annotation); + } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java index 568036a05db263..6e5f32ac07e652 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java @@ -87,9 +87,7 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, BuildProducer interceptedStaticMethods, InterceptorResolverBuildItem interceptorResolver, TransformedAnnotationsBuildItem transformedAnnotations, BuildProducer unremovableBeans) { - // In this step we collect all intercepted static methods, i.e. static methods annotated with interceptor bindings - Set interceptorBindings = interceptorResolver.getInterceptorBindings(); for (ClassInfo clazz : beanArchiveIndex.getIndex().getKnownClasses()) { for (MethodInfo method : clazz.methods()) { @@ -107,12 +105,15 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, // Only method-level bindings are considered due to backwards compatibility Set methodLevelBindings = null; for (AnnotationInstance annotationInstance : annotations) { - if (annotationInstance.target().kind() == Kind.METHOD - && interceptorBindings.contains(annotationInstance.name())) { - if (methodLevelBindings == null) { - methodLevelBindings = new HashSet<>(); + if (annotationInstance.target().kind() == Kind.METHOD) { + Collection bindings = interceptorResolver + .extractInterceptorBindings(annotationInstance); + if (!bindings.isEmpty()) { + if (methodLevelBindings == null) { + methodLevelBindings = new HashSet<>(); + } + methodLevelBindings.addAll(bindings); } - methodLevelBindings.add(annotationInstance); } } if (methodLevelBindings == null || methodLevelBindings.isEmpty()) { diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java new file mode 100644 index 00000000000000..54219d0cf4a2e3 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java @@ -0,0 +1,85 @@ +package io.quarkus.arc.test.interceptor.staticmethods; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class RepeatingBindingStaticMethodTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(InterceptMe.class, SimpleBean.class, InterceptMeAlpha.class, InterceptMeBravo.class)); + + @Test + public void testInterceptor() { + assertEquals("a/b/PONG/b/a", SimpleBean.ping("pong")); + } + + public static class SimpleBean { + + @InterceptMe("alpha") + @InterceptMe("bravo") + public static String ping(String val) { + return val.toUpperCase(); + } + } + + @Priority(1) + @Interceptor + @InterceptMe("alpha") + static class InterceptMeAlpha { + + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + return "a/" + ctx.proceed() + "/a"; + } + + } + + @Priority(2) + @Interceptor + @InterceptMe("bravo") + static class InterceptMeBravo { + + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + return "b/" + ctx.proceed() + "/b"; + } + + } + + @Repeatable(InterceptMe.List.class) + @InterceptorBinding + @Target({ TYPE, METHOD, CONSTRUCTOR }) + @Retention(RUNTIME) + @interface InterceptMe { + + String value(); + + @Target({ TYPE, METHOD, CONSTRUCTOR }) + @Retention(RUNTIME) + @interface List { + InterceptMe[] value(); + } + + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 215ce1e4c4e089..7e9457dd05743d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -511,6 +511,12 @@ Map> getQualifierNonbindingMembers() { return qualifierNonbindingMembers; } + /** + * + * @return the collection of interceptor bindings; the container annotations of repeating interceptor binding are not + * included + * @see #extractInterceptorBindings(AnnotationInstance) + */ public Collection getInterceptorBindings() { return Collections.unmodifiableCollection(interceptorBindings.values()); } @@ -614,7 +620,7 @@ Collection extractQualifiers(AnnotationInstance annotation) * @param annotation annotation to be inspected * @return a collection of interceptor bindings or an empty collection */ - Collection extractInterceptorBindings(AnnotationInstance annotation) { + public Collection extractInterceptorBindings(AnnotationInstance annotation) { return extractAnnotations(annotation, interceptorBindings, repeatingInterceptorBindingAnnotations); } From 46134457435ce3c6e80794c77c8f91997b7e37eb Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Thu, 20 Apr 2023 13:55:35 +0300 Subject: [PATCH 103/333] Add back link to gradle release checksums --- .../gradle/gradle/wrapper/gradle-wrapper.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties index 6ee7ce821e01c7..d5e7b2ac5e4170 100644 --- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +# https://gradle.org/release-checksums/ distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip networkTimeout=10000 From 41f54709ccf1e676a8ac859be28a720bf31adea0 Mon Sep 17 00:00:00 2001 From: Konstantin Gribov Date: Thu, 20 Apr 2023 19:04:30 +0300 Subject: [PATCH 104/333] Add small note to gradle build files in integration tests Signed-off-by: Konstantin Gribov --- integration-tests/gradle/build.gradle.kts | 3 +++ integration-tests/gradle/settings.gradle.kts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/integration-tests/gradle/build.gradle.kts b/integration-tests/gradle/build.gradle.kts index 6ffe25f29b6e04..8ccc008207e8da 100644 --- a/integration-tests/gradle/build.gradle.kts +++ b/integration-tests/gradle/build.gradle.kts @@ -1,3 +1,6 @@ +// This file doesn't configure integration tests and kept just to update gradle wrapper. +// To run tests use `./mvnw -f integration-tests/gradle test` from project root directory. + tasks.wrapper { // not sure if it's still required: IntelliJ works fine with `-bin` distribution // after indexing Gradle API jars diff --git a/integration-tests/gradle/settings.gradle.kts b/integration-tests/gradle/settings.gradle.kts index df5e9c5ae2828c..47486ce15dec74 100644 --- a/integration-tests/gradle/settings.gradle.kts +++ b/integration-tests/gradle/settings.gradle.kts @@ -1 +1,4 @@ +// This file is required to run gradle from this directory to simplify wrapper +// update since gradle requires `settings.gradle[.kts]` to be present. + rootProject.name = "gradle-plugins-integration-tests" From 723e0596d6fbffc162af309ed71e410ebb2823a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Thu, 20 Apr 2023 21:13:16 +0200 Subject: [PATCH 105/333] Fix K8 service binding with Reactive datasource --- .../runtime/DatasourceServiceBindingConfigSourceFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java index c12857249237c2..c4ac6cf7c98e3c 100644 --- a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java +++ b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java @@ -81,7 +81,7 @@ protected String formatUrl(String urlFormat, String type, String host, String da private boolean configureUrlPropertyUsingKey(Map properties, String key) { String value = serviceBinding.getProperties().get(key); - if (value == null) { + if (value == null || prefix == null) { return false; } else if (value.startsWith(prefix)) { properties.put(urlPropertyName, value); @@ -109,7 +109,7 @@ public Reactive() { } public Reactive(String urlFormat) { - super("reactive", "quarkus.datasource.reactive.url", "", urlFormat); + super("reactive", "quarkus.datasource.reactive.url", null, urlFormat); } } } From b12088147b80748c5388b00a9a410bb26658e1c0 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 21 Apr 2023 00:43:36 +0100 Subject: [PATCH 106/333] Report OTel config unknowns only when fallback properties used --- .../OpenTelemetryLegacyConfigurationTest.java | 7 ++- .../OTelFallbackConfigSourceInterceptor.java | 53 ++++++++++++++----- ...ConfigRelocateConfigSourceInterceptor.java | 0 3 files changed, 44 insertions(+), 16 deletions(-) delete mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OtelConfigRelocateConfigSourceInterceptor.java diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java index 79cd04d59c38b4..6ae06be35007c4 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Arrays; +import java.util.List; import jakarta.inject.Inject; @@ -24,6 +24,7 @@ class OpenTelemetryLegacyConfigurationTest { .overrideConfigKey("quarkus.opentelemetry.enabled", "false") .overrideConfigKey("quarkus.opentelemetry.tracer.enabled", "false") .overrideConfigKey("quarkus.opentelemetry.propagators", "tracecontext") + .overrideConfigKey("quarkus.opentelemetry.tracer.resource-attributes", "service.name=authservice") .overrideConfigKey("quarkus.opentelemetry.tracer.suppress-non-application-uris", "false") .overrideConfigKey("quarkus.opentelemetry.tracer.include-static-resources", "true") .overrideConfigKey("quarkus.opentelemetry.tracer.sampler", "off") @@ -46,7 +47,9 @@ void config() { assertEquals(FALSE, oTelBuildConfig.enabled()); assertTrue(oTelBuildConfig.traces().enabled().isPresent()); assertEquals(FALSE, oTelBuildConfig.traces().enabled().get()); - assertEquals(Arrays.asList("tracecontext"), oTelBuildConfig.propagators()); // will not include the default baggagge + assertEquals(List.of("tracecontext"), oTelBuildConfig.propagators()); // will not include the default baggagge + assertTrue(oTelRuntimeConfig.resourceAttributes().isPresent()); + assertEquals("service.name=authservice", oTelRuntimeConfig.resourceAttributes().get().get(0)); assertEquals(FALSE, oTelRuntimeConfig.traces().suppressNonApplicationUris()); assertEquals(TRUE, oTelRuntimeConfig.traces().includeStaticResources()); assertEquals("always_off", oTelBuildConfig.traces().sampler()); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java index ce1ea16693839a..9d97991d4ca184 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java @@ -1,5 +1,6 @@ package io.quarkus.opentelemetry.runtime.config; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -15,21 +16,27 @@ @Priority(Priorities.LIBRARY + 300 + 5) public class OTelFallbackConfigSourceInterceptor extends FallbackConfigSourceInterceptor { + private final static Map FALLBACKS = new HashMap<>(); private final static LegacySamplerNameConverter LEGACY_SAMPLER_NAME_CONVERTER = new LegacySamplerNameConverter(); + static { + FALLBACKS.put("quarkus.otel.enabled", "quarkus.opentelemetry.enabled"); + FALLBACKS.put("quarkus.otel.traces.enabled", "quarkus.opentelemetry.tracer.enabled"); + FALLBACKS.put("quarkus.otel.propagators", "quarkus.opentelemetry.propagators"); + FALLBACKS.put("quarkus.otel.resource.attributes", "quarkus.opentelemetry.tracer.resource-attributes"); + FALLBACKS.put("quarkus.otel.traces.suppress-non-application-uris", + "quarkus.opentelemetry.tracer.suppress-non-application-uris"); + FALLBACKS.put("quarkus.otel.traces.include-static-resources", "quarkus.opentelemetry.tracer.include-static-resources"); + FALLBACKS.put("quarkus.otel.traces.sampler", "quarkus.opentelemetry.tracer.sampler"); + FALLBACKS.put("quarkus.otel.traces.sampler.arg", "quarkus.opentelemetry.tracer.sampler.ratio"); + FALLBACKS.put("quarkus.otel.exporter.otlp.enabled", "quarkus.opentelemetry.tracer.exporter.otlp.enabled"); + FALLBACKS.put("quarkus.otel.exporter.otlp.traces.legacy-endpoint", + "quarkus.opentelemetry.tracer.exporter.otlp.endpoint"); + FALLBACKS.put("quarkus.otel.exporter.otlp.traces.headers", "quarkus.opentelemetry.tracer.exporter.otlp.headers"); + } + public OTelFallbackConfigSourceInterceptor() { - super(Map.of( - "quarkus.otel.enabled", "quarkus.opentelemetry.enabled", - "quarkus.otel.traces.enabled", "quarkus.opentelemetry.tracer.enabled", - "quarkus.otel.propagators", "quarkus.opentelemetry.propagators", - "quarkus.otel.traces.suppress-non-application-uris", - "quarkus.opentelemetry.tracer.suppress-non-application-uris", - "quarkus.otel.traces.include-static-resources", "quarkus.opentelemetry.tracer.include-static-resources", - "quarkus.otel.traces.sampler", "quarkus.opentelemetry.tracer.sampler", - "quarkus.otel.traces.sampler.arg", "quarkus.opentelemetry.tracer.sampler.ratio", - "quarkus.otel.exporter.otlp.enabled", "quarkus.opentelemetry.tracer.exporter.otlp.enabled", - "quarkus.otel.exporter.otlp.traces.headers", "quarkus.opentelemetry.tracer.exporter.otlp.headers", - "quarkus.otel.exporter.otlp.traces.legacy-endpoint", "quarkus.opentelemetry.tracer.exporter.otlp.endpoint")); + super(FALLBACKS); } @Override @@ -44,10 +51,28 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final @Override public Iterator iterateNames(final ConfigSourceInterceptorContext context) { Set names = new HashSet<>(); - Iterator namesIterator = super.iterateNames(context); + Iterator namesIterator = context.iterateNames(); while (namesIterator.hasNext()) { - names.add(namesIterator.next()); + String name = namesIterator.next(); + String fallback = FALLBACKS.get(name); + // We only include the used property, so if it is a fallback (not mapped), it will be reported as unknown + if (fallback != null) { + ConfigValue nameValue = context.proceed(name); + ConfigValue fallbackValue = context.proceed(fallback); + if (nameValue == null) { + names.add(fallback); + } else if (fallbackValue == null) { + names.add(name); + } else if (nameValue.getConfigSourceOrdinal() >= fallbackValue.getConfigSourceOrdinal()) { + names.add(name); + } else { + names.add(fallback); + } + } else { + names.add(name); + } } + // TODO - Required because the defaults ConfigSource for mappings does not provide configuration names. names.add("quarkus.otel.enabled"); names.add("quarkus.otel.metrics.exporter"); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OtelConfigRelocateConfigSourceInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OtelConfigRelocateConfigSourceInterceptor.java deleted file mode 100644 index e69de29bb2d1d6..00000000000000 From e23f08cf8b2b323a494fbe0ee5becf5a9aa0eae9 Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Thu, 20 Apr 2023 21:23:15 -0400 Subject: [PATCH 107/333] Create update guide --- .../main/asciidoc/update-to-quarkus-3.adoc | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/src/main/asciidoc/update-to-quarkus-3.adoc diff --git a/docs/src/main/asciidoc/update-to-quarkus-3.adoc b/docs/src/main/asciidoc/update-to-quarkus-3.adoc new file mode 100644 index 00000000000000..5fa1b716967a16 --- /dev/null +++ b/docs/src/main/asciidoc/update-to-quarkus-3.adoc @@ -0,0 +1,55 @@ +//// +This document is maintained in the main Quarkus repository, and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// +[id="update-projects-to-quarkus-3-automatically-howto"] += Update projects to Quarkus 3.x automatically +include::_attributes.adoc[] +:categories: core +:extension-status: "experimental" +:summary: Update projects from Quarkus 2.x to Quarkus 3.x. + +include::{includes}/extension-status.adoc[] + +You can update your projects from Quarkus 2.x to Quarkus 3.x by running an update command. + +The update command primarily uses OpenRewrite recipes to automate updating most of your project's dependencies, source code, and documentation. These recipes cover many but not all of the items described in the link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0[Migration Guide 3.0]. + +After updating the project, if you do not find all the updates you expect, there are two possible reasons: +- The recipe might not cover an item in your project. +- Your project might use an extension that does not support Quarkus 3 yet. + +In either case, https://github.com/quarkusio/quarkus/issues[let us know by filing an issue] so we can improve the update command. + +[IMPORTANT] +==== +If your project uses Hibernate ORM or Hibernate Reactive, read through the link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0:-Hibernate-ORM-5-to-6-migration[Hibernate ORM 5 to 6 migration] quick reference. +The following update command only covers a few items in this quick reference. +==== + +== Prerequisites + +:prerequisites-time: 30 minutes +include::{includes}/prerequisites.adoc[] + +== Procedure + +. Use your version control system to create a working branch for your project or projects. +. Optional: To use the Quarkus CLI in the next step, link:https://quarkus.io/guides/cli-tooling#installing-the-cli[install version 3 of the Quarkus CLI]. Use `quarkus -v` to verify the version number. +. Update the project: ++ +[source, bash, subs=attributes+, role="primary asciidoc-tabs-sync-cli"] +.CLI +---- +quarkus update --stream=3.0 +---- ++ +[source, bash, subs=attributes+, role="secondary asciidoc-tabs-sync-maven"] +.Maven +---- +./mvnw io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:update -N -Dstream=3.0 +---- +. Review the output from the update command for potential instructions and, if needed, perform the indicated tasks. +. Review all the changes using a diff tool. +. Review link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0[the migration guide] for any items not covered by the upgrade command and perform additional steps, if needed. +. Verify that the project builds without errors and that the application passes all tests and works as expected before releasing it to production. From db5b03ac098f857988475dce03d10dc8da7e6897 Mon Sep 17 00:00:00 2001 From: brunobat Date: Tue, 21 Mar 2023 11:52:31 +0000 Subject: [PATCH 108/333] Bump OTel to 1.25 --- bom/application/pom.xml | 16 +++---- .../VertxClientOpenTelemetryTest.java | 6 +++ .../VertxOpenTelemetryTest.java | 9 ++-- ...otlp_internal_grpc_ManagedChannelUtil.java | 14 ++----- .../restclient/OpenTelemetryClientFilter.java | 25 ++++++++++- .../EventBusInstrumenterVertxTracer.java | 15 ------- .../vertx/HttpInstrumenterVertxTracer.java | 25 ++++++++++- .../opentelemetry/OpenTelemetryTestCase.java | 3 +- .../it/opentelemetry/spi/OTelSpiTest.java | 3 +- .../it/opentelemetry/OpenTelemetryTest.java | 42 +++++++------------ .../io/quarkus/it/rest/client/BasicTest.java | 4 +- 11 files changed, 85 insertions(+), 77 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 30c634dd65d85c..24c38b9606884f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -36,8 +36,8 @@ 0.2.4 0.1.15 0.1.5 - 1.23.1 - 1.23.0-alpha + 1.25.0 + 1.25.0-alpha 1.8.1 5.0.2.Final 1.10.5 @@ -68,7 +68,7 @@ 1.0.13 3.0.0 3.3.0 - 4.4.0 + 4.5.0 2.1.0 2.1.1 2.1.1 @@ -3269,7 +3269,7 @@ router ${vaadin-router.version} runtime - + org.mvnpm path-to-regexp @@ -3645,7 +3645,7 @@ ${dedupe-mixin.version} runtime - + org.mvnpm @@ -3689,7 +3689,7 @@ ${lit-state.version} runtime - + org.mvnpm @@ -3697,7 +3697,7 @@ ${echarts.version} runtime - + org.mvnpm.at.vanillawc @@ -3705,7 +3705,7 @@ ${wc-codemirror.version} runtime - + org.mvnpm diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java index f99592e6f7706b..97419bbf5426c1 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java @@ -9,6 +9,8 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_HOST_NAME; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_HOST_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_PORT; import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; import static java.net.HttpURLConnection.HTTP_OK; import static java.util.stream.Collectors.toSet; @@ -79,6 +81,8 @@ void client() throws Exception { assertEquals(HTTP_OK, client.getAttributes().get(HTTP_STATUS_CODE)); assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_METHOD)); assertEquals(uri.toString() + "hello", client.getAttributes().get(HTTP_URL)); + assertEquals(uri.getHost(), client.getAttributes().get(NET_PEER_NAME)); + assertEquals(uri.getPort(), client.getAttributes().get(NET_PEER_PORT)); SpanData server = getSpanByKindAndParentId(spans, SERVER, client.getSpanId()); assertEquals(SERVER, server.getKind()); @@ -111,6 +115,8 @@ void path() throws Exception { assertEquals(HTTP_OK, client.getAttributes().get(HTTP_STATUS_CODE)); assertEquals(HttpMethod.GET, client.getAttributes().get(HTTP_METHOD)); assertEquals(uri.toString() + "hello/naruto", client.getAttributes().get(HTTP_URL)); + assertEquals(uri.getHost(), client.getAttributes().get(NET_PEER_NAME)); + assertEquals(uri.getPort(), client.getAttributes().get(NET_PEER_PORT)); SpanData server = getSpanByKindAndParentId(spans, SERVER, client.getSpanId()); assertEquals(SERVER, server.getKind()); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryTest.java index 1676805cda69d6..34425de9026145 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryTest.java @@ -4,15 +4,14 @@ import static io.opentelemetry.api.trace.SpanKind.INTERNAL; import static io.opentelemetry.api.trace.SpanKind.SERVER; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_CLIENT_IP; -import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_FLAVOR; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_ROUTE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_SCHEME; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; -import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_USER_AGENT; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_HOST_NAME; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_HOST_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.USER_AGENT_ORIGINAL; import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; import static io.restassured.RestAssured.given; import static io.vertx.core.http.HttpMethod.GET; @@ -90,7 +89,6 @@ void trace() throws NoSuchFieldException, IllegalAccessException, InvocationTarg SpanData server = getSpanByKindAndParentId(spans, SERVER, "0000000000000000"); assertEquals(HTTP_OK, server.getAttributes().get(HTTP_STATUS_CODE)); - assertEquals("1.1", server.getAttributes().get(HTTP_FLAVOR)); assertEquals("/tracer", server.getAttributes().get(HTTP_TARGET)); assertEquals("http", server.getAttributes().get(HTTP_SCHEME)); assertEquals("localhost", server.getAttributes().get(NET_HOST_NAME)); @@ -100,7 +98,7 @@ void trace() throws NoSuchFieldException, IllegalAccessException, InvocationTarg W3CBaggagePropagator.getInstance())); assertThat(idGenerator, instanceOf(IdGenerator.random().getClass())); assertThat(sampler.getDescription(), stringContainsInOrder("ParentBased", "AlwaysOnSampler")); - assertNotNull(server.getAttributes().get(HTTP_USER_AGENT)); + assertNotNull(server.getAttributes().get(USER_AGENT_ORIGINAL)); SpanData internal = getSpanByKindAndParentId(spans, INTERNAL, server.getSpanId()); assertEquals("io.quarkus.vertx.opentelemetry", internal.getName()); @@ -118,13 +116,12 @@ void spanNameWithoutQueryString() { final SpanData server = getSpanByKindAndParentId(spans, SERVER, "0000000000000000"); assertEquals("GET /tracer", server.getName()); assertEquals(HTTP_OK, server.getAttributes().get(HTTP_STATUS_CODE)); - assertEquals("1.1", server.getAttributes().get(HTTP_FLAVOR)); assertEquals("/tracer?id=1", server.getAttributes().get(HTTP_TARGET)); assertEquals("http", server.getAttributes().get(HTTP_SCHEME)); assertEquals("localhost", server.getAttributes().get(NET_HOST_NAME)); assertEquals("8081", server.getAttributes().get(NET_HOST_PORT).toString()); assertEquals("127.0.0.1", server.getAttributes().get(HTTP_CLIENT_IP)); - assertNotNull(server.getAttributes().get(HTTP_USER_AGENT)); + assertNotNull(server.getAttributes().get(USER_AGENT_ORIGINAL)); SpanData internal = getSpanByKindAndParentId(spans, INTERNAL, server.getSpanId()); assertEquals("io.quarkus.vertx.opentelemetry", internal.getName()); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/graal/Target_io_opentelemetry_exporter_otlp_internal_grpc_ManagedChannelUtil.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/graal/Target_io_opentelemetry_exporter_otlp_internal_grpc_ManagedChannelUtil.java index c5e632010db264..d5b554973d3012 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/graal/Target_io_opentelemetry_exporter_otlp_internal_grpc_ManagedChannelUtil.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/graal/Target_io_opentelemetry_exporter_otlp_internal_grpc_ManagedChannelUtil.java @@ -12,7 +12,6 @@ import io.grpc.ManagedChannelBuilder; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; -import io.opentelemetry.exporter.internal.TlsUtil; /** * Replace the {@code setTrustedCertificatesPem()} method in native because the upstream code supports using @@ -24,18 +23,11 @@ final class Target_io_opentelemetry_exporter_otlp_internal_grpc_ManagedChannelUt @Substitute public static void setClientKeysAndTrustedCertificatesPem( ManagedChannelBuilder managedChannelBuilder, - byte[] privateKeyPem, - byte[] certificatePem, - byte[] trustedCertificatesPem) + X509TrustManager tmf, + X509KeyManager kmf) throws SSLException { requireNonNull(managedChannelBuilder, "managedChannelBuilder"); - requireNonNull(trustedCertificatesPem, "trustedCertificatesPem"); - - X509TrustManager tmf = TlsUtil.trustManager(trustedCertificatesPem); - X509KeyManager kmf = null; - if (privateKeyPem != null && certificatePem != null) { - kmf = TlsUtil.keyManager(privateKeyPem, certificatePem); - } + requireNonNull(tmf, "X509TrustManager"); // gRPC does not abstract TLS configuration so we need to check the implementation and act // accordingly. diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/restclient/OpenTelemetryClientFilter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/restclient/OpenTelemetryClientFilter.java index 68fb3dbad454df..1a387a99ad47c0 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/restclient/OpenTelemetryClientFilter.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/restclient/OpenTelemetryClientFilter.java @@ -26,6 +26,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.quarkus.arc.Unremovable; import io.quarkus.opentelemetry.runtime.QuarkusContextStorage; @@ -60,6 +62,7 @@ public OpenTelemetryClientFilter() { @Inject public OpenTelemetryClientFilter(final OpenTelemetry openTelemetry) { ClientAttributesExtractor clientAttributesExtractor = new ClientAttributesExtractor(); + ClientNetAttributesGetter clientNetAttributesExtractor = new ClientNetAttributesGetter(); InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, @@ -68,7 +71,8 @@ public OpenTelemetryClientFilter(final OpenTelemetry openTelemetry) { this.instrumenter = builder .setSpanStatusExtractor(HttpSpanStatusExtractor.create(clientAttributesExtractor)) - .addAttributesExtractor(HttpClientAttributesExtractor.create(clientAttributesExtractor)) + .addAttributesExtractor(HttpClientAttributesExtractor.create( + clientAttributesExtractor, clientNetAttributesExtractor)) .buildClientInstrumenter(new ClientRequestContextTextMapSetter()); } @@ -178,4 +182,23 @@ public List getResponseHeader(final ClientRequestContext request, final return response.getHeaders().getOrDefault(name, emptyList()); } } + + private static class ClientNetAttributesGetter + implements NetClientAttributesGetter { + + @Override + public String getTransport(ClientRequestContext clientRequestContext, ClientResponseContext clientResponseContext) { + return SemanticAttributes.NetTransportValues.IP_TCP; + } + + @Override + public String getPeerName(ClientRequestContext clientRequestContext) { + return clientRequestContext.getUri().getHost(); + } + + @Override + public Integer getPeerPort(ClientRequestContext clientRequestContext) { + return clientRequestContext.getUri().getPort(); + } + } } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/EventBusInstrumenterVertxTracer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/EventBusInstrumenterVertxTracer.java index eb35c78cba3900..daec7431bd99a0 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/EventBusInstrumenterVertxTracer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/EventBusInstrumenterVertxTracer.java @@ -109,21 +109,6 @@ public boolean isTemporaryDestination(final Message message) { return false; } - @Override - public String getProtocol(final Message message) { - return null; - } - - @Override - public String getProtocolVersion(final Message message) { - return "4.0"; - } - - @Override - public String getUrl(final Message message) { - return null; - } - @Override public String getConversationId(final Message message) { return message.replyAddress(); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java index e75650ef412be0..254504abe1607d 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java @@ -28,7 +28,9 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.vertx.core.Context; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpHeaders; @@ -127,6 +129,7 @@ private static Instrumenter getServerInstrumenter(fin private static Instrumenter getClientInstrumenter(final OpenTelemetry openTelemetry) { ServerAttributesExtractor serverAttributesExtractor = new ServerAttributesExtractor(); ClientAttributesExtractor clientAttributesExtractor = new ClientAttributesExtractor(); + HttpClientNetAttributeGetter httpClientNetAttributeGetter = new HttpClientNetAttributeGetter(); InstrumenterBuilder clientBuilder = Instrumenter.builder( openTelemetry, @@ -135,7 +138,8 @@ private static Instrumenter getClientInstrumenter(fin return clientBuilder .setSpanStatusExtractor(HttpSpanStatusExtractor.create(serverAttributesExtractor)) - .addAttributesExtractor(HttpClientAttributesExtractor.create(clientAttributesExtractor)) + .addAttributesExtractor(HttpClientAttributesExtractor.create( + clientAttributesExtractor, httpClientNetAttributeGetter)) .buildClientInstrumenter(new HttpRequestTextMapSetter()); } @@ -295,6 +299,25 @@ public Integer getHostPort(HttpRequest httpRequest) { } } + private static class HttpClientNetAttributeGetter implements NetClientAttributesGetter { + @Override + public String getTransport(HttpRequest httpClientRequest, HttpResponse httpClientResponse) { + return SemanticAttributes.NetTransportValues.IP_TCP; + } + + @javax.annotation.Nullable + @Override + public String getPeerName(HttpRequest httpRequest) { + return httpRequest.remoteAddress().hostName(); + } + + @javax.annotation.Nullable + @Override + public Integer getPeerPort(HttpRequest httpRequest) { + return httpRequest.remoteAddress().port(); + } + } + private static class HttpRequestTextMapGetter implements TextMapGetter { @Override public Iterable keys(final HttpRequest carrier) { diff --git a/integration-tests/opentelemetry-reactive-messaging/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java b/integration-tests/opentelemetry-reactive-messaging/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java index e739c86e999165..25dba82f42a626 100644 --- a/integration-tests/opentelemetry-reactive-messaging/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java +++ b/integration-tests/opentelemetry-reactive-messaging/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java @@ -119,14 +119,13 @@ private void verifyServer(Map spanData, Map pare Assertions.assertFalse((Boolean) spanData.get("parent_remote")); Assertions.assertEquals("GET", spanData.get("attr_http.method")); - Assertions.assertEquals("1.1", spanData.get("attr_http.flavor")); Assertions.assertEquals("/direct", spanData.get("attr_http.target")); assertEquals(url.getHost(), spanData.get("attr_net.host.name")); assertEquals(url.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); Assertions.assertEquals("http", spanData.get("attr_http.scheme")); Assertions.assertEquals("200", spanData.get("attr_http.status_code")); Assertions.assertNotNull(spanData.get("attr_http.client_ip")); - Assertions.assertNotNull(spanData.get("attr_http.user_agent")); + Assertions.assertNotNull(spanData.get("attr_user_agent.original")); } private static void verifyProducer(Map spanData, diff --git a/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java b/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java index 45634a16b38362..5bd3ac48db163a 100644 --- a/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java +++ b/integration-tests/opentelemetry-spi/src/test/java/io/quarkus/it/opentelemetry/spi/OTelSpiTest.java @@ -68,14 +68,13 @@ void testResourceTracing() { assertFalse((Boolean) spanData.get("parent_remote")); assertEquals("GET", spanData.get("attr_http.method")); - assertEquals("1.1", spanData.get("attr_http.flavor")); assertEquals("/direct", spanData.get("attr_http.target")); assertEquals(deepPathUrl.getHost(), spanData.get("attr_net.host.name")); assertEquals(deepPathUrl.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); assertEquals("http", spanData.get("attr_http.scheme")); assertEquals("200", spanData.get("attr_http.status_code")); assertNotNull(spanData.get("attr_http.client_ip")); - assertNotNull(spanData.get("attr_http.user_agent")); + assertNotNull(spanData.get("attr_user_agent.original")); } @Test diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java index a778b6d9ff095e..f830b94590efaf 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java @@ -94,14 +94,13 @@ void testResourceTracing() { assertFalse((Boolean) spanData.get("parent_remote")); assertEquals("GET", spanData.get("attr_http.method")); - assertEquals("1.1", spanData.get("attr_http.flavor")); assertEquals("/direct", spanData.get("attr_http.target")); assertEquals(deepPathUrl.getHost(), spanData.get("attr_net.host.name")); assertEquals(deepPathUrl.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); assertEquals("http", spanData.get("attr_http.scheme")); assertEquals("200", spanData.get("attr_http.status_code")); assertNotNull(spanData.get("attr_http.client_ip")); - assertNotNull(spanData.get("attr_http.user_agent")); + assertNotNull(spanData.get("attr_user_agent.original")); } @Test @@ -129,7 +128,6 @@ void testEmptyClientPath() { assertFalse((Boolean) server.get("parent_valid")); assertFalse((Boolean) server.get("parent_remote")); assertEquals("GET", server.get("attr_http.method")); - assertEquals("1.1", server.get("attr_http.flavor")); assertEquals("/nopath", server.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -137,7 +135,7 @@ void testEmptyClientPath() { assertEquals("/nopath", server.get("attr_http.route")); assertEquals("200", server.get("attr_http.status_code")); assertNotNull(server.get("attr_http.client_ip")); - assertNotNull(server.get("attr_http.user_agent")); + assertNotNull(server.get("attr_user_agent.original")); Map client = getSpanByKindAndParentId(spans, CLIENT, server.get("spanId")); assertEquals(CLIENT.toString(), client.get("kind")); @@ -161,7 +159,6 @@ void testEmptyClientPath() { assertTrue((Boolean) clientServer.get("parent_valid")); assertTrue((Boolean) clientServer.get("parent_remote")); assertEquals("GET", clientServer.get("attr_http.method")); - assertEquals("1.1", clientServer.get("attr_http.flavor")); assertEquals("/", clientServer.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -169,7 +166,7 @@ void testEmptyClientPath() { assertNull(clientServer.get("attr_http.route")); assertEquals("200", clientServer.get("attr_http.status_code")); assertNotNull(clientServer.get("attr_http.client_ip")); - assertNotNull(clientServer.get("attr_http.user_agent")); + assertNotNull(clientServer.get("attr_user_agent.original")); assertEquals(clientServer.get("parentSpanId"), client.get("spanId")); } @@ -198,7 +195,6 @@ void testSlashClientPath() { assertFalse((Boolean) server.get("parent_valid")); assertFalse((Boolean) server.get("parent_remote")); assertEquals("GET", server.get("attr_http.method")); - assertEquals("1.1", server.get("attr_http.flavor")); assertEquals("/slashpath", server.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -206,7 +202,7 @@ void testSlashClientPath() { assertEquals("/slashpath", server.get("attr_http.route")); assertEquals("200", server.get("attr_http.status_code")); assertNotNull(server.get("attr_http.client_ip")); - assertNotNull(server.get("attr_http.user_agent")); + assertNotNull(server.get("attr_user_agent.original")); Map client = getSpanByKindAndParentId(spans, CLIENT, server.get("spanId")); assertEquals(CLIENT.toString(), client.get("kind")); @@ -229,7 +225,6 @@ void testSlashClientPath() { assertTrue((Boolean) clientServer.get("parent_valid")); assertTrue((Boolean) clientServer.get("parent_remote")); assertEquals("GET", clientServer.get("attr_http.method")); - assertEquals("1.1", clientServer.get("attr_http.flavor")); assertEquals("/", clientServer.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -237,7 +232,7 @@ void testSlashClientPath() { assertNull(clientServer.get("attr_http.route")); assertEquals("200", clientServer.get("attr_http.status_code")); assertNotNull(clientServer.get("attr_http.client_ip")); - assertNotNull(clientServer.get("attr_http.user_agent")); + assertNotNull(clientServer.get("attr_user_agent.original")); assertEquals(clientServer.get("parentSpanId"), client.get("spanId")); } @@ -266,14 +261,13 @@ void testChainedResourceTracing() { assertFalse((Boolean) server.get("parent_valid")); assertFalse((Boolean) server.get("parent_remote")); assertEquals("GET", server.get("attr_http.method")); - assertEquals("1.1", server.get("attr_http.flavor")); assertEquals("/chained", server.get("attr_http.target")); assertEquals(deepPathUrl.getHost(), server.get("attr_net.host.name")); assertEquals(deepPathUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); assertEquals("http", server.get("attr_http.scheme")); assertEquals("200", server.get("attr_http.status_code")); assertNotNull(server.get("attr_http.client_ip")); - assertNotNull(server.get("attr_http.user_agent")); + assertNotNull(server.get("attr_user_agent.original")); // CDI call Map cdi = getSpanByKindAndParentId(spans, INTERNAL, server.get("spanId")); @@ -322,14 +316,13 @@ void testTracingWithParentHeaders() { assertTrue((Boolean) spanData.get("parent_valid")); assertEquals("GET", spanData.get("attr_http.method")); - assertEquals("1.1", spanData.get("attr_http.flavor")); assertEquals("/direct", spanData.get("attr_http.target")); assertEquals(deepPathUrl.getHost(), spanData.get("attr_net.host.name")); assertEquals(deepPathUrl.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); assertEquals("http", spanData.get("attr_http.scheme")); assertEquals("200", spanData.get("attr_http.status_code")); assertNotNull(spanData.get("attr_http.client_ip")); - assertNotNull(spanData.get("attr_http.user_agent")); + assertNotNull(spanData.get("attr_user_agent.original")); } @Test @@ -358,14 +351,13 @@ void testDeepPathNaming() { assertFalse((Boolean) spanData.get("parent_remote")); assertEquals("GET", spanData.get("attr_http.method")); - assertEquals("1.1", spanData.get("attr_http.flavor")); assertEquals("/deep/path", spanData.get("attr_http.target")); assertEquals(deepPathUrl.getHost(), spanData.get("attr_net.host.name")); assertEquals(deepPathUrl.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); assertEquals("http", spanData.get("attr_http.scheme")); assertEquals("200", spanData.get("attr_http.status_code")); assertNotNull(spanData.get("attr_http.client_ip")); - assertNotNull(spanData.get("attr_http.user_agent")); + assertNotNull(spanData.get("attr_user_agent.original")); } @Test @@ -394,7 +386,6 @@ void testPathParameter() { assertFalse((Boolean) spanData.get("parent_remote")); assertEquals("GET", spanData.get("attr_http.method")); - assertEquals("1.1", spanData.get("attr_http.flavor")); assertEquals("/param/12345", spanData.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), spanData.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); @@ -402,7 +393,7 @@ void testPathParameter() { assertEquals("/param/{paramId}", spanData.get("attr_http.route")); assertEquals("200", spanData.get("attr_http.status_code")); assertNotNull(spanData.get("attr_http.client_ip")); - assertNotNull(spanData.get("attr_http.user_agent")); + assertNotNull(spanData.get("attr_user_agent.original")); } @Test @@ -429,7 +420,6 @@ void testClientTracing() { assertFalse((Boolean) server.get("parent_valid")); assertFalse((Boolean) server.get("parent_remote")); assertEquals("GET", server.get("attr_http.method")); - assertEquals("1.1", server.get("attr_http.flavor")); assertEquals("/client/ping/one", server.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -437,7 +427,7 @@ void testClientTracing() { assertEquals("/client/ping/{message}", server.get("attr_http.route")); assertEquals("200", server.get("attr_http.status_code")); assertNotNull(server.get("attr_http.client_ip")); - assertNotNull(server.get("attr_http.user_agent")); + assertNotNull(server.get("attr_user_agent.original")); Map client = getSpanByKindAndParentId(spans, CLIENT, server.get("spanId")); assertEquals("GET", client.get("name")); @@ -458,7 +448,6 @@ void testClientTracing() { assertTrue((Boolean) clientServer.get("parent_valid")); assertTrue((Boolean) clientServer.get("parent_remote")); assertEquals("GET", clientServer.get("attr_http.method")); - assertEquals("1.1", clientServer.get("attr_http.flavor")); assertEquals("/client/pong/one", clientServer.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -466,7 +455,7 @@ void testClientTracing() { assertEquals("/client/pong/{message}", clientServer.get("attr_http.route")); assertEquals("200", clientServer.get("attr_http.status_code")); assertNotNull(clientServer.get("attr_http.client_ip")); - assertNotNull(clientServer.get("attr_http.user_agent")); + assertNotNull(clientServer.get("attr_user_agent.original")); assertEquals(clientServer.get("parentSpanId"), client.get("spanId")); } @@ -494,7 +483,6 @@ void testAsyncClientTracing() { assertFalse((Boolean) server.get("parent_valid")); assertFalse((Boolean) server.get("parent_remote")); assertEquals("GET", server.get("attr_http.method")); - assertEquals("1.1", server.get("attr_http.flavor")); assertEquals("/client/async-ping/one", server.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -502,7 +490,7 @@ void testAsyncClientTracing() { assertEquals("/client/async-ping/{message}", server.get("attr_http.route")); assertEquals("200", server.get("attr_http.status_code")); assertNotNull(server.get("attr_http.client_ip")); - assertNotNull(server.get("attr_http.user_agent")); + assertNotNull(server.get("attr_user_agent.original")); Map client = getSpanByKindAndParentId(spans, CLIENT, server.get("spanId")); assertEquals("GET", client.get("name")); @@ -523,7 +511,6 @@ void testAsyncClientTracing() { assertTrue((Boolean) clientServer.get("parent_valid")); assertTrue((Boolean) clientServer.get("parent_remote")); assertEquals("GET", clientServer.get("attr_http.method")); - assertEquals("1.1", clientServer.get("attr_http.flavor")); assertEquals("/client/pong/one", clientServer.get("attr_http.target")); assertEquals(pathParamUrl.getHost(), server.get("attr_net.host.name")); assertEquals(pathParamUrl.getPort(), Integer.valueOf((String) server.get("attr_net.host.port"))); @@ -531,7 +518,7 @@ void testAsyncClientTracing() { assertEquals("/client/pong/{message}", clientServer.get("attr_http.route")); assertEquals("200", clientServer.get("attr_http.status_code")); assertNotNull(clientServer.get("attr_http.client_ip")); - assertNotNull(clientServer.get("attr_http.user_agent")); + assertNotNull(clientServer.get("attr_user_agent.original")); } @Test @@ -560,14 +547,13 @@ void testTemplatedPathOnClass() { assertFalse((Boolean) spanData.get("parent_remote")); assertEquals("GET", spanData.get("attr_http.method")); - assertEquals("1.1", spanData.get("attr_http.flavor")); assertEquals("/template/path/something", spanData.get("attr_http.target")); assertEquals(deepPathUrl.getHost(), spanData.get("attr_net.host.name")); assertEquals(deepPathUrl.getPort(), Integer.valueOf((String) spanData.get("attr_net.host.port"))); assertEquals("http", spanData.get("attr_http.scheme")); assertEquals("200", spanData.get("attr_http.status_code")); assertNotNull(spanData.get("attr_http.client_ip")); - assertNotNull(spanData.get("attr_http.user_agent")); + assertNotNull(spanData.get("attr_user_agent.original")); } @Test diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java index 486d0142bef788..6a9b1c6cd4037b 100644 --- a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java @@ -150,12 +150,11 @@ void shouldCreateClientSpans() { Assertions.assertFalse((Boolean) initialServerSpan.get("parent_remote")); Assertions.assertEquals("POST", initialServerSpan.get("attr_http.method")); - Assertions.assertEquals("1.1", initialServerSpan.get("attr_http.flavor")); Assertions.assertEquals("/call-hello-client-trace", initialServerSpan.get("attr_http.target")); Assertions.assertEquals("http", initialServerSpan.get("attr_http.scheme")); Assertions.assertEquals("200", initialServerSpan.get("attr_http.status_code")); Assertions.assertNotNull(initialServerSpan.get("attr_http.client_ip")); - Assertions.assertNotNull(initialServerSpan.get("attr_http.user_agent")); + Assertions.assertNotNull(initialServerSpan.get("attr_user_agent.original")); spans = getClientSpans("POST", "http://localhost:8081/hello?count=3"); Assertions.assertEquals(1, spans.size()); @@ -207,7 +206,6 @@ void shouldCreateClientSpans() { Assertions.assertTrue((Boolean) serverSpanClientSide.get("parent_remote")); Assertions.assertEquals("POST", serverSpanClientSide.get("attr_http.method")); - Assertions.assertEquals("1.1", serverSpanClientSide.get("attr_http.flavor")); Assertions.assertEquals("/hello?count=3", serverSpanClientSide.get("attr_http.target")); Assertions.assertEquals("http", serverSpanClientSide.get("attr_http.scheme")); Assertions.assertEquals("200", serverSpanClientSide.get("attr_http.status_code")); From d0993ef9deccb0bc35dee1ae3ba3cafa85833918 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 21 Apr 2023 10:49:40 +0200 Subject: [PATCH 109/333] ArC new Dev UI - fired events monitoring - various fixes - also do not store the event payload - move some parts of the backend logic from the ArcDevConsoleProcessor to the ArcDevUIProcessor --- .../devconsole/ArcDevConsoleProcessor.java | 45 ------------- .../deployment/devui/ArcDevUIProcessor.java | 65 +++++++++++++++++++ .../resources/dev-ui/qwc-arc-fired-events.js | 41 ++++++------ .../arc/runtime/devmode/EventInfo.java | 14 ---- .../arc/runtime/devmode/EventsMonitor.java | 18 ++--- .../arc/runtime/devui/ArcJsonRPCService.java | 51 +++++++++------ .../quarkus/arc/processor/BuiltinScope.java | 4 +- 7 files changed, 124 insertions(+), 114 deletions(-) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java index df0b3ce4ce5b92..53d1a2b115c781 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java @@ -10,9 +10,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; - import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.ArcConfig; @@ -22,7 +19,6 @@ import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.deployment.devconsole.DependencyGraph.Link; import io.quarkus.arc.deployment.devui.ArcBeanInfoBuildItem; -import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanDeploymentValidator; import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext; import io.quarkus.arc.processor.BeanInfo; @@ -35,10 +31,7 @@ import io.quarkus.arc.runtime.ArcContainerSupplier; import io.quarkus.arc.runtime.ArcRecorder; import io.quarkus.arc.runtime.BeanLookupSupplier; -import io.quarkus.arc.runtime.devconsole.InvocationInterceptor; -import io.quarkus.arc.runtime.devconsole.InvocationTree; import io.quarkus.arc.runtime.devconsole.InvocationsMonitor; -import io.quarkus.arc.runtime.devconsole.Monitored; import io.quarkus.arc.runtime.devmode.EventsMonitor; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; @@ -71,37 +64,9 @@ void monitor(ArcConfig config, BuildProducer skipNames = new HashSet<>(); - skipNames.add(DotName.createSimple(InvocationTree.class.getName())); - skipNames.add(DotName.createSimple(InvocationsMonitor.class.getName())); - skipNames.add(DotName.createSimple(EventsMonitor.class.getName())); - annotationTransformers.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { - @Override - public void transform(TransformationContext transformationContext) { - if (transformationContext.isClass()) { - ClassInfo beanClass = transformationContext.getTarget().asClass(); - if ((customScopes.isScopeDeclaredOn(beanClass) - || isAdditionalBeanDefiningAnnotationOn(beanClass, beanDefiningAnnotations)) - && !skipNames.contains(beanClass.name())) { - transformationContext.transform().add(Monitored.class).done(); - } - } - } - })); runtimeInfos.produce(new DevConsoleRuntimeTemplateInfoBuildItem("invocationsMonitor", new BeanLookupSupplier(InvocationsMonitor.class), this.getClass(), curateOutcomeBuildItem)); } @@ -226,16 +191,6 @@ public void handle(Void ignore) { static final int DEFAULT_MAX_DEPENDENCY_LEVEL = 10; - private boolean isAdditionalBeanDefiningAnnotationOn(ClassInfo beanClass, - List beanDefiningAnnotations) { - for (BeanDefiningAnnotationBuildItem beanDefiningAnnotation : beanDefiningAnnotations) { - if (beanClass.classAnnotation(beanDefiningAnnotation.getName()) != null) { - return true; - } - } - return false; - } - DependencyGraph buildDependencyGraph(BeanInfo bean, ValidationContext validationContext, BeanResolver resolver, DevBeanInfos devBeanInfos, List allInjectionPoints, Map> declaringToProducers, diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java index c76dac830e220b..0aeb3f4ab27e28 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java @@ -2,15 +2,30 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.ArcConfig; +import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; +import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem; import io.quarkus.arc.deployment.devconsole.DevBeanInfo; import io.quarkus.arc.deployment.devconsole.DevBeanInfos; import io.quarkus.arc.deployment.devconsole.DevDecoratorInfo; import io.quarkus.arc.deployment.devconsole.DevInterceptorInfo; import io.quarkus.arc.deployment.devconsole.DevObserverInfo; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.runtime.devconsole.InvocationInterceptor; +import io.quarkus.arc.runtime.devconsole.InvocationTree; +import io.quarkus.arc.runtime.devconsole.InvocationsMonitor; +import io.quarkus.arc.runtime.devconsole.Monitored; +import io.quarkus.arc.runtime.devmode.EventsMonitor; import io.quarkus.arc.runtime.devui.ArcJsonRPCService; import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; import io.quarkus.devui.spi.page.CardPageBuildItem; @@ -94,6 +109,46 @@ JsonRPCProvidersBuildItem createJsonRPCService() { return new JsonRPCProvidersBuildItem(ArcJsonRPCService.class); } + @BuildStep(onlyIf = IsDevelopment.class) + void registerMonitoringComponents(ArcConfig config, BuildProducer beans, + BuildProducer annotationTransformers, + CustomScopeAnnotationsBuildItem customScopes, List beanDefiningAnnotations) { + if (!config.devMode.monitoringEnabled) { + return; + } + if (!config.transformUnproxyableClasses) { + throw new IllegalStateException( + "Dev UI problem: monitoring of CDI business method invocations not possible\n\t- quarkus.arc.transform-unproxyable-classes was set to false and therefore it would not be possible to apply interceptors to unproxyable bean classes\n\t- please disable the monitoring feature via quarkus.arc.dev-mode.monitoring-enabled=false or enable unproxyable classes transformation"); + } + // Register beans + beans.produce(AdditionalBeanBuildItem.builder().setUnremovable() + .addBeanClasses(EventsMonitor.class, InvocationTree.class, InvocationsMonitor.class, + InvocationInterceptor.class, + Monitored.class) + .build()); + + // Add @Monitored to all beans + Set skipNames = Set.of(DotName.createSimple(InvocationTree.class), + DotName.createSimple(InvocationsMonitor.class), DotName.createSimple(EventsMonitor.class)); + annotationTransformers.produce(new AnnotationsTransformerBuildItem(AnnotationsTransformer + .appliedToClass() + .whenClass(c -> (customScopes.isScopeDeclaredOn(c) + || isAdditionalBeanDefiningAnnotationOn(c, beanDefiningAnnotations)) + && !skipClass(c, skipNames)) + .thenTransform(t -> t.add(Monitored.class)))); + } + + private boolean skipClass(ClassInfo beanClass, Set skipNames) { + if (skipNames.contains(beanClass.name())) { + return true; + } + if (beanClass.name().packagePrefix().startsWith("io.quarkus.devui.runtime")) { + // Skip monitoring for internal devui components + return true; + } + return false; + } + private List toDevBeanWithInterceptorInfo(List beans, DevBeanInfos devBeanInfos) { List l = new ArrayList<>(); for (DevBeanInfo dbi : beans) { @@ -102,6 +157,16 @@ private List toDevBeanWithInterceptorInfo(List beanDefiningAnnotations) { + for (BeanDefiningAnnotationBuildItem beanDefiningAnnotation : beanDefiningAnnotations) { + if (beanClass.hasDeclaredAnnotation(beanDefiningAnnotation.getName())) { + return true; + } + } + return false; + } + private static final String BEANS = "beans"; private static final String OBSERVERS = "observers"; private static final String INTERCEPTORS = "interceptors"; diff --git a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js index 5b6e8bb5c8309b..64fdf4ccd13288 100644 --- a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js +++ b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js @@ -3,7 +3,6 @@ import { until } from 'lit/directives/until.js'; import { JsonRpc } from 'jsonrpc'; import '@vaadin/grid'; import { columnBodyRenderer } from '@vaadin/grid/lit.js'; -import '@vaadin/details'; import '@vaadin/vertical-layout'; import '@vaadin/button'; import '@vaadin/checkbox'; @@ -28,27 +27,29 @@ export class QwcArcFiredEvents extends LitElement { .arctable { height: 100%; padding-bottom: 10px; - } - .payload { - color: grey; - font-size: small; }`; static properties = { _firedEvents: {state: true}, - _observer: {state:false}, + _skipLifecycleEvents: {state: true} }; connectedCallback() { super.connectedCallback(); this._refresh(); - this._observer = this.jsonRpc.streamEvents().onNext(jsonRpcResponse => { + this._eventsStream = this.jsonRpc.streamEvents().onNext(jsonRpcResponse => { this._addToEvents(jsonRpcResponse.result); }); + // Context lifecycle events are skipped by default; updates are handled by the stream + this._skipLifecycleEvents = true; + this._skipLifecycleEventsStream = this.jsonRpc.streamSkipContextEvents().onNext(jsonRpcResponse => { + this._skipLifecycleEvents = jsonRpcResponse.result; + }); } disconnectedCallback() { - this._observer.cancel(); + this._eventsStream.cancel(); + this._skipLifecycleEventsStream.cancel(); super.disconnectedCallback(); } @@ -59,13 +60,13 @@ export class QwcArcFiredEvents extends LitElement { _renderFiredEvents(){ if(this._firedEvents){ return html`

@@ -90,15 +91,9 @@ export class QwcArcFiredEvents extends LitElement { } } - _payloadRenderer(event) { + _eventTypeRenderer(event) { return html` - -
${event.type}
- - - ${event.payload} - -
+ ${event.type} `; } @@ -115,13 +110,15 @@ export class QwcArcFiredEvents extends LitElement { } _toggleContext(){ - // TODO: + this.jsonRpc.toggleSkipContextEvents().then(events => { + this._firedEvents = events.result; + }); } _addToEvents(event){ this._firedEvents = [ - ...this._firedEvents, event, + ...this._firedEvents, ]; } } diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java index fde1f35d48f422..9ce8f7a265fc41 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java @@ -7,21 +7,15 @@ public class EventInfo { private String type; private List qualifiers; private boolean isContextEvent; - private Object payload; public EventInfo() { } public EventInfo(String timestamp, String type, List qualifiers, boolean isContextEvent) { - this(timestamp, type, qualifiers, isContextEvent, null); - } - - public EventInfo(String timestamp, String type, List qualifiers, boolean isContextEvent, Object payload) { this.timestamp = timestamp; this.type = type; this.qualifiers = qualifiers; this.isContextEvent = isContextEvent; - this.payload = payload; } public String getTimestamp() { @@ -56,12 +50,4 @@ public void setIsContextEvent(boolean isContextEvent) { this.isContextEvent = isContextEvent; } - public Object getPayload() { - return payload; - } - - public void setPayload(Object payload) { - this.payload = payload; - } - } diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java index 2d85c7db162faa..94a6ebb2d7819a 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java @@ -28,6 +28,7 @@ public class EventsMonitor { private volatile boolean skipContextEvents = true; private final List events = Collections.synchronizedList(new ArrayList<>(DEFAULT_LIMIT)); private final BroadcastProcessor eventsStream = BroadcastProcessor.create(); + private final BroadcastProcessor skipContextEventsStream = BroadcastProcessor.create(); void notify(@Observes Object payload, EventMetadata eventMetadata) { if (skipContextEvents && isContextEvent(eventMetadata)) { @@ -54,6 +55,10 @@ public Multi streamEvents() { return eventsStream; } + public Multi streamSkipContextEvents() { + return skipContextEventsStream; + } + public List getLastEvents() { List result = new ArrayList<>(events); Collections.reverse(result); @@ -67,6 +72,7 @@ public boolean isSkipContextEvents() { public void toggleSkipContextEvents() { // This is not thread-safe but we don't expect concurrent actions from dev ui skipContextEvents = !skipContextEvents; + skipContextEventsStream.onNext(skipContextEvents); } boolean isContextEvent(EventMetadata eventMetadata) { @@ -87,14 +93,8 @@ boolean isContextEvent(EventMetadata eventMetadata) { private EventInfo toEventInfo(Object payload, EventMetadata eventMetadata) { EventInfo eventInfo = new EventInfo(); - - // Timestamp eventInfo.setTimestamp(now()); - - // Type eventInfo.setType(eventMetadata.getType().getTypeName()); - - // Qualifiers List q = new ArrayList<>(); if (eventMetadata.getQualifiers().size() > 1) { for (Annotation qualifier : eventMetadata.getQualifiers()) { @@ -105,13 +105,7 @@ private EventInfo toEventInfo(Object payload, EventMetadata eventMetadata) { } } eventInfo.setQualifiers(q); - - // ContextEvent eventInfo.setIsContextEvent(isContextEvent(eventMetadata)); - - // Payload - eventInfo.setPayload(payload.toString()); - return eventInfo; } diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java index 4b125b56a18727..93f37e76304a7c 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java @@ -7,7 +7,9 @@ import java.util.ArrayList; import java.util.List; -import io.quarkus.arc.Arc; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + import io.quarkus.arc.runtime.devconsole.Invocation; import io.quarkus.arc.runtime.devconsole.InvocationsMonitor; import io.quarkus.arc.runtime.devmode.EventInfo; @@ -18,38 +20,50 @@ public class ArcJsonRPCService { + @Inject + Instance eventsMonitor; + + @Inject + Instance invocationsMonitor; + public Multi streamEvents() { - EventsMonitor eventsMonitor = Arc.container().instance(EventsMonitor.class).get(); - if (eventsMonitor != null) { - return eventsMonitor.streamEvents(); - } - return Multi.createFrom().empty(); + return eventsMonitor.isResolvable() ? eventsMonitor.get().streamEvents() : Multi.createFrom().empty(); + } + + public Multi streamSkipContextEvents() { + return eventsMonitor.isResolvable() ? eventsMonitor.get().streamSkipContextEvents() : Multi.createFrom().empty(); } @NonBlocking public List getLastEvents() { - EventsMonitor eventsMonitor = Arc.container().instance(EventsMonitor.class).get(); - if (eventsMonitor != null) { - return eventsMonitor.getLastEvents(); + if (eventsMonitor.isResolvable()) { + return eventsMonitor.get().getLastEvents(); } return List.of(); } @NonBlocking public List clearLastEvents() { - EventsMonitor eventsMonitor = Arc.container().instance(EventsMonitor.class).get(); - if (eventsMonitor != null) { - eventsMonitor.clear(); - return eventsMonitor.getLastEvents(); + if (eventsMonitor.isResolvable()) { + eventsMonitor.get().clear(); + return getLastEvents(); + } + return List.of(); + } + + @NonBlocking + public List toggleSkipContextEvents() { + if (eventsMonitor.isResolvable()) { + eventsMonitor.get().toggleSkipContextEvents(); + return getLastEvents(); } return List.of(); } @NonBlocking public List getLastInvocations() { - InvocationsMonitor invocationsMonitor = Arc.container().instance(InvocationsMonitor.class).get(); - if (invocationsMonitor != null) { - List lastInvocations = invocationsMonitor.getLastInvocations(); + if (invocationsMonitor.isResolvable()) { + List lastInvocations = invocationsMonitor.get().getLastInvocations(); return toInvocationInfos(lastInvocations); } return List.of(); @@ -57,9 +71,8 @@ public List getLastInvocations() { @NonBlocking public List clearLastInvocations() { - InvocationsMonitor invocationsMonitor = Arc.container().instance(InvocationsMonitor.class).get(); - if (invocationsMonitor != null) { - invocationsMonitor.clear(); + if (invocationsMonitor.isResolvable()) { + invocationsMonitor.get().clear(); return getLastInvocations(); } return List.of(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java index d664b561671e16..29ade50172ec4e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java @@ -43,7 +43,7 @@ public static BuiltinScope from(DotName scopeAnnotationName) { public static BuiltinScope from(ClassInfo clazz) { for (BuiltinScope scope : BuiltinScope.values()) { - if (clazz.classAnnotation(scope.getName()) != null) { + if (clazz.hasDeclaredAnnotation(scope.getName())) { return scope; } } @@ -69,7 +69,7 @@ public static boolean isIn(Iterable annotations) { public static boolean isDeclaredOn(ClassInfo clazz) { for (BuiltinScope scope : BuiltinScope.values()) { - if (clazz.classAnnotation(scope.getName()) != null) { + if (clazz.hasDeclaredAnnotation(scope.getName())) { return true; } } From a990266263a8b77e24db58fbb062c12345ba764e Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 20 Apr 2023 18:57:17 +0100 Subject: [PATCH 110/333] Set OIDC user-info-required when UserInfo is known to be required --- ...erificationWithUserInfoValidationTest.java | 4 ++- .../io/quarkus/oidc/runtime/OidcRecorder.java | 29 ++++++++++++++++--- .../keycloak/CustomTenantConfigResolver.java | 2 ++ .../src/main/resources/application.properties | 1 - .../src/main/resources/application.properties | 1 - 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java index b6eaff8715becc..1c53cc39659047 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java @@ -16,7 +16,9 @@ public class OpaqueTokenVerificationWithUserInfoValidationTest { @RegisterExtension static final QuarkusUnitTest test = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addAsResource(new StringAsset("quarkus.oidc.token.verify-access-token-with-user-info=true\n"), + .addAsResource(new StringAsset( + "quarkus.oidc.token.verify-access-token-with-user-info=true\n" + + "quarkus.oidc.authentication.user-info-required=false\n"), "application.properties")) .assertException(t -> { Throwable e = t; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index 64187c10e2cef8..f5fd7d69857104 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -172,6 +172,19 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf return Uni.createFrom().failure(t); } + if (oidcConfig.roles.source.orElse(null) == Source.userinfo && !enableUserInfo(oidcConfig)) { + throw new ConfigurationException( + "UserInfo is not required but UserInfo is expected to be the source of authorization roles"); + } + if (oidcConfig.token.verifyAccessTokenWithUserInfo && !enableUserInfo(oidcConfig)) { + throw new ConfigurationException( + "UserInfo is not required but 'verifyAccessTokenWithUserInfo' is enabled"); + } + if (!oidcConfig.authentication.isIdTokenRequired().orElse(true) && !enableUserInfo(oidcConfig)) { + throw new ConfigurationException( + "UserInfo is not required but it will be needed to verify a code flow access token"); + } + if (!oidcConfig.discoveryEnabled.orElse(true)) { if (!isServiceApp(oidcConfig)) { if (!oidcConfig.authorizationPath.isPresent() || !oidcConfig.tokenPath.isPresent()) { @@ -226,10 +239,6 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf } if (oidcConfig.token.verifyAccessTokenWithUserInfo) { - if (!oidcConfig.authentication.isUserInfoRequired().orElse(false)) { - throw new ConfigurationException( - "UserInfo is not required but 'verifyAccessTokenWithUserInfo' is enabled"); - } if (!oidcConfig.isDiscoveryEnabled().orElse(true)) { if (oidcConfig.userInfoPath.isEmpty()) { throw new ConfigurationException( @@ -251,6 +260,18 @@ public TenantConfigContext apply(OidcProvider p) { }); } + private static boolean enableUserInfo(OidcTenantConfig oidcConfig) { + Optional userInfoRequired = oidcConfig.authentication.isUserInfoRequired(); + if (userInfoRequired.isPresent()) { + if (!userInfoRequired.get()) { + return false; + } + } else { + oidcConfig.authentication.setUserInfoRequired(true); + } + return true; + } + private static TenantConfigContext createTenantContextFromPublicKey(OidcTenantConfig oidcConfig) { if (!isServiceApp(oidcConfig)) { throw new ConfigurationException("'public-key' property can only be used with the 'service' applications"); diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java index 219738a57ebd3f..2d2ee30dea0913 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java @@ -137,6 +137,8 @@ public OidcTenantConfig get() { config.setTokenPath(tokenUri); String jwksUri = uri.replace("/tenant-refresh/tenant-web-app-refresh/api/user", "/oidc/jwks"); config.setJwksPath(jwksUri); + String userInfoPath = uri.replace("/tenant-refresh/tenant-web-app-refresh/api/user", "/oidc/userinfo"); + config.setUserInfoPath(userInfoPath); config.getToken().setIssuer("any"); config.tokenStateManager.setSplitTokens(true); config.tokenStateManager.setEncryptionRequired(false); diff --git a/integration-tests/oidc-tenancy/src/main/resources/application.properties b/integration-tests/oidc-tenancy/src/main/resources/application.properties index 00746917495911..af8297b86a9412 100644 --- a/integration-tests/oidc-tenancy/src/main/resources/application.properties +++ b/integration-tests/oidc-tenancy/src/main/resources/application.properties @@ -49,7 +49,6 @@ quarkus.oidc.tenant-web-app.auth-server-url=${keycloak.url}/realms/quarkus-webap quarkus.oidc.tenant-web-app.client-id=quarkus-app-webapp quarkus.oidc.tenant-web-app.credentials.secret=secret quarkus.oidc.tenant-web-app.application-type=web-app -quarkus.oidc.tenant-web-app.authentication.user-info-required=true quarkus.oidc.tenant-web-app.roles.source=userinfo quarkus.oidc.tenant-web-app.allow-user-info-cache=false diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index c318ee68b82acd..e90a280bead002 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -56,7 +56,6 @@ quarkus.oidc.code-flow-user-info-only.authorization-path=/ quarkus.oidc.code-flow-user-info-only.token-path=access_token quarkus.oidc.code-flow-user-info-only.user-info-path=protocol/openid-connect/userinfo quarkus.oidc.code-flow-user-info-only.authentication.id-token-required=false -quarkus.oidc.code-flow-user-info-only.authentication.user-info-required=true quarkus.oidc.code-flow-user-info-only.code-grant.extra-params.extra-param=extra-param-value quarkus.oidc.code-flow-user-info-only.code-grant.headers.X-Custom=XCustomHeaderValue quarkus.oidc.code-flow-user-info-only.client-id=quarkus-web-app From 337c3d7267ef0b9dd3ffc467b14b998d09882fee Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 21 Apr 2023 13:38:48 +0300 Subject: [PATCH 111/333] Properly pass annotation to Writers when streaming data Closes: #31587 --- .../reactive/server/core/SseUtil.java | 6 +-- .../reactive/server/core/StreamingUtil.java | 6 +-- .../hibernate-orm-envers/pom.xml | 17 ++++++ .../io/quarkus/it/envers/CustomOutput.java | 13 +++++ .../io/quarkus/it/envers/MessageProvider.java | 11 +++- .../io/quarkus/it/envers/OutputResource.java | 21 ++++++++ .../quarkus/it/envers/OutputResourceTest.java | 54 ++++++++++++++++++- 7 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java index f0a3824f25c424..edef2f560405d0 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java @@ -16,7 +16,6 @@ import jakarta.ws.rs.sse.OutboundSseEvent; import jakarta.ws.rs.sse.SseEvent; -import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.util.CommonSseUtil; import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap; import org.jboss.resteasy.reactive.server.handlers.PublisherResponseHandler; @@ -135,10 +134,9 @@ private static String serialiseDataToString(ResteasyReactiveRequestContext conte ByteArrayOutputStream baos = new ByteArrayOutputStream(); boolean wrote = false; for (MessageBodyWriter writer : writers) { - // Spec(API) says we should use class/type/mediaType but doesn't talk about annotations - if (writer.isWriteable(entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType)) { + if (writer.isWriteable(entityClass, entityType, context.getAllAnnotations(), mediaType)) { // FIXME: spec doesn't really say what headers we should use here - writer.writeTo(entity, entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType, + writer.writeTo(entity, entityClass, entityType, context.getAllAnnotations(), mediaType, new QuarkusMultivaluedHashMap<>(), baos); wrote = true; break; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java index 0903202a534028..8140b79ca2f394 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java @@ -13,7 +13,6 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.MessageBodyWriter; -import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap; import org.jboss.resteasy.reactive.server.StreamingOutputStream; import org.jboss.resteasy.reactive.server.handlers.PublisherResponseHandler; @@ -63,10 +62,9 @@ private static byte[] serialiseEntity(ResteasyReactiveRequestContext context, Ob StreamingOutputStream baos = new StreamingOutputStream(); boolean wrote = false; for (MessageBodyWriter writer : writers) { - // Spec(API) says we should use class/type/mediaType but doesn't talk about annotations - if (writer.isWriteable(entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType)) { + if (writer.isWriteable(entityClass, entityType, context.getAllAnnotations(), mediaType)) { // FIXME: spec doesn't really say what headers we should use here - writer.writeTo(entity, entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType, + writer.writeTo(entity, entityClass, entityType, context.getAllAnnotations(), mediaType, new QuarkusMultivaluedHashMap<>(), baos); wrote = true; break; diff --git a/integration-tests/hibernate-orm-envers/pom.xml b/integration-tests/hibernate-orm-envers/pom.xml index 27284183873297..ee235744893161 100644 --- a/integration-tests/hibernate-orm-envers/pom.xml +++ b/integration-tests/hibernate-orm-envers/pom.xml @@ -28,6 +28,10 @@ io.quarkus quarkus-jdbc-h2 + + io.quarkus + quarkus-jaxrs-client-reactive + @@ -99,6 +103,19 @@ + + io.quarkus + quarkus-jaxrs-client-reactive-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java new file mode 100644 index 00000000000000..503abf7959c71b --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java @@ -0,0 +1,13 @@ +package io.quarkus.it.envers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CustomOutput { + + String value(); +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java index f09fbac07220ef..3bbff668bba352 100644 --- a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java @@ -40,6 +40,15 @@ public boolean isWriteable(Class type, Type genericType, Annotation[] annotat @Override public void writeTo(Message event, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - entityStream.write("{\"data\": \"out\"}".getBytes(StandardCharsets.UTF_8)); + String data = "out"; + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(CustomOutput.class)) { + data = ((CustomOutput) annotation).value(); + break; + } + } + } + entityStream.write(String.format("{\"data\": \"%s\"}", data).getBytes(StandardCharsets.UTF_8)); } } diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java index a3882b22dbc414..4376c1378611f2 100644 --- a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java @@ -1,10 +1,16 @@ package io.quarkus.it.envers; +import java.util.List; + import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import org.jboss.resteasy.reactive.RestStreamElementType; + +import io.smallrye.mutiny.Multi; + @Path("output") public class OutputResource { @@ -13,4 +19,19 @@ public class OutputResource { public Message out() { return new Message("test"); } + + @GET + @RestStreamElementType(MediaType.APPLICATION_JSON) + @CustomOutput("dummy") + @Path("annotation") + public Multi sseOut() { + return Multi.createFrom().iterable(List.of(new Message("test"), new Message("test"))); + } + + @GET + @RestStreamElementType(MediaType.APPLICATION_JSON) + @Path("no-annotation") + public Multi sseOut2() { + return Multi.createFrom().iterable(List.of(new Message("test"), new Message("test"))); + } } diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java index a1a5d6efe039bf..74b6aa4ffa9bbe 100644 --- a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java @@ -3,21 +3,73 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.sse.SseEventSource; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; @QuarkusTest class OutputResourceTest { + private static final String RESOURCE_PATH = "/jpa-envers-test/output"; + @TestHTTPEndpoint(OutputResource.class) + @TestHTTPResource + URL url; + @Test void test() { given().accept(ContentType.JSON) .when() - .get("/jpa-envers-test/output") + .get(RESOURCE_PATH) .then() .statusCode(200) .body("data", equalTo("out")); } + + @Test + public void testSseWithAnnotation() throws InterruptedException, URISyntaxException, MalformedURLException { + doTestSee("annotation", "dummy"); + } + + @Test + public void testSseWithoutAnnotation() throws InterruptedException, URISyntaxException, MalformedURLException { + doTestSee("no-annotation", "out"); + } + + private void doTestSee(String path, String expectedDataValue) + throws URISyntaxException, InterruptedException, MalformedURLException { + Client client = ClientBuilder.newBuilder().build(); + WebTarget target = client.target(new URL(ConfigProvider.getConfig().getValue("test.url", String.class)).toURI()) + .path(RESOURCE_PATH).path(path); + try (SseEventSource sse = SseEventSource.target(target).build()) { + CountDownLatch latch = new CountDownLatch(1); + List errors = new CopyOnWriteArrayList<>(); + List results = new CopyOnWriteArrayList<>(); + sse.register(event -> results.add(event.readData()), errors::add, latch::countDown); + sse.open(); + Assertions.assertTrue(latch.await(20, TimeUnit.SECONDS)); + + String json = String.format("{\"data\": \"%s\"}", expectedDataValue); + Assertions.assertEquals(Arrays.asList(json, json), results); + Assertions.assertEquals(0, errors.size()); + } + } } From eaf40b64bf81e7572219d468f25345951fa39ce7 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 21 Apr 2023 13:35:45 +0200 Subject: [PATCH 112/333] ArC new Dev UI - monitoring - invocation trees - add basic implementation (no streaming, no clickable trees, etc.) --- .../resources/dev-templates/invocations.html | 2 +- .../dev-ui/qwc-arc-invocation-trees.js | 62 ++++++++++++++++--- .../arc/runtime/devconsole/Invocation.java | 7 +++ .../devconsole/InvocationsMonitor.java | 12 +++- .../arc/runtime/devmode/InvocationInfo.java | 54 ++++++++++++++++ .../arc/runtime/devui/ArcJsonRPCService.java | 9 ++- 6 files changed, 131 insertions(+), 15 deletions(-) diff --git a/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html b/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html index 320bf446991099..69f7a8c6190c4b 100644 --- a/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html +++ b/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html @@ -79,7 +79,7 @@ - {#each info:invocationsMonitor.lastInvocations.orEmpty} + {#each info:invocationsMonitor.filteredLastInvocations.orEmpty} {it.startFormatted}
    {#tree it root=true /}
diff --git a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js index 6d5e0e2a97a534..1a5dc198f04ea4 100644 --- a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js +++ b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js @@ -1,8 +1,8 @@ import { LitElement, html, css} from 'lit'; import { until } from 'lit/directives/until.js'; import { JsonRpc } from 'jsonrpc'; +import { columnBodyRenderer } from '@vaadin/grid/lit.js'; import '@vaadin/grid'; -import '@vaadin/grid/vaadin-grid-tree-column.js'; import '@vaadin/button'; import '@vaadin/checkbox'; @@ -26,14 +26,26 @@ export class QwcArcInvocationTrees extends LitElement { .arctable { height: 100%; padding-bottom: 10px; - }`; + } + ul li::before { + content: "└─ "; + } + ul { + list-style: none; + } + code { + font-size: 90%; + } + `; static properties = { - _invocations: {state: true} + _invocations: {state: true}, + _filterOutQuarkusBeans: {state: true} }; connectedCallback() { super.connectedCallback(); + this._filterOutQuarkusBeans = true; this._refresh(); } @@ -44,13 +56,13 @@ export class QwcArcInvocationTrees extends LitElement { _renderInvocations(){ if(this._invocations){ return html` - + + `; } } + _invocationsRenderer(invocation) { + return html` +
    + ${this._invocationRenderer(invocation)} +
+ `; + } + + _invocationRenderer(invocation) { + return html` +
  • + ${invocation.kind.toLowerCase()} + ${invocation.methodName} + ${invocation.duration == 0 ? '< 1' : invocation.duration} ms +
      + ${invocation.children.map(child => + html`${this._invocationRenderer(child)}` + )} +
    +
  • + `; + } + _refresh(){ this.jsonRpc.getLastInvocations().then(invocations => { - this._invocations = invocations.result; + if (this._filterOutQuarkusBeans) { + this._invocations = invocations.result.filter(i => !i.quarkusBean); + } else { + this._invocations = invocations.result; + } }); } @@ -76,7 +119,8 @@ export class QwcArcInvocationTrees extends LitElement { } _toggleFilter(){ - // TODO: + this._filterOutQuarkusBeans = !this._filterOutQuarkusBeans; + this._refresh(); } } customElements.define('qwc-arc-invocation-trees', QwcArcInvocationTrees); \ No newline at end of file diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java index 4ac219e7108693..cd301510a341df 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java @@ -94,6 +94,13 @@ public String getPackageName(String name) { return ""; } + public boolean isQuarkusBean() { + if (interceptedBean == null) { + return false; + } + return interceptedBean.getBeanClass().getName().startsWith("io.quarkus"); + } + public enum Kind { BUSINESS, diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java index 61f1189c55d8dc..0f25f90f722f68 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java @@ -28,16 +28,22 @@ void addInvocation(Invocation invocation) { invocations.add(invocation); } - public List getLastInvocations() { - List result = new ArrayList<>(invocations); + // this method should be removed when the Dev UI 1 components are removed + public List getFilteredLastInvocations() { + List result = getLastInvocations(); if (filterOutQuarkusBeans) { for (Iterator it = result.iterator(); it.hasNext();) { Invocation invocation = it.next(); - if (invocation.getInterceptedBean().getBeanClass().getName().startsWith("io.quarkus")) { + if (invocation.isQuarkusBean()) { it.remove(); } } } + return result; + } + + public List getLastInvocations() { + List result = new ArrayList<>(invocations); Collections.reverse(result); return result; } diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java index e681fd05e0af3b..acc98f37e72079 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java @@ -1,9 +1,23 @@ package io.quarkus.arc.runtime.devmode; +import java.util.List; + public class InvocationInfo { private String startTime; + private String methodName; + + // in milliseconds + private long duration; + + // business method, producer, observer, etc. + private String kind; + + private List children; + + private boolean quarkusBean; + public String getStartTime() { return startTime; } @@ -12,4 +26,44 @@ public void setStartTime(String startTime) { this.startTime = startTime; } + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public boolean isQuarkusBean() { + return quarkusBean; + } + + public void setQuarkusBean(boolean quarkusBean) { + this.quarkusBean = quarkusBean; + } + } diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java index 93f37e76304a7c..996fc1f969deeb 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java @@ -88,8 +88,13 @@ private List toInvocationInfos(List invocations) { private InvocationInfo toInvocationInfo(Invocation invocation) { InvocationInfo info = new InvocationInfo(); - LocalDateTime starttime = LocalDateTime.ofInstant(Instant.ofEpochMilli(invocation.getStart()), ZoneId.systemDefault()); - info.setStartTime(timeString(starttime)); + info.setStartTime( + timeString(LocalDateTime.ofInstant(Instant.ofEpochMilli(invocation.getStart()), ZoneId.systemDefault()))); + info.setMethodName(invocation.getDeclaringClassName() + "#" + invocation.getMethod().getName()); + info.setDuration(invocation.getDurationMillis()); + info.setKind(invocation.getKind().toString()); + info.setChildren(toInvocationInfos(invocation.getChildren())); + info.setQuarkusBean(invocation.isQuarkusBean()); return info; } From 3c2f4f4f87dec8f69cb12c59dbbf983d7f58653c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:54:24 +0000 Subject: [PATCH 113/333] Bump smallrye-mutiny-vertx-core from 3.2.0 to 3.3.0 Bumps smallrye-mutiny-vertx-core from 3.2.0 to 3.3.0. --- updated-dependencies: - dependency-name: io.smallrye.reactive:smallrye-mutiny-vertx-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- independent-projects/resteasy-reactive/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 155ee9656caffd..ccbf35acef87c7 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -72,7 +72,7 @@ 3.0.3 3.0.0 4.2.0 - 3.2.0 + 3.3.0 1.0.4 1.0.0 From a76e2101cc93605d29942951647e4fcbe59041b9 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Fri, 21 Apr 2023 23:23:45 +0900 Subject: [PATCH 114/333] Correct a typo in redis-reference.adoc --- docs/src/main/asciidoc/redis-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc index a9e3d1e8822299..55fba59026da1f 100644 --- a/docs/src/main/asciidoc/redis-reference.adoc +++ b/docs/src/main/asciidoc/redis-reference.adoc @@ -730,7 +730,7 @@ This behavior is disabled in _prod_ mode, and if you want to import even in prod %prod.quarkus.redis.load-script=import.redis ---- -Before importing in _prod_ mode, mae sure you configured `quarkus.redis.flush-before-load` accordingly. +Before importing in _prod_ mode, make sure you configured `quarkus.redis.flush-before-load` accordingly. IMPORTANT: In dev mode, to reload the content of the `.redis` load scripts, you need to add: `%dev.quarkus.vertx.caching=false` From 035a3e5a351eb483eae83ba01c7149c81b637ef6 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 21 Apr 2023 18:11:57 +0300 Subject: [PATCH 115/333] Use specific PgPoolOptions in reactive-pg-client The idea behind this is to allow `PgPoolCreator` to be able to set pipelining as well --- docs/src/main/asciidoc/reactive-sql-clients.adoc | 4 ++-- .../quarkus/reactive/pg/client/PgPoolCreator.java | 3 ++- .../pg/client/runtime/PgPoolRecorder.java | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/src/main/asciidoc/reactive-sql-clients.adoc b/docs/src/main/asciidoc/reactive-sql-clients.adoc index 50685c3ccd5356..59c9d145f2c46a 100644 --- a/docs/src/main/asciidoc/reactive-sql-clients.adoc +++ b/docs/src/main/asciidoc/reactive-sql-clients.adoc @@ -755,7 +755,7 @@ import jakarta.inject.Singleton; import io.quarkus.reactive.pg.client.PgPoolCreator; import io.vertx.pgclient.PgConnectOptions; -import io.vertx.pgclient.PgPool; +import io.vertx.pgclient.PgPoolOptions; import io.vertx.sqlclient.PoolOptions; @Singleton @@ -764,7 +764,7 @@ public class CustomPgPoolCreator implements PgPoolCreator { @Override public PgPool create(Input input) { PgConnectOptions connectOptions = input.pgConnectOptions(); - PoolOptions poolOptions = input.poolOptions(); + PgPoolOptions poolOptions = input.poolOptions(); // Customize connectOptions, poolOptions or both, as required return PgPool.pool(input.vertx(), connectOptions, poolOptions); } diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java index 34bb67c732742e..7612924b5a582e 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java @@ -6,6 +6,7 @@ import io.vertx.core.Vertx; import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.PgPool; +import io.vertx.pgclient.impl.PgPoolOptions; import io.vertx.sqlclient.PoolOptions; /** @@ -25,7 +26,7 @@ interface Input { Vertx vertx(); - PoolOptions poolOptions(); + PgPoolOptions poolOptions(); List pgConnectOptionsList(); } diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java index d7763a6d5f4e2f..058451b3ab8bca 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java @@ -36,6 +36,7 @@ import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.PgPool; import io.vertx.pgclient.SslMode; +import io.vertx.pgclient.impl.PgPoolOptions; import io.vertx.sqlclient.PoolOptions; @Recorder @@ -73,7 +74,7 @@ private PgPool initialize(Vertx vertx, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig) { - PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, + PgPoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); List pgConnectOptionsList = toPgConnectOptions(dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); @@ -86,7 +87,7 @@ private PgPool initialize(Vertx vertx, return createPool(vertx, poolOptions, pgConnectOptionsList, dataSourceName); } - private PoolOptions toPoolOptions(Integer eventLoopCount, + private PgPoolOptions toPoolOptions(Integer eventLoopCount, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig) { @@ -113,7 +114,7 @@ private PoolOptions toPoolOptions(Integer eventLoopCount, poolOptions.setEventLoopSize(Math.max(0, eventLoopCount)); } - return poolOptions; + return new PgPoolOptions(poolOptions); } private List toPgConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, @@ -196,7 +197,7 @@ private List toPgConnectOptions(DataSourceRuntimeConfig dataSo return pgConnectOptionsList; } - private PgPool createPool(Vertx vertx, PoolOptions poolOptions, List pgConnectOptionsList, + private PgPool createPool(Vertx vertx, PgPoolOptions poolOptions, List pgConnectOptionsList, String dataSourceName) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { @@ -214,10 +215,10 @@ private PgPool createPool(Vertx vertx, PoolOptions poolOptions, List pgConnectOptionsList; - public DefaultInput(Vertx vertx, PoolOptions poolOptions, List pgConnectOptionsList) { + public DefaultInput(Vertx vertx, PgPoolOptions poolOptions, List pgConnectOptionsList) { this.vertx = vertx; this.poolOptions = poolOptions; this.pgConnectOptionsList = pgConnectOptionsList; @@ -229,7 +230,7 @@ public Vertx vertx() { } @Override - public PoolOptions poolOptions() { + public PgPoolOptions poolOptions() { return poolOptions; } From 16a4f911982c7291942d53fd208bab42b6100a6b Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 21 Apr 2023 17:13:10 +0100 Subject: [PATCH 116/333] Set correct OIDC Google principal claim --- .../src/main/java/io/quarkus/oidc/runtime/OidcUtils.java | 3 +++ .../io/quarkus/oidc/runtime/providers/KnownOidcProviders.java | 1 + .../src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java | 3 +++ 3 files changed, 7 insertions(+) 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 1141e45b6418f3..bf5e70200cf6a6 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 @@ -429,6 +429,9 @@ static OidcTenantConfig mergeTenantConfig(OidcTenantConfig tenant, OidcTenantCon if (tenant.token.issuer.isEmpty()) { tenant.token.issuer = provider.token.issuer; } + if (tenant.token.principalClaim.isEmpty()) { + tenant.token.principalClaim = provider.token.principalClaim; + } return tenant; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java index 6c94a35a371539..38909dfce88412 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java @@ -63,6 +63,7 @@ private static OidcTenantConfig google() { ret.setAuthServerUrl("https://accounts.google.com"); ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); ret.getAuthentication().setScopes(List.of("openid", "email", "profile")); + ret.getToken().setPrincipalClaim("name"); return ret; } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java index 37cc57d5294e9b..e82bea9dd13688 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java @@ -195,6 +195,7 @@ public void testAcceptGoogleProperties() throws Exception { assertEquals(OidcUtils.DEFAULT_TENANT_ID, config.getTenantId().get()); assertEquals(ApplicationType.WEB_APP, config.getApplicationType().get()); assertEquals("https://accounts.google.com", config.getAuthServerUrl().get()); + assertEquals("name", config.getToken().getPrincipalClaim().get()); assertEquals(List.of("openid", "email", "profile"), config.authentication.scopes.get()); } @@ -206,12 +207,14 @@ public void testOverrideGoogleProperties() throws Exception { tenant.setApplicationType(ApplicationType.HYBRID); tenant.setAuthServerUrl("http://localhost/wiremock"); tenant.authentication.setScopes(List.of("write")); + tenant.token.setPrincipalClaim("firstname"); OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.GOOGLE)); assertEquals(OidcUtils.DEFAULT_TENANT_ID, config.getTenantId().get()); assertEquals(ApplicationType.HYBRID, config.getApplicationType().get()); assertEquals("http://localhost/wiremock", config.getAuthServerUrl().get()); + assertEquals("firstname", config.getToken().getPrincipalClaim().get()); assertEquals(List.of("write"), config.authentication.scopes.get()); } From 89865da3e2a4ad021a759ad85dc1b44caf131e06 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 21 Apr 2023 17:28:38 +0100 Subject: [PATCH 117/333] Update OIDC Dev code to recognize quarkus.oidc.provider --- .../devservices/OidcDevConsoleProcessor.java | 51 ++++++++++++++----- .../runtime/OidcConfigPropertySupplier.java | 14 ++++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java index 84f96c04657684..90e9bea7c86675 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.oidc.deployment.devservices; +import java.util.Optional; import java.util.Set; import org.eclipse.microprofile.config.ConfigProvider; @@ -16,8 +17,12 @@ import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; +import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.OidcTenantConfig.ApplicationType; +import io.quarkus.oidc.OidcTenantConfig.Provider; import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.deployment.OidcBuildTimeConfig; +import io.quarkus.oidc.runtime.providers.KnownOidcProviders; import io.quarkus.runtime.configuration.ConfigUtils; import io.vertx.core.Vertx; import io.vertx.core.http.HttpHeaders; @@ -34,6 +39,7 @@ public class OidcDevConsoleProcessor extends AbstractDevConsoleProcessor { private static final String DISCOVERY_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "discovery-enabled"; private static final String AUTH_SERVER_URL_CONFIG_KEY = CONFIG_PREFIX + "auth-server-url"; private static final String APP_TYPE_CONFIG_KEY = CONFIG_PREFIX + "application-type"; + private static final String OIDC_PROVIDER_CONFIG_KEY = "quarkus.oidc.provider"; private static final String SERVICE_APP_TYPE = "service"; // Well-known providers @@ -51,7 +57,12 @@ void prepareOidcDevConsole(BuildProducer devCon CuratedApplicationShutdownBuildItem closeBuildItem, BuildProducer devConsoleRoute, Capabilities capabilities, CurateOutcomeBuildItem curateOutcomeBuildItem) { - if (isOidcTenantEnabled() && isAuthServerUrlSet() && isClientIdSet()) { + if (!isOidcTenantEnabled() || !isClientIdSet()) { + return; + } + final OidcTenantConfig providerConfig = getProviderConfig(); + final String authServerUrl = getAuthServerUrl(providerConfig); + if (authServerUrl != null) { if (vertxInstance == null) { vertxInstance = Vertx.vertx(); @@ -72,13 +83,6 @@ public void run() { closeBuildItem.addCloseTask(closeTask, true); } - String authServerUrl = null; - try { - authServerUrl = getConfigProperty(AUTH_SERVER_URL_CONFIG_KEY); - } catch (Exception ex) { - // It is not possible to initialize OIDC Dev Console UI without being able to access this property at the build time - return; - } JsonObject metadata = null; if (isDiscoveryEnabled()) { metadata = discoverMetadata(authServerUrl); @@ -96,7 +100,7 @@ public void run() { devConsoleRuntimeInfo, curateOutcomeBuildItem, providerName, - getApplicationType(), + getApplicationType(providerConfig), oidcConfig.devui.grant.type.isPresent() ? oidcConfig.devui.grant.type.get().getGrantType() : "code", metadata != null ? metadata.getString("authorization_endpoint") : null, metadata != null ? metadata.getString("token_endpoint") : null, @@ -150,7 +154,7 @@ private JsonObject discoverMetadata(String authServerUrl) { } } - private String getConfigProperty(String name) { + private static String getConfigProperty(String name) { return ConfigProvider.getConfig().getValue(name, String.class); } @@ -170,12 +174,31 @@ private static boolean isClientIdSet() { return ConfigUtils.isPropertyPresent(CLIENT_ID_CONFIG_KEY); } - private static boolean isAuthServerUrlSet() { - return ConfigUtils.isPropertyPresent(AUTH_SERVER_URL_CONFIG_KEY); + private static String getAuthServerUrl(OidcTenantConfig providerConfig) { + try { + return getConfigProperty(AUTH_SERVER_URL_CONFIG_KEY); + } catch (Exception ex) { + return providerConfig != null ? providerConfig.authServerUrl.get() : null; + } } - private static String getApplicationType() { - return ConfigProvider.getConfig().getOptionalValue(APP_TYPE_CONFIG_KEY, String.class).orElse(SERVICE_APP_TYPE); + private static String getApplicationType(OidcTenantConfig providerConfig) { + Optional appType = ConfigProvider.getConfig().getOptionalValue(APP_TYPE_CONFIG_KEY, + ApplicationType.class); + if (appType.isEmpty() && providerConfig != null) { + appType = providerConfig.applicationType; + } + return appType.isPresent() ? appType.get().name().toLowerCase() : SERVICE_APP_TYPE; + } + + private static OidcTenantConfig getProviderConfig() { + try { + Provider p = ConfigProvider.getConfig().getValue(OIDC_PROVIDER_CONFIG_KEY, Provider.class); + return KnownOidcProviders.provider(p); + } catch (Exception ex) { + return null; + } + } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java index bc7384b392e10a..c2494749fa2706 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java @@ -6,12 +6,15 @@ import org.eclipse.microprofile.config.ConfigProvider; +import io.quarkus.oidc.OidcTenantConfig.Provider; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.runtime.providers.KnownOidcProviders; public class OidcConfigPropertySupplier implements Supplier { private static final String AUTH_SERVER_URL_CONFIG_KEY = "quarkus.oidc.auth-server-url"; - private static final String END_SESSION_PATH_KEY = "quarkus.oidc.end-session-path"; + private static final String END_SESSION_PATH_CONFIG_KEY = "quarkus.oidc.end-session-path"; + private static final String OIDC_PROVIDER_CONFIG_KEY = "quarkus.oidc.provider"; private static final String SCOPES_KEY = "quarkus.oidc.authentication.scopes"; private String oidcConfigProperty; private String defaultValue; @@ -37,7 +40,7 @@ public OidcConfigPropertySupplier(String oidcConfigProperty, String defaultValue @Override public String get() { - if (defaultValue != null || END_SESSION_PATH_KEY.equals(oidcConfigProperty)) { + if (defaultValue != null || END_SESSION_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { Optional value = ConfigProvider.getConfig().getOptionalValue(oidcConfigProperty, String.class); if (value.isPresent()) { return checkUrlProperty(value); @@ -45,6 +48,13 @@ public String get() { return defaultValue; } else if (SCOPES_KEY.equals(oidcConfigProperty)) { Optional> scopes = ConfigProvider.getConfig().getOptionalValues(oidcConfigProperty, String.class); + if (scopes.isEmpty()) { + Optional provider = ConfigProvider.getConfig().getOptionalValue(OIDC_PROVIDER_CONFIG_KEY, + Provider.class); + if (provider.isPresent()) { + scopes = KnownOidcProviders.provider(provider.get()).authentication.scopes; + } + } if (scopes.isPresent()) { return OidcCommonUtils.urlEncode(String.join(" ", scopes.get())); } else { From 1517e14af9df62236f9410f669203b99c408bd95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crocquesel?= <88554524+scrocquesel@users.noreply.github.com> Date: Fri, 21 Apr 2023 18:59:22 +0200 Subject: [PATCH 118/333] Fix generateJniConfig step --- core/builder/src/main/java/io/quarkus/builder/Json.java | 2 +- .../quarkus/deployment/steps/NativeImageJNIConfigStep.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/builder/src/main/java/io/quarkus/builder/Json.java b/core/builder/src/main/java/io/quarkus/builder/Json.java index d6bc9ab46465b6..1973e3a0411c1f 100644 --- a/core/builder/src/main/java/io/quarkus/builder/Json.java +++ b/core/builder/src/main/java/io/quarkus/builder/Json.java @@ -187,7 +187,7 @@ void addInternal(Object value) { } } - boolean isEmpty() { + public boolean isEmpty() { return isValuesEmpty(values); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java index b6381bc9b5a5d4..70e744f9b3ec72 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java @@ -40,10 +40,10 @@ void generateJniConfig(BuildProducer jniConfig, json.put("name", entry.getKey()); JniInfo info = entry.getValue(); + JsonArrayBuilder methodsArray = Json.array(); if (info.constructors) { json.put("allDeclaredConstructors", true); } else if (!info.ctorSet.isEmpty()) { - JsonArrayBuilder methodsArray = Json.array(); for (JniRuntimeAccessMethodBuildItem ctor : info.ctorSet) { JsonObjectBuilder methodObject = Json.object(); methodObject.put("name", ctor.getName()); @@ -54,12 +54,10 @@ void generateJniConfig(BuildProducer jniConfig, methodObject.put("parameterTypes", paramsArray); methodsArray.add(methodObject); } - json.put("methods", methodsArray); } if (info.methods) { json.put("allDeclaredMethods", true); } else if (!info.methodSet.isEmpty()) { - JsonArrayBuilder methodsArray = Json.array(); for (JniRuntimeAccessMethodBuildItem method : info.methodSet) { JsonObjectBuilder methodObject = Json.object(); methodObject.put("name", method.getName()); @@ -70,8 +68,11 @@ void generateJniConfig(BuildProducer jniConfig, methodObject.put("parameterTypes", paramsArray); methodsArray.add(methodObject); } + } + if (!methodsArray.isEmpty()) { json.put("methods", methodsArray); } + if (info.fields) { json.put("allDeclaredFields", true); } else if (!info.fieldSet.isEmpty()) { From 57c8dc523b30b58b2ee535312c5a6a6832abd7bd Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 21 Apr 2023 23:17:35 +0100 Subject: [PATCH 119/333] Fix OIDC UserInfo to better handle null, array, map --- .../runtime/AbstractJsonObjectResponse.java | 8 ++-- .../io/quarkus/oidc/runtime/UserInfoTest.java | 40 ++++++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java index c01494cc941c56..6dc0e7d4a2ca59 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java @@ -37,16 +37,16 @@ public Boolean getBoolean(String name) { } public Long getLong(String name) { - JsonNumber number = json.getJsonNumber(name); + JsonNumber number = contains(name) ? json.getJsonNumber(name) : null; return number != null ? number.longValue() : null; } public JsonArray getArray(String name) { - return json.getJsonArray(name); + return contains(name) ? json.getJsonArray(name) : null; } public JsonObject getObject(String name) { - return json.getJsonObject(name); + return contains(name) ? json.getJsonObject(name) : null; } public JsonObject getJsonObject() { @@ -58,7 +58,7 @@ public Object get(String name) { } public boolean contains(String propertyName) { - return json.containsKey(propertyName); + return json.containsKey(propertyName) && !json.isNull(propertyName); } public Set getPropertyNames() { diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java index 83d4f6ffba34fe..dd28befc9a69f8 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java @@ -1,9 +1,13 @@ package io.quarkus.oidc.runtime; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; + import org.junit.jupiter.api.Test; import io.quarkus.oidc.UserInfo; @@ -12,7 +16,11 @@ public class UserInfoTest { UserInfo userInfo = new UserInfo( "{" + "\"name\": \"alice\"," - + "\"admin\": true" + + "\"admin\": true," + + "\"email\": null," + + "\"id\": 1234," + + "\"permissions\": [\"read\", \"write\"]," + + "\"scopes\": {\"scope\": \"see\"}" + "}"); @Test @@ -26,4 +34,34 @@ public void testGetBoolean() { assertTrue(userInfo.getBoolean("admin")); assertNull(userInfo.getBoolean("admins")); } + + @Test + public void testGetLong() { + assertEquals(1234, userInfo.getLong("id")); + assertNull(userInfo.getLong("ids")); + } + + @Test + public void testGetArray() { + JsonArray array = userInfo.getArray("permissions"); + assertNotNull(array); + assertEquals(2, array.size()); + assertEquals("read", array.getString(0)); + assertEquals("write", array.getString(1)); + assertNull(userInfo.getArray("permit")); + } + + @Test + public void testGetObject() { + JsonObject map = userInfo.getObject("scopes"); + assertNotNull(map); + assertEquals(1, map.size()); + assertEquals("see", map.getString("scope")); + assertNull(userInfo.getObject("scope")); + } + + @Test + public void testGetNullProperty() { + assertNull(userInfo.getString("email")); + } } From 50bd92e8e5c80eae25128b06541d5e00d31436d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sat, 22 Apr 2023 21:08:48 +0200 Subject: [PATCH 120/333] Disable DEV UI for remote dev mode --- .../builditem/LaunchModeBuildItem.java | 9 +++++++ .../devui/deployment/DevUIProcessor.java | 25 +++++++++++++++++++ .../io/quarkus/maven/it/RemoteDevMojoIT.java | 1 + 3 files changed, 35 insertions(+) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java index 20fad98e87f289..e25cef971c186b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java @@ -43,6 +43,15 @@ public Optional getDevModeType() { return devModeType; } + /** + * Whether the development mode type is not local. + * + * @return true if {@link #getDevModeType()} is not {@link DevModeType#LOCAL} + */ + public boolean isNotLocalDevModeType() { + return devModeType.orElse(null) != DevModeType.LOCAL; + } + /** * An Auxiliary Application is a second application running in the same JVM as a primary application. *

    diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java index 4c2e6707cb3d3f..e6a67155f6e516 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java @@ -38,6 +38,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.dev.console.DevConsoleManager; @@ -124,9 +125,14 @@ void registerDevUiHandlers( List staticContentBuildItems, BuildProducer routeProducer, DevUIRecorder recorder, + LaunchModeBuildItem launchModeBuildItem, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, ShutdownContextBuildItem shutdownContext) throws IOException { + if (launchModeBuildItem.isNotLocalDevModeType()) { + return; + } + // Websocket for JsonRPC comms routeProducer.produce( nonApplicationRootPathBuildItem @@ -215,6 +221,7 @@ void registerDevUiHandlers( void additionalBean(BuildProducer additionalBeanProducer, BuildProducer additionalIndexProducer, List jsonRPCProvidersBuildItems) { + additionalBeanProducer.produce(AdditionalBeanBuildItem.builder() .addBeanClass(JsonRpcRouter.class) .setUnremovable().build()); @@ -244,10 +251,15 @@ void additionalBean(BuildProducer additionalBeanProduce @BuildStep(onlyIf = IsDevelopment.class) void findAllJsonRPCMethods(BuildProducer jsonRPCMethodsProvider, BuildProducer buildTimeConstProducer, + LaunchModeBuildItem launchModeBuildItem, CombinedIndexBuildItem combinedIndexBuildItem, CurateOutcomeBuildItem curateOutcomeBuildItem, List jsonRPCProvidersBuildItems) { + if (launchModeBuildItem.isNotLocalDevModeType()) { + return; + } + IndexView index = combinedIndexBuildItem.getIndex(); Map> extensionMethodsMap = new HashMap<>(); // All methods so that we can build the reflection @@ -354,11 +366,19 @@ void createJsonRpcRouter(DevUIRecorder recorder, void getAllExtensions(List cardPageBuildItems, List menuPageBuildItems, List footerPageBuildItems, + LaunchModeBuildItem launchModeBuildItem, CurateOutcomeBuildItem curateOutcomeBuildItem, BuildProducer extensionsProducer, BuildProducer webJarBuildProducer, BuildProducer devUIWebJarProducer) { + if (launchModeBuildItem.isNotLocalDevModeType()) { + // produce extension build item as cascade of build steps rely on it + var emptyExtensionBuildItem = new ExtensionsBuildItem(List.of(), List.of(), List.of(), List.of()); + extensionsProducer.produce(emptyExtensionBuildItem); + return; + } + // First create the static resources for our own internal components webJarBuildProducer.produce(WebJarBuildItem.builder() .artifactKey(UI_JAR) @@ -560,9 +580,14 @@ public WebJarResourcesFilter.FilterResult apply(String fileName, InputStream fil @BuildStep(onlyIf = IsDevelopment.class) void createAllRoutes(WebJarResultsBuildItem webJarResultsBuildItem, + LaunchModeBuildItem launchModeBuildItem, List devUIWebJarBuiltItems, BuildProducer devUIRoutesProducer) { + if (launchModeBuildItem.isNotLocalDevModeType()) { + return; + } + for (DevUIWebJarBuildItem devUIWebJarBuiltItem : devUIWebJarBuiltItems) { WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem .byArtifactKey(devUIWebJarBuiltItem.getArtifactKey()); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java index 8ecc9ff41e6e03..9732478d18c478 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java @@ -57,6 +57,7 @@ public void testThatTheApplicationIsReloadedOnJavaChange() //also verify that the dev ui console is disabled DevModeTestUtils.getHttpResponse("/q/dev-v1", 404, 10, TimeUnit.SECONDS); + DevModeTestUtils.getHttpResponse("/q/dev-ui", 404, 10, TimeUnit.SECONDS); } @Test From 7ef7ffa69c1a0cfd532eb606eb0aa0e7c873a814 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Sun, 23 Apr 2023 12:06:43 +0900 Subject: [PATCH 121/333] Fix a typo in security-openid-connect-multitenancy.adoc --- .../src/main/asciidoc/security-openid-connect-multitenancy.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index e3f4fba9d24756..a2f21633539e00 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -211,7 +211,7 @@ quarkus.http.auth.permission.authenticated.paths=/* quarkus.http.auth.permission.authenticated.policy=authenticated ---- -The first configuration is the default tenant configuration that should be used when the tenant can not be inferred from the request. Note that a `%prod` prodile prefix is used with `quarkus.oidc.auth-server-url` - it is done to support testing a multi-tenant application with `Dev Services For Keycloak`. This configuration is using a Keycloak instance to authenticate users. +The first configuration is the default tenant configuration that should be used when the tenant can not be inferred from the request. Note that a `%prod` profile prefix is used with `quarkus.oidc.auth-server-url` - it is done to support testing a multi-tenant application with `Dev Services For Keycloak`. This configuration is using a Keycloak instance to authenticate users. The second configuration is provided by `TenantConfigResolver`, it is the configuration that will be used when an incoming request is mapped to the tenant `tenant-a`. From 97e101b526ab82db414e6180e6bd3c20d3d27c85 Mon Sep 17 00:00:00 2001 From: Jose Date: Sun, 23 Apr 2023 08:46:08 +0200 Subject: [PATCH 122/333] Support blocking exception mappers in REST Client Reactive Fix https://github.com/quarkusio/quarkus/issues/30312 --- .../main/asciidoc/rest-client-reactive.adoc | 62 +++++ .../ClientExceptionMapperHandler.java | 20 +- .../client/reactive/deployment/DotNames.java | 3 + .../RestClientReactiveProcessor.java | 21 ++ .../error/BlockingExceptionMapperTest.java | 253 ++++++++++++++++++ .../ClientUseWorkerExecutorRestHandler.java | 41 +++ .../MicroProfileRestClientResponseFilter.java | 51 +++- .../reactive/runtime/RestClientRecorder.java | 10 + .../scanning/ClientEndpointIndexer.java | 18 +- .../reactive/common/processor/JandexUtil.java | 35 +++ .../startup/RuntimeResourceDeployment.java | 2 +- 11 files changed, 495 insertions(+), 21 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index c25f0f7dffdffa..f4ae3aef37ec6a 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -985,6 +985,68 @@ Naturally this handling is per REST Client. `@ClientExceptionMapper` uses the de NOTE: Methods annotated with `@ClientExceptionMapper` can also take a `java.lang.reflect.Method` parameter which is useful if the exception mapping code needs to know the REST Client method that was invoked and caused the exception mapping code to engage. +=== Using @Blocking annotation in exception mappers + +In cases that warrant using `InputStream` as the return type of REST Client method (such as when large amounts of data need to be read): + +[source, java] +---- +@Path("/echo") +@RegisterRestClient +public interface EchoClient { + + @GET + InputStream get(); +} +---- + +This will work as expected, but if you try to read this InputStream object in a custom exception mapper, you will receive a `BlockingNotAllowedException` exception. This is because `ResponseExceptionMapper` classes are run on the Event Loop thread executor by default - which does not allow to perform IO operations. + +To make your exception mapper blocking, you can annotate the exception mapper with the `@Blocking` annotation: + +[source, java] +---- +@Provider +@Blocking <1> +public class MyResponseExceptionMapper implements ResponseExceptionMapper { + + @Override + public RuntimeException toThrowable(Response response) { + if (response.getStatus() == 500) { + response.readEntity(String.class); <2> + return new RuntimeException("The remote service responded with HTTP 500"); + } + return null; + } +} +---- + +<1> With the `@Blocking` annotation, the MyResponseExceptionMapper exception mapper will be executed in the worker thread pool. +<2> Reading the entity is now allowed because we're executing the mapper on the worker thread pool. + +Note that you can also use the `@Blocking` annotation when using @ClientExceptionMapper: + +[source, java] +---- +@Path("/echo") +@RegisterRestClient +public interface EchoClient { + + @GET + InputStream get(); + + @ClientExceptionMapper + @Blocking + static RuntimeException toException(Response response) { + if (response.getStatus() == 500) { + response.readEntity(String.class); + return new RuntimeException("The remote service responded with HTTP 500"); + } + return null; + } +} +---- + [[multipart]] == Multipart Form support diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java index 583aa2cfa6883b..ae97af6d7c3b3a 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java @@ -90,12 +90,6 @@ GeneratedClassResult generateResponseExceptionMapper(AnnotationInstance instance throw new IllegalStateException(message); } - StringBuilder sigBuilder = new StringBuilder(); - sigBuilder.append(targetMethod.name()).append("_").append(targetMethod.returnType().name().toString()); - for (Type i : targetMethod.parameterTypes()) { - sigBuilder.append(i.name().toString()); - } - int priority = Priorities.USER; AnnotationValue priorityAnnotationValue = instance.value("priority"); if (priorityAnnotationValue != null) { @@ -103,8 +97,7 @@ GeneratedClassResult generateResponseExceptionMapper(AnnotationInstance instance } ClassInfo restClientInterfaceClassInfo = targetMethod.declaringClass(); - String generatedClassName = restClientInterfaceClassInfo.name().toString() + "_" + targetMethod.name() + "_" - + "ResponseExceptionMapper" + "_" + HashUtil.sha1(sigBuilder.toString()); + String generatedClassName = getGeneratedClassName(targetMethod); try (ClassCreator cc = ClassCreator.builder().classOutput(classOutput).className(generatedClassName) .interfaces(ResteasyReactiveResponseExceptionMapper.class).build()) { MethodCreator toThrowable = cc.getMethodCreator("toThrowable", Throwable.class, Response.class, @@ -143,6 +136,17 @@ GeneratedClassResult generateResponseExceptionMapper(AnnotationInstance instance return new GeneratedClassResult(restClientInterfaceClassInfo.name().toString(), generatedClassName, priority); } + public static String getGeneratedClassName(MethodInfo methodInfo) { + StringBuilder sigBuilder = new StringBuilder(); + sigBuilder.append(methodInfo.name()).append("_").append(methodInfo.returnType().name().toString()); + for (Type i : methodInfo.parameterTypes()) { + sigBuilder.append(i.name().toString()); + } + + return methodInfo.declaringClass().name().toString() + "_" + methodInfo.name() + "_" + + "ResponseExceptionMapper" + "_" + HashUtil.sha1(sigBuilder.toString()); + } + private static boolean ignoreAnnotation(MethodInfo methodInfo) { // ignore the annotation if it's placed on a Kotlin companion class // this is not a problem since the Kotlin compiler will also place the annotation the static method interface method diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java index 1d0b3f5d45d2c8..df1a36f223decc 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java @@ -10,6 +10,7 @@ import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.annotation.RegisterProviders; +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; import org.jboss.jandex.DotName; import io.quarkus.rest.client.reactive.ClientExceptionMapper; @@ -32,6 +33,8 @@ public class DotNames { public static final DotName CLIENT_EXCEPTION_MAPPER = DotName.createSimple(ClientExceptionMapper.class.getName()); public static final DotName CLIENT_REDIRECT_HANDLER = DotName.createSimple(ClientRedirectHandler.class.getName()); + public static final DotName RESPONSE_EXCEPTION_MAPPER = DotName.createSimple(ResponseExceptionMapper.class.getName()); + static final DotName METHOD = DotName.createSimple(Method.class.getName()); private DotNames() { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 9f4d51271242bc..d456cbea6cdb46 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -10,9 +10,12 @@ import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS; +import static io.quarkus.rest.client.reactive.deployment.DotNames.RESPONSE_EXCEPTION_MAPPER; import static java.util.Arrays.asList; import static java.util.stream.Collectors.*; import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.CDI_WRAPPER_SUFFIX; +import static org.jboss.resteasy.reactive.common.processor.JandexUtil.isImplementorOf; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.BLOCKING; import static org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner.BUILTIN_HTTP_ANNOTATIONS_TO_METHOD; import java.lang.annotation.RetentionPolicy; @@ -497,6 +500,24 @@ void addRestClientBeans(Capabilities capabilities, } } } + + Set blockingClassNames = new HashSet<>(); + Set registerBlockingClasses = new HashSet<>(index.getAnnotations(BLOCKING)); + for (AnnotationInstance registerBlockingClass : registerBlockingClasses) { + AnnotationTarget target = registerBlockingClass.target(); + if (target.kind() == AnnotationTarget.Kind.CLASS + && isImplementorOf(index, target.asClass(), RESPONSE_EXCEPTION_MAPPER)) { + // Watch for @Blocking annotations in classes that implements ResponseExceptionMapper: + blockingClassNames.add(target.asClass().toString()); + } else if (target.kind() == AnnotationTarget.Kind.METHOD + && target.asMethod().annotation(CLIENT_EXCEPTION_MAPPER) != null) { + // Watch for @Blocking annotations in methods that are also annotated with @ClientExceptionMapper: + blockingClassNames.add(ClientExceptionMapperHandler.getGeneratedClassName(target.asMethod())); + } + } + + recorder.setBlockingClassNames(blockingClassNames); + if (LaunchMode.current() == LaunchMode.DEVELOPMENT) { recorder.setConfigKeys(configKeys); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java new file mode 100644 index 00000000000000..47b82d59933302 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java @@ -0,0 +1,253 @@ +package io.quarkus.rest.client.reactive.error; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.rest.client.reactive.ClientExceptionMapper; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Blocking; +import io.vertx.core.Context; + +public class BlockingExceptionMapperTest { + + private static final AtomicBoolean EVENT_LOOP_THREAD_USED_BY_MAPPER = new AtomicBoolean(); + private static final int STATUS_FOR_BLOCKING_MAPPER = 501; + private static final int STATUS_FOR_NON_BLOCKING_MAPPER = 500; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Client.class, + ClientUsingNotBlockingExceptionMapper.class, + ClientUsingBlockingExceptionMapper.class, + ClientUsingBlockingExceptionMapperWithAnnotation.class, + ClientUsingBothExceptionMappers.class, + NotBlockingExceptionMapper.class, + BlockingExceptionMapper.class, + ClientResource.class, + Resource.class) + .addAsResource( + new StringAsset(setUrlForClass(ClientUsingNotBlockingExceptionMapper.class) + "\n" + + setUrlForClass(ClientUsingBlockingExceptionMapper.class) + "\n" + + setUrlForClass(ClientUsingBlockingExceptionMapperWithAnnotation.class) + "\n" + + setUrlForClass(ClientUsingBothExceptionMappers.class) + "\n"), + "application.properties")); + + public static final String ERROR_MESSAGE = "The entity was not found"; + + @RestClient + ClientUsingNotBlockingExceptionMapper clientUsingNotBlockingExceptionMapper; + + @RestClient + ClientUsingBlockingExceptionMapper clientUsingBlockingExceptionMapper; + + @RestClient + ClientUsingBlockingExceptionMapperWithAnnotation clientUsingBlockingExceptionMapperWithAnnotation; + + @RestClient + ClientUsingBothExceptionMappers clientUsingBothExceptionMappers; + + @BeforeEach + public void setup() { + EVENT_LOOP_THREAD_USED_BY_MAPPER.set(false); + } + + @Disabled("This test randomly fails because https://github.com/quarkusio/quarkus/issues/32839") + @Test + public void shouldUseEventLoopByDefault() { + assertThrows(BlockingNotAllowedException.class, clientUsingNotBlockingExceptionMapper::nonBlocking); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); + } + + @Test + public void shouldUseWorkerThreadIfExceptionMapperIsAnnotatedWithBlocking() { + RuntimeException exception = assertThrows(RuntimeException.class, clientUsingBlockingExceptionMapper::blocking); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); + assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE); + } + + @Test + public void shouldUseWorkerThreadOnlyIfExceptionMapperIsAnnotatedWithBlockingIsUsed() { + // To be uncommented after https://github.com/quarkusio/quarkus/issues/32839 is fixed: + // assertThrows(BlockingNotAllowedException.class, clientUsingBothExceptionMappers::nonBlocking); + // assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); + + RuntimeException exception = assertThrows(RuntimeException.class, clientUsingBothExceptionMappers::blocking); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); + assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE); + } + + @Test + public void shouldUseWorkerThreadWhenClientIsInjected() { + // To be uncommented after https://github.com/quarkusio/quarkus/issues/32839 is fixed: + // given().get("/client/non-blocking").then().statusCode(500); + // assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); + + given().get("/client/blocking").then().statusCode(500); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); + } + + @Test + public void shouldUseWorkerThreadIfExceptionMapperIsAnnotatedWithBlockingAndUsingClientExceptionMapper() { + RuntimeException exception = assertThrows(RuntimeException.class, + clientUsingBlockingExceptionMapperWithAnnotation::blocking); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); + assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE); + } + + @Test + public void shouldUseWorkerThreadUsingProgrammaticApproach() { + var client = RestClientBuilder.newBuilder() + .baseUri(UriBuilder.fromUri("http://localhost:8081").build()) + .register(BlockingExceptionMapper.class) + .build(Client.class); + + RuntimeException exception = assertThrows(RuntimeException.class, client::blocking); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); + assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE); + } + + @Path("/error") + @RegisterRestClient + public interface Client { + @GET + @Path("/blocking") + InputStream blocking(); + } + + @Path("/error") + @RegisterRestClient + @RegisterProvider(NotBlockingExceptionMapper.class) + public interface ClientUsingNotBlockingExceptionMapper { + + @GET + @Path("/non-blocking") + InputStream nonBlocking(); + } + + @Path("/error") + @RegisterRestClient + @RegisterProvider(BlockingExceptionMapper.class) + public interface ClientUsingBlockingExceptionMapper { + @GET + @Path("/blocking") + InputStream blocking(); + } + + @Path("/error") + @RegisterRestClient + @RegisterProvider(NotBlockingExceptionMapper.class) + @RegisterProvider(BlockingExceptionMapper.class) + public interface ClientUsingBothExceptionMappers { + @GET + @Path("/blocking") + InputStream blocking(); + + @GET + @Path("/non-blocking") + InputStream nonBlocking(); + } + + @Path("/error") + @RegisterRestClient + public interface ClientUsingBlockingExceptionMapperWithAnnotation { + @GET + @Path("/blocking") + InputStream blocking(); + + @Blocking + @ClientExceptionMapper + static RuntimeException map(Response response) { + EVENT_LOOP_THREAD_USED_BY_MAPPER.set(Context.isOnEventLoopThread()); + return new RuntimeException(response.readEntity(String.class)); + } + } + + public static class NotBlockingExceptionMapper implements ResponseExceptionMapper { + + @Override + public boolean handles(int status, MultivaluedMap headers) { + return status == STATUS_FOR_NON_BLOCKING_MAPPER; + } + + @Override + public Exception toThrowable(Response response) { + EVENT_LOOP_THREAD_USED_BY_MAPPER.set(Context.isOnEventLoopThread()); + // Reading InputStream in the Event Loop throws the BlockingNotAllowedException exception + response.readEntity(String.class); + return null; + } + } + + @Blocking + public static class BlockingExceptionMapper implements ResponseExceptionMapper { + @Override + public boolean handles(int status, MultivaluedMap headers) { + return status == STATUS_FOR_BLOCKING_MAPPER; + } + + @Override + public Exception toThrowable(Response response) { + EVENT_LOOP_THREAD_USED_BY_MAPPER.set(Context.isOnEventLoopThread()); + return new RuntimeException(response.readEntity(String.class)); + } + } + + @Path("/error") + public static class Resource { + + @GET + @Path("/blocking") + public Response blocking() { + return Response.status(STATUS_FOR_BLOCKING_MAPPER).entity(ERROR_MESSAGE).build(); + } + + @GET + @Path("/non-blocking") + public Response nonBlocking() { + return Response.status(STATUS_FOR_NON_BLOCKING_MAPPER).entity(ERROR_MESSAGE).build(); + } + } + + @Path("/client") + public static class ClientResource { + + @RestClient + ClientUsingBothExceptionMappers clientUsingBothExceptionMappers; + + @GET + @Path("/blocking") + public void callBlocking() { + clientUsingBothExceptionMappers.blocking(); + } + + @GET + @Path("/non-blocking") + public void callNonBlocking() { + clientUsingBothExceptionMappers.nonBlocking(); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java new file mode 100644 index 00000000000000..d8cf3b8305253f --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java @@ -0,0 +1,41 @@ +package io.quarkus.rest.client.reactive.runtime; + +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; + +import io.quarkus.runtime.ExecutorRecorder; +import io.vertx.core.Context; + +/** + * This is added by the Reactive Rest Client if the `@Blocking` annotation is used in some scenarios. For example, when users + * provide a custom ResponseExceptionMapper that is annotates with the `@Blocking` annotation. + * + * Then this handler is applied, the execution of the next handlers will use the worker thread pool. + */ +public class ClientUseWorkerExecutorRestHandler implements ClientRestHandler { + + private volatile Executor executor; + private final Supplier supplier = new Supplier() { + @Override + public Executor get() { + return ExecutorRecorder.getCurrent(); + } + }; + + @Override + public void handle(RestClientRequestContext requestContext) throws Exception { + if (!Context.isOnEventLoopThread()) { + return; //already dispatched + } + + if (executor == null) { + executor = supplier.get(); + } + + requestContext.suspend(); + requestContext.resume(executor); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java index 8e78ea877f6df7..1d369c71e5a474 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java @@ -1,6 +1,7 @@ package io.quarkus.rest.client.reactive.runtime; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; @@ -11,10 +12,14 @@ import org.jboss.resteasy.reactive.client.handlers.ClientResponseCompleteRestHandler; import org.jboss.resteasy.reactive.client.impl.ClientRequestContextImpl; import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.common.core.UnwrappableException; import org.jboss.resteasy.reactive.common.jaxrs.ResponseImpl; +import io.vertx.core.Context; + public class MicroProfileRestClientResponseFilter implements ClientResponseFilter { + private static final ClientRestHandler[] EMPTY_CLIENT_REST_HANDLERS = new ClientRestHandler[0]; private final List> exceptionMappers; public MicroProfileRestClientResponseFilter(List> exceptionMappers) { @@ -29,21 +34,47 @@ public MicroProfileRestClientResponseFilter(List> exc public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { for (ResponseExceptionMapper exceptionMapper : exceptionMappers) { if (exceptionMapper.handles(responseContext.getStatus(), responseContext.getHeaders())) { - // we have an exception mapper, we don't need the response anymore, we can map it to response right away (I hope :D) RestClientRequestContext restClientContext = ((ClientRequestContextImpl) requestContext) .getRestClientRequestContext(); - ResponseImpl response = ClientResponseCompleteRestHandler.mapToResponse(restClientContext, false); - Throwable throwable; - if (exceptionMapper instanceof ResteasyReactiveResponseExceptionMapper) { - throwable = ((ResteasyReactiveResponseExceptionMapper) exceptionMapper).toThrowable(response, - restClientContext); + + boolean requiresBlocking = RestClientRecorder.isClassBlocking(exceptionMapper.getClass()); + if (Context.isOnEventLoopThread() && requiresBlocking) { + switchToWorkerThreadPoolAndRetry(restClientContext); + break; } else { - throwable = exceptionMapper.toThrowable(response); - } - if (throwable != null) { - throw new UnwrappableException(throwable); + // we have an exception mapper, we don't need the response anymore, we can map it to response right away (I hope :D) + ResponseImpl response = ClientResponseCompleteRestHandler.mapToResponse(restClientContext, false); + Throwable throwable; + if (exceptionMapper instanceof ResteasyReactiveResponseExceptionMapper) { + throwable = ((ResteasyReactiveResponseExceptionMapper) exceptionMapper).toThrowable(response, + restClientContext); + } else { + throwable = exceptionMapper.toThrowable(response); + } + if (throwable != null) { + throw new UnwrappableException(throwable); + } } } } } + + private void switchToWorkerThreadPoolAndRetry(RestClientRequestContext restClientContext) { + int position = restClientContext.getPosition(); + + List nextHandlers = new ArrayList<>(2 + restClientContext.getHandlers().length - position); + nextHandlers.add(new ClientUseWorkerExecutorRestHandler()); + nextHandlers.add(currentHandler(restClientContext)); + + while (position < restClientContext.getHandlers().length) { + nextHandlers.add(restClientContext.getHandlers()[position]); + position++; + } + + restClientContext.restart(nextHandlers.toArray(EMPTY_CLIENT_REST_HANDLERS), true); + } + + private ClientRestHandler currentHandler(RestClientRequestContext restClientContext) { + return restClientContext.getHandlers()[restClientContext.getPosition() - 1]; + } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java index e310af1d531373..6049e880488f16 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java @@ -1,6 +1,7 @@ package io.quarkus.rest.client.reactive.runtime; import java.util.Map; +import java.util.Set; import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver; @@ -9,15 +10,24 @@ @Recorder public class RestClientRecorder { private static volatile Map configKeys; + private static volatile Set blockingClassNames; public void setConfigKeys(Map configKeys) { RestClientRecorder.configKeys = configKeys; } + public void setBlockingClassNames(Set blockingClassNames) { + RestClientRecorder.blockingClassNames = blockingClassNames; + } + public static Map getConfigKeys() { return configKeys; } + public static boolean isClassBlocking(Class exceptionMapperClass) { + return blockingClassNames.contains(exceptionMapperClass.getName()); + } + public void setRestClientBuilderResolver() { RestClientBuilderResolver.setInstance(new BuilderResolver()); } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java index e390280be4816c..eaba90c22749cb 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java @@ -8,6 +8,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_STRUCTURE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_VALUE; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -44,6 +45,8 @@ public class ClientEndpointIndexer extends EndpointIndexer { static final DotName CONTINUATION = DotName.createSimple("kotlin.coroutines.Continuation"); + static final DotName CLIENT_EXCEPTION_MAPPER = DotName + .createSimple("io.quarkus.rest.client.reactive.ClientExceptionMapper"); private final String[] defaultProduces; private final String[] defaultProducesNegotiated; @@ -86,8 +89,19 @@ public MaybeRestClientInterface createClientProxy(ClassInfo classInfo, } private void warnForUnsupportedAnnotations(ClassInfo classInfo) { - if ((classInfo.annotationsMap().get(ResteasyReactiveDotNames.BLOCKING) != null) - || (classInfo.annotationsMap().get(ResteasyReactiveDotNames.NON_BLOCKING) != null)) { + List offendingBlockingAnnotations = new ArrayList<>(); + + List blockingAnnotations = classInfo.annotationsMap().get(ResteasyReactiveDotNames.BLOCKING); + if (blockingAnnotations != null) { + for (AnnotationInstance blockingAnnotation : blockingAnnotations) { + // If the `@Blocking` annotation is used along with the `@ClientExceptionMapper`, we support it. + if (blockingAnnotation.target().annotation(CLIENT_EXCEPTION_MAPPER) == null) { + offendingBlockingAnnotations.add(blockingAnnotation); + } + } + } + if (!offendingBlockingAnnotations.isEmpty() + || classInfo.annotationsMap().get(ResteasyReactiveDotNames.NON_BLOCKING) != null) { log.warn( "'@Blocking' and '@NonBlocking' annotations are not necessary (or supported) on REST Client interfaces. Offending class is '" + classInfo.name() diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java index 1e2f2f6fc29adf..fbc85b77b380e6 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java @@ -364,4 +364,39 @@ public static boolean isSubclassOf(IndexView index, ClassInfo info, DotName pare return isSubclassOf(index, superClass, parentName); } + /** + * Returns true if the given Jandex ClassInfo is a subclass of or inherits the given name. + * + * @param index the index to use to look up super classes. + * @param info the ClassInfo we want to check. + * @param name the name of the superclass or interface we want to find. + * @throws RuntimeException if one of the superclasses is not indexed. + */ + public static boolean isImplementorOf(IndexView index, ClassInfo info, DotName name) { + // Check interfaces + List interfaceNames = info.interfaceNames(); + for (DotName interfaceName : interfaceNames) { + if (interfaceName.equals(name)) { + return true; + } + } + + // Check direct hierarchy + if (info.superName().equals(DOTNAME_OBJECT) || info.superName().equals(DOTNAME_RECORD)) { + return false; + } + if (info.superName().equals(name)) { + return true; + } + + // climb up the hierarchy of classes + Type superType = info.superClassType(); + ClassInfo superClass = index.getClassByName(superType.name()); + if (superClass == null) { + // this can happens if the parent is not inside the Jandex index + throw new RuntimeException("The class " + superType.name() + " is not inside the Jandex index"); + } + return isImplementorOf(index, superClass, name); + } + } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index d166fc9aebbed2..a03c161fa1106c 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -211,7 +211,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, // when a method is blocking, we also want all the request filters to run on the worker thread // because they can potentially set thread local variables - //we don't need to run this for Servlet and other runtimes that default to blocking + // we don't need to run this for Servlet and other runtimes that default to blocking Optional blockingHandlerIndex = Optional.empty(); if (!defaultBlocking) { if (method.isBlocking()) { From fe6c60fb771feb09af7bbdd4ad2e6f90672b0c15 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Sun, 23 Apr 2023 20:52:10 +0900 Subject: [PATCH 123/333] Correct a minor error in qute-reference.adoc --- docs/src/main/asciidoc/qute-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 94cb42d973fcd3..77fba89455d2ed 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -1665,7 +1665,7 @@ On the other hand, if the value is set (e.g. via `TemplateInstance.data("foo", " The type of a default value must be assignable to the type of the parameter declaration. For example, see the incorrect parameter declaration that results in a build failure: `{@org.acme.Foo foo=1}`. TIP: The default value is actually an <>. So the default value does not have to be a literal (such as `42` or `true`). For example, you can leverage the `@TemplateEnum` and specify an enum constant as a default value of a parameter declaration: `{@org.acme.MyEnum myEnum=MyEnum:FOO}`. -However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. `{@org.acme.Foo foo=(foo1 ?: foo2)}``. +However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. `{@org.acme.Foo foo=(foo1 ?: foo2)}`. IMPORTANT: The type of a default value is not validated in <>. From 1b93833f974f2c9346abbe7434d9286ad0a166f9 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Sun, 23 Apr 2023 22:07:42 +0900 Subject: [PATCH 124/333] Add a column before a table column separator `|` Without the space, po4a, which is used to build localized quarkus.io sites fails to parse the asciidoc. --- docs/src/main/asciidoc/doc-reference.adoc | 2 +- .../security-authentication-mechanisms-concept.adoc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/doc-reference.adoc b/docs/src/main/asciidoc/doc-reference.adoc index 907359f0952c61..0c8774698d3022 100644 --- a/docs/src/main/asciidoc/doc-reference.adoc +++ b/docs/src/main/asciidoc/doc-reference.adoc @@ -57,7 +57,7 @@ Your titles and headings must also follow the specific guidance for the Quarkus .Title guidance for different Quarkus content types [cols="15%,25%a,30%,30%"] |=== -|Content type |Should ... |Good example|Bad example +|Content type |Should ... |Good example |Bad example |Concept |* Start with a noun that names the concept or topic diff --git a/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc b/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc index c61f195a26d377..8b11df87220872 100644 --- a/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc +++ b/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc @@ -195,11 +195,11 @@ For more information about OIDC authentication and authorization methods you can [options="header"] |==== |OIDC topic |Quarkus information resource -|Bearer token authentication mechanism|xref:security-oidc-bearer-authentication-concept.adoc[OIDC Bearer authentication] -|Authorization code flow authentication mechanism|xref:security-oidc-code-flow-authentication-concept.adoc[OpenID Connect (OIDC) authorization code flow mechanism] -|Multiple tenants that can support bearer token or authorization code flow mechanisms|xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect (OIDC) multi-tenancy] -|Using Keycloak to centralize authorization|xref:security-keycloak-authorization.adoc[Using OpenID Connect (OIDC) and Keycloak to centralize authorization] -|Configuring Keycloak programmatically|xref:security-keycloak-admin-client.adoc[Using the Keycloak admin client] +|Bearer token authentication mechanism |xref:security-oidc-bearer-authentication-concept.adoc[OIDC Bearer authentication] +|Authorization code flow authentication mechanism |xref:security-oidc-code-flow-authentication-concept.adoc[OpenID Connect (OIDC) authorization code flow mechanism] +|Multiple tenants that can support bearer token or authorization code flow mechanisms |xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect (OIDC) multi-tenancy] +|Using Keycloak to centralize authorization |xref:security-keycloak-authorization.adoc[Using OpenID Connect (OIDC) and Keycloak to centralize authorization] +|Configuring Keycloak programmatically |xref:security-keycloak-admin-client.adoc[Using the Keycloak admin client] |==== [NOTE] From d7842f983f719c19785080da7179ce9c02c9e80a Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Sun, 23 Apr 2023 15:48:03 +0200 Subject: [PATCH 125/333] Adds infinispan caches configuration resource build property --- .../asciidoc/infinispan-client-reference.adoc | 22 +++- .../infinispan-client/deployment/pom.xml | 12 ++ .../deployment/InfinispanClientProcessor.java | 20 ++++ .../InfinispanConfigurationSetupTest.java | 61 ++++++++++ ...nispanDefaultMinimalConfigurationTest.java | 38 ++++++ ...NamedInfinispanConfigurationSetupTest.java | 48 ++++++++ .../cache-config-application.properties | 33 ++++++ .../resources/distributed-cache-config.xml | 1 + .../src/test/resources/local-cache-config.xml | 1 + .../resources/minimal-application.properties | 7 ++ ...ltiple-cache-config-application.properties | 20 ++++ .../InfinispanClientBuildTimeConfig.java | 29 ++++- .../runtime/InfinispanClientProducer.java | 109 ++++++++++++------ .../InfinispanClientRuntimeConfig.java | 23 +--- .../InfinispanClientsBuildTimeConfig.java | 4 +- .../src/main/resources/application.properties | 10 +- 16 files changed, 370 insertions(+), 68 deletions(-) create mode 100644 extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanConfigurationSetupTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanDefaultMinimalConfigurationTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/MultipleNamedInfinispanConfigurationSetupTest.java create mode 100644 extensions/infinispan-client/deployment/src/test/resources/cache-config-application.properties create mode 100644 extensions/infinispan-client/deployment/src/test/resources/distributed-cache-config.xml create mode 100644 extensions/infinispan-client/deployment/src/test/resources/local-cache-config.xml create mode 100644 extensions/infinispan-client/deployment/src/test/resources/minimal-application.properties create mode 100644 extensions/infinispan-client/deployment/src/test/resources/multiple-cache-config-application.properties diff --git a/docs/src/main/asciidoc/infinispan-client-reference.adoc b/docs/src/main/asciidoc/infinispan-client-reference.adoc index 8ee6a65733c0be..49bdb980801b59 100644 --- a/docs/src/main/asciidoc/infinispan-client-reference.adoc +++ b/docs/src/main/asciidoc/infinispan-client-reference.adoc @@ -169,14 +169,24 @@ to create it on first access, use one of the following properties: [source,properties] ---- -quarkus.infinispan-client.cache.books.configuration-uri=cacheConfig.json <1> -quarkus.infinispan-client.cache.magazine.configuration= <2> +quarkus.infinispan-client.cache.magazine.configuration= <1> +quarkus.infinispan-client.cache.books.configuration-resource=booksDistributedCache.json <2> +quarkus.infinispan-client.cache.authors.configuration-uri=/file/authorsIndexedCache.yaml <3> ---- -<1> The file name located under the `resources` folder that contains the configuration of the 'books' cache -<2> The configuration in xml of the 'magazine' (yaml and json are also supported) +<1> The configuration in xml of the 'magazine' (yaml and json are also supported) +<2> The file name located under the `resources` folder that contains the configuration of the 'books' cache +<3> A provided file URI. The file URI can also be a file under resources -If both `configuration-uri` and `configuration` are configured for the same cache with the same Quarkus profile, -`configuration-uri` gets preference over `configuration`. +If `configuration-resource`, `configuration` and `configuration-uri` are configured for the same cache with +the same Quarkus profile, `configuration-uri` gets the highest preference, over `configuration-resource` and `configuration`. +`configuration-resource` gets preference over `configuration`. + +[WARNING] +==== +The `configuration-resource` is build time property and the file will be included in the native build automatically. +`configuration-uri` can also point to a file under the `resources` folder. However, the file won't be automatically included +in the native executable, unless you configure the property `quarkus.native.resources.includes`. +==== [TIP] ==== diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index bef2b1168ff346..e0b7dadb2d40f6 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -61,6 +61,18 @@ quarkus-kubernetes-service-binding-spi + + io.quarkus + quarkus-junit5-internal + test + + + + org.assertj + assertj-core + test + + org.infinispan infinispan-server-testdriver-core diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java index 1d871298e268d2..58201a89f0b4fe 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java @@ -85,6 +85,7 @@ import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; @@ -192,6 +193,7 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch BuildProducer nativeImageConfig, BuildProducer infinispanClientNames, MarshallingBuildItem marshallingBuildItem, + BuildProducer resourceBuildItem, CombinedIndexBuildItem applicationIndexBuildItem) throws ClassNotFoundException, IOException { feature.produce(new FeatureBuildItem(Feature.INFINISPAN_CLIENT)); @@ -211,12 +213,19 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch sslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(Feature.INFINISPAN_CLIENT)); nativeImageSecurityProviders.produce(new NativeImageSecurityProviderBuildItem(SASL_SECURITY_PROVIDER)); + // add per cache file config + handlePerCacheFileConfig(infinispanClientsBuildTimeConfig.defaultInfinispanClient, resourceBuildItem, hotDeployment); + for (InfinispanClientBuildTimeConfig config : infinispanClientsBuildTimeConfig.namedInfinispanClients.values()) { + handlePerCacheFileConfig(config, resourceBuildItem, hotDeployment); + } + Map propertiesMap = new HashMap<>(); IndexView index = applicationIndexBuildItem.getIndex(); // named and default Set allClientNames = infinispanClientNames(applicationIndexBuildItem, infinispanClientNames); allClientNames.addAll(infinispanClientsBuildTimeConfig.getInfinispanNamedClientConfigNames()); + allClientNames.add(DEFAULT_INFINISPAN_CLIENT_NAME); for (String clientName : allClientNames) { Properties properties = loadHotrodProperties(clientName, reflectiveClass, marshallingBuildItem); propertiesMap.put(clientName, properties); @@ -310,6 +319,17 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch return new InfinispanPropertiesBuildItem(propertiesMap); } + private void handlePerCacheFileConfig(InfinispanClientBuildTimeConfig config, + BuildProducer resourceBuildItem, + BuildProducer hotDeployment) { + for (InfinispanClientBuildTimeConfig.RemoteCacheConfig cacheConfig : config.cache.values()) { + if (cacheConfig.configurationResource.isPresent()) { + resourceBuildItem.produce(new NativeImageResourceBuildItem(cacheConfig.configurationResource.get())); + hotDeployment.produce(new HotDeploymentWatchedFileBuildItem(cacheConfig.configurationResource.get())); + } + } + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) BeanContainerListenerBuildItem build(InfinispanRecorder recorder, InfinispanPropertiesBuildItem builderBuildItem) { diff --git a/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanConfigurationSetupTest.java b/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanConfigurationSetupTest.java new file mode 100644 index 00000000000000..c6aa1c49e57886 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanConfigurationSetupTest.java @@ -0,0 +1,61 @@ +package org.quarkus.infinispan.client.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.ClientIntelligence; +import org.infinispan.client.hotrod.configuration.Configuration; +import org.infinispan.client.hotrod.configuration.NearCacheMode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class InfinispanConfigurationSetupTest { + @Inject + RemoteCacheManager remoteCacheManager; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withEmptyApplication() + .withConfigurationResource("cache-config-application.properties") + .withApplicationRoot((jar) -> jar + .addAsResource("distributed-cache-config.xml") + .addAsResource("local-cache-config.xml")); + + @Test + public void infinispanConnectionConfiguration() { + assertThat(remoteCacheManager).isNotNull(); + Configuration configuration = remoteCacheManager.getConfiguration(); + assertThat(configuration.servers().size()).isEqualTo(1); + assertThat(configuration.servers().get(0).host()).isEqualTo("cluster1"); + assertThat(configuration.servers().get(0).port()).isEqualTo(31000); + assertThat(configuration.tracingPropagationEnabled()).isFalse(); + assertThat(configuration.clientIntelligence()).isEqualTo(ClientIntelligence.BASIC); + assertThat(configuration.security().authentication().enabled()).isTrue(); + assertThat(configuration.security().authentication().saslMechanism()).isEqualTo("BASIC"); + assertThat(configuration.security().authentication().serverName()).isEqualTo("custom-server-name"); + assertThat(configuration.security().ssl().enabled()).isTrue(); + assertThat(configuration.security().ssl().trustStorePassword()).isEqualTo("trust-pass".toCharArray()); + assertThat(configuration.security().ssl().trustStoreFileName()).isEqualTo("custom-trust-store"); + assertThat(configuration.security().ssl().trustStoreType()).isEqualTo("JCEKS"); + assertThat(configuration.security().ssl().provider()).isEqualTo("SSL_prov"); + assertThat(configuration.security().ssl().protocol()).isEqualTo("SSL_protocol"); + assertThat(configuration.security().ssl().ciphers()).containsExactlyInAnyOrder("SSL_cipher1", "SSL_cipher2"); + + assertThat(configuration.remoteCaches().get("cache1").configuration()).isEqualTo(""); + assertThat(configuration.remoteCaches().get("cache1").nearCacheBloomFilter()).isTrue(); + assertThat(configuration.remoteCaches().get("cache1").nearCacheMaxEntries()).isEqualTo(100); + assertThat(configuration.remoteCaches().get("cache1").nearCacheMode()).isEqualTo(NearCacheMode.INVALIDATED); + + assertThat(configuration.remoteCaches().get("cache2").configuration()).isEqualTo(""); + assertThat(configuration.remoteCaches().get("cache2").nearCacheBloomFilter()).isFalse(); + assertThat(configuration.remoteCaches().get("cache2").nearCacheMaxEntries()).isEqualTo(-1); + assertThat(configuration.remoteCaches().get("cache2").nearCacheMode()).isEqualTo(NearCacheMode.DISABLED); + + assertThat(configuration.remoteCaches().get("cache3").configuration()).isEqualTo(""); + + } +} diff --git a/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanDefaultMinimalConfigurationTest.java b/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanDefaultMinimalConfigurationTest.java new file mode 100644 index 00000000000000..61003f76eac8dd --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/InfinispanDefaultMinimalConfigurationTest.java @@ -0,0 +1,38 @@ +package org.quarkus.infinispan.client.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.ClientIntelligence; +import org.infinispan.client.hotrod.configuration.Configuration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class InfinispanDefaultMinimalConfigurationTest { + @Inject + RemoteCacheManager remoteCacheManager; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withEmptyApplication() + .withConfigurationResource("minimal-application.properties"); + + @Test + public void infinispanConnectionConfiguration() { + assertThat(remoteCacheManager).isNotNull(); + Configuration configuration = remoteCacheManager.getConfiguration(); + assertThat(configuration.servers().size()).isEqualTo(1); + assertThat(configuration.servers().get(0).host()).isEqualTo("cluster1"); + assertThat(configuration.servers().get(0).port()).isEqualTo(31000); + assertThat(configuration.tracingPropagationEnabled()).isTrue(); + assertThat(configuration.clientIntelligence()).isEqualTo(ClientIntelligence.HASH_DISTRIBUTION_AWARE); + assertThat(configuration.remoteCaches()).isEmpty(); + assertThat(configuration.security().authentication().enabled()).isTrue(); + assertThat(configuration.security().authentication().saslMechanism()).isEqualTo("DIGEST-MD5"); + assertThat(configuration.security().ssl().enabled()).isFalse(); + } +} diff --git a/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/MultipleNamedInfinispanConfigurationSetupTest.java b/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/MultipleNamedInfinispanConfigurationSetupTest.java new file mode 100644 index 00000000000000..6d62a93844482a --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/java/org/quarkus/infinispan/client/deployment/MultipleNamedInfinispanConfigurationSetupTest.java @@ -0,0 +1,48 @@ +package org.quarkus.infinispan.client.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; + +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.Configuration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.infinispan.client.InfinispanClientName; +import io.quarkus.test.QuarkusUnitTest; + +public class MultipleNamedInfinispanConfigurationSetupTest { + @Inject + RemoteCacheManager remoteCacheManager; + + @Inject + @InfinispanClientName("another") + RemoteCacheManager anotherRemoteCacheManager; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withEmptyApplication() + .withConfigurationResource("multiple-cache-config-application.properties") + .withApplicationRoot((jar) -> jar + .addAsResource("distributed-cache-config.xml") + .addAsResource("local-cache-config.xml")); + + @Test + public void infinispanMultipleConnectionsConfiguration() { + assertThat(remoteCacheManager).isNotNull(); + assertThat(anotherRemoteCacheManager).isNotNull(); + + Configuration configuration = remoteCacheManager.getConfiguration(); + assertThat(configuration.servers().size()).isEqualTo(1); + assertThat(configuration.servers().get(0).host()).isEqualTo("cluster1"); + assertThat(configuration.servers().get(0).port()).isEqualTo(31000); + assertThat(configuration.remoteCaches().get("cache3").configuration()).isEqualTo(""); + + Configuration anotherConfiguration = anotherRemoteCacheManager.getConfiguration(); + assertThat(anotherConfiguration.servers().size()).isEqualTo(1); + assertThat(anotherConfiguration.servers().get(0).host()).isEqualTo("cluster2"); + assertThat(anotherConfiguration.servers().get(0).port()).isEqualTo(41000); + assertThat(anotherConfiguration.remoteCaches().get("cache4").configuration()).isEqualTo(""); + } +} diff --git a/extensions/infinispan-client/deployment/src/test/resources/cache-config-application.properties b/extensions/infinispan-client/deployment/src/test/resources/cache-config-application.properties new file mode 100644 index 00000000000000..49238028c10100 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/cache-config-application.properties @@ -0,0 +1,33 @@ +# don't run any server with test containers. this config is used to test the configuration mapping +quarkus.infinispan-client.devservices.enabled=false +quarkus.infinispan-client.use-schema-registration=false + +quarkus.infinispan-client.hosts=cluster1:31000 +quarkus.infinispan-client.username=infinispan +quarkus.infinispan-client.password=secret +quarkus.infinispan-client.tracing.propagation.enabled=false +quarkus.infinispan-client.client-intelligence=BASIC +quarkus.infinispan-client.sasl-mechanism=BASIC +quarkus.infinispan-client.auth-realm=infiniRealm +quarkus.infinispan-client.auth-server-name=custom-server-name +quarkus.infinispan-client.trust-store=custom-trust-store +quarkus.infinispan-client.trust-store-password=trust-pass +quarkus.infinispan-client.trust-store-type=JCEKS +quarkus.infinispan-client.ssl-provider=SSL_prov +quarkus.infinispan-client.ssl-protocol=SSL_protocol +quarkus.infinispan-client.ssl-ciphers=SSL_cipher1,SSL_cipher2 + +# cache 1 config +quarkus.infinispan-client.cache.cache1.configuration= +quarkus.infinispan-client.cache.cache1.near-cache-use-bloom-filter=true +quarkus.infinispan-client.cache.cache1.near-cache-max-entries=100 +quarkus.infinispan-client.cache.cache1.near-cache-mode=INVALIDATED + +# cache 2 config (configuration-resource over configuration) +quarkus.infinispan-client.cache.cache2.configuration= +quarkus.infinispan-client.cache.cache2.configuration-resource=distributed-cache-config.xml + +# cache 3 config (configuration-uri over configuration-resource and configuration) +quarkus.infinispan-client.cache.cache3.configuration= +quarkus.infinispan-client.cache.cache3.configuration-resource=distributed-cache-config.xml +quarkus.infinispan-client.cache.cache3.configuration-uri=local-cache-config.xml diff --git a/extensions/infinispan-client/deployment/src/test/resources/distributed-cache-config.xml b/extensions/infinispan-client/deployment/src/test/resources/distributed-cache-config.xml new file mode 100644 index 00000000000000..c1e032bcd9e8d1 --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/distributed-cache-config.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/infinispan-client/deployment/src/test/resources/local-cache-config.xml b/extensions/infinispan-client/deployment/src/test/resources/local-cache-config.xml new file mode 100644 index 00000000000000..51ebbe9c2bc0fe --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/local-cache-config.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/infinispan-client/deployment/src/test/resources/minimal-application.properties b/extensions/infinispan-client/deployment/src/test/resources/minimal-application.properties new file mode 100644 index 00000000000000..b496e8226789fb --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/minimal-application.properties @@ -0,0 +1,7 @@ +# don't run any server with test containers. this config is used to test the configuration mapping +quarkus.infinispan-client.devservices.enabled=false +quarkus.infinispan-client.use-schema-registration=false + +quarkus.infinispan-client.hosts=cluster1:31000 +quarkus.infinispan-client.username=infinispan +quarkus.infinispan-client.password=secret \ No newline at end of file diff --git a/extensions/infinispan-client/deployment/src/test/resources/multiple-cache-config-application.properties b/extensions/infinispan-client/deployment/src/test/resources/multiple-cache-config-application.properties new file mode 100644 index 00000000000000..122f879448a43d --- /dev/null +++ b/extensions/infinispan-client/deployment/src/test/resources/multiple-cache-config-application.properties @@ -0,0 +1,20 @@ +# don't run any server with test containers. this config is used to test the configuration mapping +quarkus.infinispan-client.devservices.enabled=false +quarkus.infinispan-client.devservices.another.enabled=false +quarkus.infinispan-client.use-schema-registration=false +quarkus.infinispan-client.another.use-schema-registration=false + +quarkus.infinispan-client.hosts=cluster1:31000 +quarkus.infinispan-client.username=infinispan +quarkus.infinispan-client.password=secret + +quarkus.infinispan-client.another.hosts=cluster2:41000 +quarkus.infinispan-client.another.username=another-infinispan +quarkus.infinispan-client.another.password=another-secret + +# cache 3 config (configuration-uri over configuration-resource and configuration) +quarkus.infinispan-client.cache.cache3.configuration-resource=distributed-cache-config.xml +quarkus.infinispan-client.cache.cache3.configuration-uri=local-cache-config.xml + +# cache 4 config (only present in another and build time config) +quarkus.infinispan-client.another.cache.cache4.configuration-resource=local-cache-config.xml \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java index 6f625e0d6ee3b3..5ab65898dba2f9 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java @@ -1,5 +1,7 @@ package io.quarkus.infinispan.client.runtime; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -14,8 +16,11 @@ public class InfinispanClientBuildTimeConfig { /** * Sets the bounded entry count for near cache. If this value is 0 or less near cache is disabled. + * + * @deprecated use per cache configuration for near cache max entries */ @ConfigItem + @Deprecated public int nearCacheMaxEntries; /** @@ -24,6 +29,26 @@ public class InfinispanClientBuildTimeConfig { @ConfigItem public Optional marshallerClass; + /** + * Configures caches build time config from the client with the provided configuration. + */ + @ConfigItem + public Map cache = new HashMap<>(); + + @ConfigGroup + public static class RemoteCacheConfig { + + // @formatter:off + /** + * Cache configuration file in XML, JSON or YAML is defined in build time to create the cache on first access. + * An example of the user defined property. cacheConfig.xml file is located in the 'resources' folder: + * quarkus.infinispan-client.cache.bookscache.configuration-resource=cacheConfig.xml + */ + // @formatter:on + @ConfigItem + public Optional configurationResource; + } + /** * Configuration for DevServices. DevServices allows Quarkus to automatically start an Infinispan Server in dev and test * mode. @@ -60,7 +85,7 @@ public int hashCode() { @Override public String toString() { - return "InfinispanClientBuildTimeConfig{" + - "nearCacheMaxEntries=" + nearCacheMaxEntries + '}'; + return "InfinispanClientBuildTimeConfig{" + "nearCacheMaxEntries=" + nearCacheMaxEntries + ", marshallerClass=" + + marshallerClass + ", cache=" + cache + ", devService=" + devService + '}'; } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java index dbf227a7078242..952afe58b1ac43 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java @@ -5,12 +5,12 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.spi.CreationalContext; import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.literal.NamedLiteral; import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; @@ -56,6 +56,9 @@ public class InfinispanClientProducer { @Inject private Instance infinispanClientsRuntimeConfigHandle; + @Inject + private Instance infinispanClientsBuildTimeConfigHandle; + private void registerSchemaInServer(String infinispanConfigName, Map properties, RemoteCacheManager cacheManager) { @@ -134,6 +137,9 @@ private ConfigurationBuilder builderFromProperties(String infinispanClientName, InfinispanClientRuntimeConfig infinispanClientRuntimeConfig = infinispanClientsRuntimeConfigHandle.get() .getInfinispanClientRuntimeConfig(infinispanClientName); + InfinispanClientBuildTimeConfig infinispanClientBuildTimeConfig = infinispanClientsBuildTimeConfigHandle.get() + .getInfinispanClientBuildTimeConfig(infinispanClientName); + // client name not found if (infinispanClientRuntimeConfig == null) { return builder; @@ -173,9 +179,6 @@ private ConfigurationBuilder builderFromProperties(String infinispanClientName, } } - properties.put(ConfigurationProperties.TRACING_PROPAGATION_ENABLED, - infinispanClientRuntimeConfig.tracingPropagationEnabled); - if (infinispanClientRuntimeConfig.clientIntelligence.isPresent()) { properties.put(ConfigurationProperties.CLIENT_INTELLIGENCE, infinispanClientRuntimeConfig.clientIntelligence.get()); } @@ -190,13 +193,6 @@ private ConfigurationBuilder builderFromProperties(String infinispanClientName, if (infinispanClientRuntimeConfig.authServerName.isPresent()) { properties.put(ConfigurationProperties.AUTH_SERVER_NAME, infinispanClientRuntimeConfig.authServerName.get()); } - if (infinispanClientRuntimeConfig.authClientSubject.isPresent()) { - properties.put(ConfigurationProperties.AUTH_CLIENT_SUBJECT, infinispanClientRuntimeConfig.authClientSubject.get()); - } - if (infinispanClientRuntimeConfig.authCallbackHandler.isPresent()) { - properties.put(ConfigurationProperties.AUTH_CALLBACK_HANDLER, - infinispanClientRuntimeConfig.authCallbackHandler.get()); - } if (infinispanClientRuntimeConfig.saslMechanism.isPresent()) { properties.put(ConfigurationProperties.SASL_MECHANISM, infinispanClientRuntimeConfig.saslMechanism.get()); @@ -222,43 +218,90 @@ private ConfigurationBuilder builderFromProperties(String infinispanClientName, } if (infinispanClientRuntimeConfig.sslCiphers.isPresent()) { - properties.put(ConfigurationProperties.SSL_CIPHERS, infinispanClientRuntimeConfig.sslCiphers.get().toArray()); + properties.put(ConfigurationProperties.SSL_CIPHERS, + infinispanClientRuntimeConfig.sslCiphers.get().stream().collect(Collectors.joining(" "))); } builder.withProperties(properties); - for (Map.Entry cache : infinispanClientRuntimeConfig.cache + if (infinispanClientRuntimeConfig.tracingPropagationEnabled.isPresent()) { + if (!infinispanClientRuntimeConfig.tracingPropagationEnabled.get()) { + builder.disableTracingPropagation(); + } + } + + if (infinispanClientBuildTimeConfig != null) { + for (Map.Entry buildCacheConfig : infinispanClientBuildTimeConfig.cache + .entrySet()) { + String cacheName = buildCacheConfig.getKey(); + // Do this if the cache config is only present in the build time configuration + if (!infinispanClientRuntimeConfig.cache.containsKey(cacheName)) { + InfinispanClientBuildTimeConfig.RemoteCacheConfig buildCacheConfigValue = buildCacheConfig.getValue(); + if (buildCacheConfig.getValue().configurationResource.isPresent()) { + URL configFile = Thread.currentThread().getContextClassLoader() + .getResource(buildCacheConfigValue.configurationResource.get()); + configureRemoteCacheConfigurationURI(builder, cacheName, configFile); + } + } + } + } + + for (Map.Entry cacheConfig : infinispanClientRuntimeConfig.cache .entrySet()) { - String cacheName = cache.getKey(); - InfinispanClientRuntimeConfig.RemoteCacheConfig remoteCacheConfig = cache.getValue(); - if (remoteCacheConfig.configurationUri.isPresent()) { - String cacheConfigUri = remoteCacheConfig.configurationUri.get(); - log.infof("Configuration URI for cache %s found: %s", cacheName, cacheConfigUri); - URL configFile = Thread.currentThread().getContextClassLoader() - .getResource(cacheConfigUri); - try { - builder.remoteCache(cacheName).configurationURI(configFile.toURI()); - } catch (Exception e) { - - throw new RuntimeException(e); + String cacheName = cacheConfig.getKey(); + InfinispanClientRuntimeConfig.RemoteCacheConfig runtimeCacheConfig = cacheConfig.getValue(); + URL configFile = null; + // Check if the build time resource file configuration exists + if (infinispanClientBuildTimeConfig != null) { + InfinispanClientBuildTimeConfig.RemoteCacheConfig buildtimeCacheConfig = infinispanClientBuildTimeConfig.cache + .get( + cacheName); + if (buildtimeCacheConfig != null && buildtimeCacheConfig.configurationResource.isPresent()) { + configFile = Thread.currentThread().getContextClassLoader() + .getResource(buildtimeCacheConfig.configurationResource.get()); } - } else if (remoteCacheConfig.configuration.isPresent()) { - builder.remoteCache(cacheName).configuration(remoteCacheConfig.configuration.get()); } - if (remoteCacheConfig.nearCacheMaxEntries.isPresent()) { - builder.remoteCache(cacheName).nearCacheMaxEntries(remoteCacheConfig.nearCacheMaxEntries.get()); + + // Override build time resource if configuration-uri runtime resource is provided + if (runtimeCacheConfig.configurationUri.isPresent()) { + configFile = Thread.currentThread().getContextClassLoader() + .getResource(runtimeCacheConfig.configurationUri.get()); } - if (remoteCacheConfig.nearCacheMode.isPresent()) { - builder.remoteCache(cacheName).nearCacheMode(remoteCacheConfig.nearCacheMode.get()); + + if (configFile != null) { + configureRemoteCacheConfigurationURI(builder, cacheName, configFile); + } else { + // Inline configuration + if (runtimeCacheConfig.configuration.isPresent()) { + builder.remoteCache(cacheName).configuration(runtimeCacheConfig.configuration.get()); + } } - if (remoteCacheConfig.nearCacheUseBloomFilter.isPresent()) { - builder.remoteCache(cacheName).nearCacheUseBloomFilter(remoteCacheConfig.nearCacheUseBloomFilter.get()); + + // Configures near caching + if (runtimeCacheConfig.nearCacheMaxEntries.isPresent()) { + builder.remoteCache(cacheName).nearCacheMaxEntries(runtimeCacheConfig.nearCacheMaxEntries.get()); + } + if (runtimeCacheConfig.nearCacheMode.isPresent()) { + builder.remoteCache(cacheName).nearCacheMode(runtimeCacheConfig.nearCacheMode.get()); + } + if (runtimeCacheConfig.nearCacheUseBloomFilter.isPresent()) { + builder.remoteCache(cacheName).nearCacheUseBloomFilter(runtimeCacheConfig.nearCacheUseBloomFilter.get()); } } return builder; } + private void configureRemoteCacheConfigurationURI(ConfigurationBuilder builder, String cacheName, URL configFile) { + try { + builder.remoteCache(cacheName).configurationURI(configFile.toURI()); + } catch (Exception e) { + log.errorf("Provided configuration-resource or configuration-uri can't be loaded. %s", + configFile.getPath()); + throw new IllegalStateException(e); + } + } + private static void handleProtoStreamMarshaller(ProtoStreamMarshaller marshaller, Properties properties, BeanManager beanManager) { SerializationContext serializationContext = marshaller.getSerializationContext(); diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java index beed9c042b4a97..6884512349fff3 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java @@ -6,8 +6,6 @@ import java.util.Optional; import javax.net.ssl.SSLContext; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; import org.infinispan.client.hotrod.configuration.NearCacheMode; @@ -114,19 +112,6 @@ public class InfinispanClientRuntimeConfig { @ConfigItem(defaultValue = "infinispan") Optional authServerName; - /** - * Sets client subject, necessary for those SASL mechanisms which require it to access client credentials. - */ - @ConfigItem - Optional authClientSubject; - - /** - * Specifies a {@link CallbackHandler} to be used during the authentication handshake. - * The {@link Callback}s that need to be handled are specific to the chosen SASL mechanism. - */ - @ConfigItem - Optional authCallbackHandler; - // @formatter:off /** * Sets SASL mechanism used by authentication. @@ -186,14 +171,14 @@ public class InfinispanClientRuntimeConfig { * Whether a tracing propagation is enabled in case the Opentelemetry extension is present. * By default the propagation of the context is propagated from the client to the Infinispan Server. */ - @ConfigItem(name = "tracing.propagation.enabled", defaultValue = "true") - public boolean tracingPropagationEnabled; + @ConfigItem(name = "tracing.propagation.enabled") + public Optional tracingPropagationEnabled; /** * Configures caches from the client with the provided configuration. */ @ConfigItem - Map cache = new HashMap<>(); + public Map cache = new HashMap<>(); @ConfigGroup public static class RemoteCacheConfig { @@ -211,7 +196,7 @@ public static class RemoteCacheConfig { // @formatter:off /** - * Cache configuration file in XML whose path will be converted to URI to create the cache on first access. + * Cache configuration file in XML, Json or YAML whose path will be converted to URI to create the cache on first access. * An example of the user defined property. cacheConfig.xml file is located in the 'resources' folder: * quarkus.infinispan-client.cache.bookscache.configuration-uri=cacheConfig.xml */ diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java index c57a79c711e472..d14a2dbc9fd293 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientsBuildTimeConfig.java @@ -3,7 +3,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Map; -import java.util.Objects; import java.util.Set; import io.quarkus.runtime.annotations.ConfigItem; @@ -40,7 +39,6 @@ public InfinispanClientBuildTimeConfig getInfinispanClientBuildTimeConfig(String if (InfinispanClientUtil.isDefault(infinispanClientName)) { return defaultInfinispanClient; } - InfinispanClientBuildTimeConfig infinispanClientBuildTimeConfig = namedInfinispanClients.get(infinispanClientName); - return Objects.requireNonNullElseGet(infinispanClientBuildTimeConfig, InfinispanClientBuildTimeConfig::new); + return namedInfinispanClients.get(infinispanClientName); } } diff --git a/integration-tests/infinispan-client/src/main/resources/application.properties b/integration-tests/infinispan-client/src/main/resources/application.properties index 0b08500be50fde..d9e2391da36e85 100644 --- a/integration-tests/infinispan-client/src/main/resources/application.properties +++ b/integration-tests/infinispan-client/src/main/resources/application.properties @@ -1,5 +1,5 @@ -quarkus.infinispan-client.cache.default.configuration-uri=cacheConfig.xml -quarkus.infinispan-client.cache.books.configuration-uri=booksIndexedConfig.json +quarkus.infinispan-client.cache.default.configuration-resource=cacheConfig.xml +quarkus.infinispan-client.cache.books.configuration-resource=booksIndexedConfig.json quarkus.infinispan-client.cache.magazine.configuration= quarkus.infinispan-client.cache.default.near-cache-mode=INVALIDATED quarkus.infinispan-client.cache.default.near-cache-max-entries=2 @@ -7,7 +7,9 @@ quarkus.infinispan-client.cache.default.near-cache-use-bloom-filter=false quarkus.infinispan-client.cache.magazine.near-cache-mode=INVALIDATED quarkus.infinispan-client.cache.magazine.near-cache-max-entries=2 quarkus.infinispan-client.cache.magazine.near-cache-use-bloom-filter=false -quarkus.native.resources.includes=cacheConfig.xml,booksIndexedConfig.json + +# Have a default cache in another remote infinispan client +quarkus.infinispan-client.another.cache.books.configuration-resource=cacheConfig.xml # Dev services configuration quarkus.infinispan-client.devservices.site=NYC @@ -19,5 +21,3 @@ quarkus.infinispan-client.another.devservices.mcast-port=46667 quarkus.infinispan-client.another.devservices.port=31223 quarkus.infinispan-client.another.devservices.service-name=infinispanAnother -# Have a default cache in another remote infinispan client -quarkus.infinispan-client.another.cache.books.configuration-uri=cacheConfig.xml From 903698b25853a79dc48d27c15251725298c20440 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Sun, 23 Apr 2023 22:48:20 +0900 Subject: [PATCH 126/333] Correct a typo in redis-reference.adoc --- docs/src/main/asciidoc/redis-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc index a9e3d1e8822299..55fba59026da1f 100644 --- a/docs/src/main/asciidoc/redis-reference.adoc +++ b/docs/src/main/asciidoc/redis-reference.adoc @@ -730,7 +730,7 @@ This behavior is disabled in _prod_ mode, and if you want to import even in prod %prod.quarkus.redis.load-script=import.redis ---- -Before importing in _prod_ mode, mae sure you configured `quarkus.redis.flush-before-load` accordingly. +Before importing in _prod_ mode, make sure you configured `quarkus.redis.flush-before-load` accordingly. IMPORTANT: In dev mode, to reload the content of the `.redis` load scripts, you need to add: `%dev.quarkus.vertx.caching=false` From 751e00c034d968267b1e03397ab3eb47150c4bf6 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Sun, 23 Apr 2023 19:13:00 +0100 Subject: [PATCH 127/333] Support for GitHub in OIDC Dev code --- .../OidcAuthorizationCodePostHandler.java | 2 + .../devservices/OidcDevConsoleProcessor.java | 21 +++++++--- .../resources/dev-templates/provider.html | 8 ++-- .../runtime/OidcConfigPropertySupplier.java | 38 ++++++++++++++----- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcAuthorizationCodePostHandler.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcAuthorizationCodePostHandler.java index d6335bfc4919df..61037a11b0fbb0 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcAuthorizationCodePostHandler.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcAuthorizationCodePostHandler.java @@ -18,6 +18,7 @@ public class OidcAuthorizationCodePostHandler extends DevConsolePostHandler { private static final Logger LOG = Logger.getLogger(OidcAuthorizationCodePostHandler.class); + private static final String APPLICATION_JSON = "application/json"; Vertx vertxInstance; Duration timeout; @@ -41,6 +42,7 @@ protected void handlePostAsync(RoutingContext event, MultiMap form) throws Excep HttpRequest request = client.postAbs(tokenUrl); request.putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString()); + request.putHeader(HttpHeaders.ACCEPT.toString(), APPLICATION_JSON); io.vertx.mutiny.core.MultiMap props = new io.vertx.mutiny.core.MultiMap(MultiMap.caseInsensitiveMultiMap()); props.add("client_id", form.get("client")); diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java index 90e9bea7c86675..6c15186d98fee9 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java @@ -46,7 +46,7 @@ public class OidcDevConsoleProcessor extends AbstractDevConsoleProcessor { private static final String KEYCLOAK = "Keycloak"; private static final String AZURE = "Azure"; - private static final Set OTHER_PROVIDERS = Set.of("Auth0", "Okta", "Google"); + private static final Set OTHER_PROVIDERS = Set.of("Auth0", "Okta", "Google", "Github"); OidcBuildTimeConfig oidcConfig; @@ -84,7 +84,7 @@ public void run() { } JsonObject metadata = null; - if (isDiscoveryEnabled()) { + if (isDiscoveryEnabled(providerConfig)) { metadata = discoverMetadata(authServerUrl); if (metadata == null) { return; @@ -105,8 +105,9 @@ public void run() { metadata != null ? metadata.getString("authorization_endpoint") : null, metadata != null ? metadata.getString("token_endpoint") : null, metadata != null ? metadata.getString("end_session_endpoint") : null, - metadata != null ? metadata.containsKey("introspection_endpoint") - || metadata.containsKey("userinfo_endpoint") : false); + metadata != null + ? (metadata.containsKey("introspection_endpoint") || metadata.containsKey("userinfo_endpoint")) + : checkProviderUserInfoRequired(providerConfig)); produceDevConsoleRouteItems(devConsoleRoute, new OidcTestServiceHandler(vertxInstance, oidcConfig.devui.webClientTimeout), @@ -117,6 +118,13 @@ public void run() { } } + private boolean checkProviderUserInfoRequired(OidcTenantConfig providerConfig) { + if (providerConfig != null) { + return providerConfig.authentication.userInfoRequired.orElse(false); + } + return false; + } + private String tryToGetProviderName(String authServerUrl) { if (authServerUrl.contains("/realms/")) { return KEYCLOAK; @@ -162,8 +170,9 @@ private static boolean isOidcTenantEnabled() { return getBooleanProperty(TENANT_ENABLED_CONFIG_KEY); } - private static boolean isDiscoveryEnabled() { - return getBooleanProperty(DISCOVERY_ENABLED_CONFIG_KEY); + private static boolean isDiscoveryEnabled(OidcTenantConfig providerConfig) { + return ConfigProvider.getConfig().getOptionalValue(DISCOVERY_ENABLED_CONFIG_KEY, Boolean.class) + .orElse((providerConfig != null ? providerConfig.discoveryEnabled.orElse(true) : true)); } private static boolean getBooleanProperty(String name) { diff --git a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html index 0006e4572e6038..6f17c20060e04d 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html +++ b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html @@ -231,11 +231,13 @@ function(data, status){ var tokens = JSON.parse(data); accessToken = tokens.access_token - idToken = tokens.id_token $('#accessTokenEncodedArea').html(prettyToken(accessToken)); $('#accessTokenDecodedArea').html(decodeToken(accessToken)); - $('#idTokenEncodedArea').html(prettyToken(idToken)); - $('#idTokenDecodedArea').html(decodeToken(idToken)); + if ("id_token" in tokens) { + idToken = tokens.id_token + $('#idTokenEncodedArea').html(prettyToken(idToken)); + $('#idTokenDecodedArea').html(decodeToken(idToken)); + } }); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java index c2494749fa2706..5e56ce93552037 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java @@ -2,10 +2,12 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import org.eclipse.microprofile.config.ConfigProvider; +import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.OidcTenantConfig.Provider; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; @@ -14,6 +16,10 @@ public class OidcConfigPropertySupplier implements Supplier { private static final String AUTH_SERVER_URL_CONFIG_KEY = "quarkus.oidc.auth-server-url"; private static final String END_SESSION_PATH_CONFIG_KEY = "quarkus.oidc.end-session-path"; + private static final String TOKEN_PATH_CONFIG_KEY = "quarkus.oidc.token-path"; + private static final String AUTH_PATH_CONFIG_KEY = "quarkus.oidc.authorization-path"; + private static final Set RELATIVE_PATH_CONFIG_PROPS = Set.of(END_SESSION_PATH_CONFIG_KEY, + TOKEN_PATH_CONFIG_KEY, AUTH_PATH_CONFIG_KEY); private static final String OIDC_PROVIDER_CONFIG_KEY = "quarkus.oidc.provider"; private static final String SCOPES_KEY = "quarkus.oidc.authentication.scopes"; private String oidcConfigProperty; @@ -40,20 +46,28 @@ public OidcConfigPropertySupplier(String oidcConfigProperty, String defaultValue @Override public String get() { - if (defaultValue != null || END_SESSION_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { + Optional provider = ConfigProvider.getConfig().getOptionalValue(OIDC_PROVIDER_CONFIG_KEY, + Provider.class); + OidcTenantConfig providerConfig = provider.isPresent() ? KnownOidcProviders.provider(provider.get()) : null; + if (defaultValue != null || RELATIVE_PATH_CONFIG_PROPS.contains(oidcConfigProperty)) { Optional value = ConfigProvider.getConfig().getOptionalValue(oidcConfigProperty, String.class); + if (value.isEmpty() && providerConfig != null) { + if (END_SESSION_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { + value = providerConfig.endSessionPath; + } else if (TOKEN_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { + value = providerConfig.tokenPath; + } else if (AUTH_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { + value = providerConfig.authorizationPath; + } + } if (value.isPresent()) { - return checkUrlProperty(value); + return checkUrlProperty(value, providerConfig); } return defaultValue; } else if (SCOPES_KEY.equals(oidcConfigProperty)) { Optional> scopes = ConfigProvider.getConfig().getOptionalValues(oidcConfigProperty, String.class); - if (scopes.isEmpty()) { - Optional provider = ConfigProvider.getConfig().getOptionalValue(OIDC_PROVIDER_CONFIG_KEY, - Provider.class); - if (provider.isPresent()) { - scopes = KnownOidcProviders.provider(provider.get()).authentication.scopes; - } + if (scopes.isEmpty() && providerConfig != null) { + scopes = providerConfig.authentication.scopes; } if (scopes.isPresent()) { return OidcCommonUtils.urlEncode(String.join(" ", scopes.get())); @@ -61,14 +75,18 @@ public String get() { return OidcConstants.OPENID_SCOPE; } } else { - return checkUrlProperty(ConfigProvider.getConfig().getOptionalValue(oidcConfigProperty, String.class)); + return checkUrlProperty(ConfigProvider.getConfig().getOptionalValue(oidcConfigProperty, String.class), + providerConfig); } } - private String checkUrlProperty(Optional value) { + private String checkUrlProperty(Optional value, OidcTenantConfig providerConfig) { if (urlProperty && value.isPresent() && !value.get().startsWith("http:")) { Optional authServerUrl = ConfigProvider.getConfig().getOptionalValue(AUTH_SERVER_URL_CONFIG_KEY, String.class); + if (authServerUrl.isEmpty() && providerConfig != null) { + authServerUrl = providerConfig.authServerUrl; + } return authServerUrl.isPresent() ? OidcCommonUtils.getOidcEndpointUrl(authServerUrl.get(), value) : null; } return value.orElse(null); From a9204fc0323006aa5fa1cde416a4ba3242567219 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Mon, 24 Apr 2023 11:26:55 +0900 Subject: [PATCH 128/333] Fix a typo in mongodb-panache.adoc --- docs/src/main/asciidoc/mongodb-panache.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 2bf957cecf4855..c9821898e0afbb 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -622,7 +622,7 @@ For these operations, you can express the update document the same way you expre - `firstname = ?1 and status = ?2` will be mapped to the update document `{'$set' : {'firstname': ?1, 'status': ?2}}` - `firstname = :firstname and status = :status` will be mapped to the update document `{'$set' : {'firstname': :firstname, 'status': :status}}` - `{'firstname' : ?1 and 'status' : ?2}` will be mapped to the update document `{'$set' : {'firstname': ?1, 'status': ?2}}` -- `{'firstname' : :firstname and 'status' : :status}` ` will be mapped to the update document `{'$set' : {'firstname': :firstname, 'status': :status}}` +- `{'firstname' : :firstname and 'status' : :status}` will be mapped to the update document `{'$set' : {'firstname': :firstname, 'status': :status}}` - `{'$inc': {'cpt': ?1}}` will be used as-is === Query parameters From e8ce3cfe0b4397c8852ef8c0f4a6c20a49b24f25 Mon Sep 17 00:00:00 2001 From: Yoshikazu Nojima Date: Mon, 24 Apr 2023 11:37:52 +0900 Subject: [PATCH 129/333] Fix a typo in rest-client-multipart.adoc --- docs/src/main/asciidoc/rest-client-multipart.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/rest-client-multipart.adoc b/docs/src/main/asciidoc/rest-client-multipart.adoc index f9525694340787..e4d04f8784e7be 100644 --- a/docs/src/main/asciidoc/rest-client-multipart.adoc +++ b/docs/src/main/asciidoc/rest-client-multipart.adoc @@ -153,7 +153,7 @@ The name of the property needs to follow a certain convention which is best disp quarkus.rest-client."org.acme.rest.client.multipart.MultipartService".url=http://localhost:8080/ ---- -Having this configuration means that all requests performed using `org.acme.rest.client.multipart.MultipartService` will use `http://localhost:8080/ ` as the base URL. +Having this configuration means that all requests performed using `org.acme.rest.client.multipart.MultipartService` will use `http://localhost:8080/` as the base URL. Note that `org.acme.rest.client.multipart.MultipartService` _must_ match the fully qualified name of the `MultipartService` interface we created in the previous section. From ff99a4af1dd6e37afe86828975314f7ca6e2e93b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 24 Apr 2023 08:04:12 +0300 Subject: [PATCH 130/333] Revert "Use specific PgPoolOptions in reactive-pg-client" --- docs/src/main/asciidoc/reactive-sql-clients.adoc | 4 ++-- .../quarkus/reactive/pg/client/PgPoolCreator.java | 3 +-- .../pg/client/runtime/PgPoolRecorder.java | 15 +++++++-------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/src/main/asciidoc/reactive-sql-clients.adoc b/docs/src/main/asciidoc/reactive-sql-clients.adoc index 59c9d145f2c46a..50685c3ccd5356 100644 --- a/docs/src/main/asciidoc/reactive-sql-clients.adoc +++ b/docs/src/main/asciidoc/reactive-sql-clients.adoc @@ -755,7 +755,7 @@ import jakarta.inject.Singleton; import io.quarkus.reactive.pg.client.PgPoolCreator; import io.vertx.pgclient.PgConnectOptions; -import io.vertx.pgclient.PgPoolOptions; +import io.vertx.pgclient.PgPool; import io.vertx.sqlclient.PoolOptions; @Singleton @@ -764,7 +764,7 @@ public class CustomPgPoolCreator implements PgPoolCreator { @Override public PgPool create(Input input) { PgConnectOptions connectOptions = input.pgConnectOptions(); - PgPoolOptions poolOptions = input.poolOptions(); + PoolOptions poolOptions = input.poolOptions(); // Customize connectOptions, poolOptions or both, as required return PgPool.pool(input.vertx(), connectOptions, poolOptions); } diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java index 7612924b5a582e..34bb67c732742e 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/PgPoolCreator.java @@ -6,7 +6,6 @@ import io.vertx.core.Vertx; import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.PgPool; -import io.vertx.pgclient.impl.PgPoolOptions; import io.vertx.sqlclient.PoolOptions; /** @@ -26,7 +25,7 @@ interface Input { Vertx vertx(); - PgPoolOptions poolOptions(); + PoolOptions poolOptions(); List pgConnectOptionsList(); } diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java index 058451b3ab8bca..d7763a6d5f4e2f 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java @@ -36,7 +36,6 @@ import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.PgPool; import io.vertx.pgclient.SslMode; -import io.vertx.pgclient.impl.PgPoolOptions; import io.vertx.sqlclient.PoolOptions; @Recorder @@ -74,7 +73,7 @@ private PgPool initialize(Vertx vertx, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig) { - PgPoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, + PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); List pgConnectOptionsList = toPgConnectOptions(dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); @@ -87,7 +86,7 @@ private PgPool initialize(Vertx vertx, return createPool(vertx, poolOptions, pgConnectOptionsList, dataSourceName); } - private PgPoolOptions toPoolOptions(Integer eventLoopCount, + private PoolOptions toPoolOptions(Integer eventLoopCount, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig) { @@ -114,7 +113,7 @@ private PgPoolOptions toPoolOptions(Integer eventLoopCount, poolOptions.setEventLoopSize(Math.max(0, eventLoopCount)); } - return new PgPoolOptions(poolOptions); + return poolOptions; } private List toPgConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, @@ -197,7 +196,7 @@ private List toPgConnectOptions(DataSourceRuntimeConfig dataSo return pgConnectOptionsList; } - private PgPool createPool(Vertx vertx, PgPoolOptions poolOptions, List pgConnectOptionsList, + private PgPool createPool(Vertx vertx, PoolOptions poolOptions, List pgConnectOptionsList, String dataSourceName) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { @@ -215,10 +214,10 @@ private PgPool createPool(Vertx vertx, PgPoolOptions poolOptions, List pgConnectOptionsList; - public DefaultInput(Vertx vertx, PgPoolOptions poolOptions, List pgConnectOptionsList) { + public DefaultInput(Vertx vertx, PoolOptions poolOptions, List pgConnectOptionsList) { this.vertx = vertx; this.poolOptions = poolOptions; this.pgConnectOptionsList = pgConnectOptionsList; @@ -230,7 +229,7 @@ public Vertx vertx() { } @Override - public PgPoolOptions poolOptions() { + public PoolOptions poolOptions() { return poolOptions; } From a3d828e5a498058d6449f6f08ccd9a3fe4f1a8ed Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 24 Apr 2023 07:11:25 +0200 Subject: [PATCH 131/333] Provide correct generic type and annotations in ParamConverterProvider These changes will provide the correct generic type and array of annotations when the JAX-RS annotation is present on a field or a method of a Bean Param class. The solution is similar to what was being done for the parameters of a REST Client method: it will load the metadata (generic type and annotations from fields and methods) of a BeanParam class using reflection only if there is a ParamConverterProvider present. Fix https://github.com/quarkusio/quarkus/issues/32765 --- .../deployment/ClassRestClientContext.java | 47 ++- .../JaxrsClientReactiveProcessor.java | 267 +++++++++++------- .../ParameterDescriptorFromClassSupplier.java | 52 ++++ .../reactive/runtime/RestClientBase.java | 24 +- .../converter/ParamConverterProviderTest.java | 37 ++- .../processor/beanparam/BeanParamItem.java | 12 +- .../processor/beanparam/BeanParamParser.java | 47 +-- .../processor/beanparam/CookieParamItem.java | 4 +- .../processor/beanparam/FormParamItem.java | 4 +- .../processor/beanparam/HeaderParamItem.java | 4 +- .../client/processor/beanparam/Item.java | 8 +- .../processor/beanparam/PathParamItem.java | 4 +- .../processor/beanparam/QueryParamItem.java | 4 +- 13 files changed, 347 insertions(+), 167 deletions(-) create mode 100644 extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java index 1f2762d529d9f9..b7d1f63436ddeb 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java @@ -19,6 +19,7 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.jaxrs.client.reactive.runtime.ParameterAnnotationsSupplier; +import io.quarkus.jaxrs.client.reactive.runtime.ParameterDescriptorFromClassSupplier; import io.quarkus.jaxrs.client.reactive.runtime.ParameterGenericTypesSupplier; class ClassRestClientContext implements AutoCloseable { @@ -30,6 +31,9 @@ class ClassRestClientContext implements AutoCloseable { public final Map methodStaticFields = new HashMap<>(); public final Map methodParamAnnotationsStaticFields = new HashMap<>(); public final Map methodGenericParametersStaticFields = new HashMap<>(); + public final Map beanTypesParameterDescriptorsStaticFields = new HashMap<>(); + public final Map classesMap = new HashMap<>(); + private int beanParamIndex = 0; public ClassRestClientContext(String name, BuildProducer generatedClasses, String... interfaces) { @@ -53,12 +57,12 @@ public void close() { } protected FieldDescriptor createJavaMethodField(ClassInfo interfaceClass, MethodInfo method, int methodIndex) { - ResultHandle interfaceClassHandle = clinit.loadClassFromTCCL(interfaceClass.toString()); + ResultHandle interfaceClassHandle = loadClass(interfaceClass.toString()); ResultHandle parameterArray = clinit.newArray(Class.class, method.parametersCount()); for (int i = 0; i < method.parametersCount(); i++) { String parameterClass = method.parameterType(i).name().toString(); - clinit.writeArrayValue(parameterArray, i, clinit.loadClassFromTCCL(parameterClass)); + clinit.writeArrayValue(parameterArray, i, loadClass(parameterClass)); } ResultHandle javaMethodHandle = clinit.invokeVirtualMethod( @@ -125,4 +129,43 @@ protected Supplier getLazyJavaMethodGenericParametersField(int return javaMethodGenericParametersField; }; } + + /** + * Generates "Class.forName(beanClass)" to generate the parameter descriptors. This method will only be created if and only + * if the supplier is used in order to not have a penalty performance. + */ + protected Supplier getLazyBeanParameterDescriptors(String beanClass) { + return () -> { + FieldDescriptor field = beanTypesParameterDescriptorsStaticFields.get(beanClass); + if (field != null) { + return field; + } + + ResultHandle clazz = loadClass(beanClass); + + ResultHandle mapWithAnnotationsHandle = clinit.newInstance(MethodDescriptor.ofConstructor( + ParameterDescriptorFromClassSupplier.class, Class.class), + clazz); + field = FieldDescriptor.of(classCreator.getClassName(), "beanParamDescriptors" + beanParamIndex, Supplier.class); + classCreator.getFieldCreator(field).setModifiers(Modifier.FINAL | Modifier.STATIC); // needs to be package-private because it's used by subresources + clinit.writeStaticField(field, mapWithAnnotationsHandle); + + beanTypesParameterDescriptorsStaticFields.put(beanClass, field); + + beanParamIndex++; + + return field; + }; + } + + private ResultHandle loadClass(String className) { + ResultHandle classType = classesMap.get(className); + if (classType != null) { + return classType; + } + + ResultHandle classFromTCCL = clinit.loadClassFromTCCL(className); + classesMap.put(className, classFromTCCL); + return classFromTCCL; + } } diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index b7aa64fa5d191c..3113b9af635e7d 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -19,6 +19,7 @@ import java.io.Closeable; import java.io.File; import java.io.InputStream; +import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.AbstractMap; @@ -152,6 +153,7 @@ import io.quarkus.gizmo.TryBlock; import io.quarkus.jaxrs.client.reactive.runtime.ClientResponseBuilderFactory; import io.quarkus.jaxrs.client.reactive.runtime.JaxrsClientReactiveRecorder; +import io.quarkus.jaxrs.client.reactive.runtime.ParameterDescriptorFromClassSupplier; import io.quarkus.jaxrs.client.reactive.runtime.RestClientBase; import io.quarkus.jaxrs.client.reactive.runtime.ToObjectArray; import io.quarkus.jaxrs.client.reactive.runtime.impl.MultipartResponseDataBase; @@ -909,9 +911,8 @@ A more full example of generated client (with sub-resource) can is at the bottom addQueryParam(jandexMethod, methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx), jandexMethod.parameterType(paramIdx), index, methodCreator.getThis(), - methodCreator.readStaticField(methodGenericParametersField.get()), - methodCreator.readStaticField(methodParamAnnotationsField.get()), - paramIdx)); + getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx), + getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx))); } else if (param.parameterType == ParameterType.BEAN || param.parameterType == ParameterType.MULTI_PART_FORM) { // bean params require both, web-target and Invocation.Builder, modifications @@ -930,13 +931,17 @@ A more full example of generated client (with sub-resource) can is at the bottom AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); + + Supplier beanParamDescriptorsField = classContext + .getLazyBeanParameterDescriptors(beanParam.type); + formParams = addBeanParamData(jandexMethod, methodCreator, handleBeanParamMethod, - invocationBuilderRef, beanParam.getItems(), + invocationBuilderRef, classContext, beanParam.getItems(), methodCreator.getMethodParam(paramIdx), methodTarget, index, restClientInterface.getClassName(), methodCreator.getThis(), handleBeanParamMethod.getThis(), - formParams, methodGenericParametersField, methodParamAnnotationsField, paramIdx, multipart, + formParams, beanParamDescriptorsField, multipart, beanParam.type); handleBeanParamMethod.returnValue(invocationBuilderRef); @@ -945,9 +950,8 @@ A more full example of generated client (with sub-resource) can is at the bottom // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue); addPathParam(methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx), param.type, methodCreator.getThis(), - methodCreator.readStaticField(methodGenericParametersField.get()), - methodCreator.readStaticField(methodParamAnnotationsField.get()), - paramIdx); + getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx), + getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx)); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterIdx = paramIdx; @@ -965,8 +969,9 @@ A more full example of generated client (with sub-resource) can is at the bottom handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0)); addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, handleHeaderMethod.getMethodParam(1), param.type, - handleHeaderMethod.getThis(), methodGenericParametersField.get(), - methodParamAnnotationsField.get(), paramIdx); + handleHeaderMethod.getThis(), + getGenericTypeFromArray(handleHeaderMethod, methodGenericParametersField, paramIdx), + getAnnotationsFromArray(handleHeaderMethod, methodParamAnnotationsField, paramIdx)); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.COOKIE) { @@ -984,7 +989,8 @@ A more full example of generated client (with sub-resource) can is at the bottom addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, handleCookieMethod.getMethodParam(1), param.type, handleCookieMethod.getThis(), - methodGenericParametersField.get(), methodParamAnnotationsField.get(), paramIdx); + getGenericTypeFromArray(handleCookieMethod, methodGenericParametersField, paramIdx), + getAnnotationsFromArray(handleCookieMethod, methodParamAnnotationsField, paramIdx)); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.FORM) { @@ -993,9 +999,9 @@ A more full example of generated client (with sub-resource) can is at the bottom addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), param.declaredType, param.signature, restClientInterface.getClassName(), methodCreator.getThis(), formParams, - methodCreator.readStaticField(methodGenericParametersField.get()), - methodCreator.readStaticField(methodParamAnnotationsField.get()), - paramIdx, multipart, + getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx), + getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx), + multipart, param.mimeType, param.partFileName, jandexMethod.declaringClass().name() + "." + jandexMethod.name()); } @@ -1215,9 +1221,8 @@ private void handleSubResourceMethod(List addPathParam(ownerMethod, webTarget, param.name, paramValue, param.type, client, - ownerMethod.readStaticField(methodGenericParametersField.get()), - ownerMethod.readStaticField(methodParamAnnotationsField.get()), - i); + getGenericTypeFromArray(ownerMethod, methodGenericParametersField, i), + getAnnotationsFromArray(ownerMethod, methodParamAnnotationsField, i)); } } @@ -1322,9 +1327,10 @@ private void handleSubResourceMethod(List addQueryParam(jandexMethod, subMethodCreator, methodTarget, param.name, paramValue, subParamField.type, index, subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), - subMethodCreator.readStaticField(subParamField.genericsParametersField.get()), - subMethodCreator.readStaticField(subParamField.paramAnnotationsField.get()), - subParamField.paramIndex)); + getGenericTypeFromArray(subMethodCreator, subParamField.genericsParametersField, + subParamField.paramIndex), + getAnnotationsFromArray(subMethodCreator, subParamField.paramAnnotationsField, + subParamField.paramIndex))); } else if (param.parameterType == ParameterType.BEAN || param.parameterType == ParameterType.MULTI_PART_FORM) { // bean params require both, web-target and Invocation.Builder, modifications @@ -1341,17 +1347,20 @@ private void handleSubResourceMethod(List MethodCreator handleBeanParamMethod = subContext.classCreator.getMethodCreator( handleBeanParamDescriptor).setModifiers(Modifier.PRIVATE); + Supplier beanParamDescriptors = subContext + .getLazyBeanParameterDescriptors(beanParam.type); + AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); formParams = addBeanParamData(jandexMethod, subMethodCreator, handleBeanParamMethod, - invocationBuilderRef, beanParam.getItems(), + invocationBuilderRef, subContext, beanParam.getItems(), paramValue, methodTarget, index, interfaceClass.name().toString(), subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), handleBeanParamMethod.readInstanceField(clientField, handleBeanParamMethod.getThis()), formParams, - methodGenericParametersField, methodParamAnnotationsField, subParamField.paramIndex, + beanParamDescriptors, multipart, beanParam.type); handleBeanParamMethod.returnValue(invocationBuilderRef); @@ -1361,9 +1370,10 @@ private void handleSubResourceMethod(List addPathParam(subMethodCreator, methodTarget, param.name, paramValue, param.type, subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), - subMethodCreator.readStaticField(subParamField.genericsParametersField.get()), - subMethodCreator.readStaticField(subParamField.paramAnnotationsField.get()), - subParamField.paramIndex); + getGenericTypeFromArray(subMethodCreator, subParamField.genericsParametersField, + subParamField.paramIndex), + getAnnotationsFromArray(subMethodCreator, subParamField.paramAnnotationsField, + subParamField.paramIndex)); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterValue = paramValue; @@ -1384,9 +1394,10 @@ private void handleSubResourceMethod(List handleHeaderMethod.getMethodParam(1), param.type, handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()), - subParamField.genericsParametersField.get(), - subParamField.paramAnnotationsField.get(), - subParamField.paramIndex); + getGenericTypeFromArray(handleHeaderMethod, subParamField.genericsParametersField, + subParamField.paramIndex), + getAnnotationsFromArray(handleHeaderMethod, subParamField.paramAnnotationsField, + subParamField.paramIndex)); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, paramValue); } else if (param.parameterType == ParameterType.COOKIE) { @@ -1406,9 +1417,10 @@ private void handleSubResourceMethod(List handleCookieMethod.getMethodParam(1), param.type, handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis()), - subParamField.genericsParametersField.get(), - subParamField.paramAnnotationsField.get(), - subParamField.paramIndex); + getGenericTypeFromArray(handleCookieMethod, subParamField.genericsParametersField, + subParamField.paramIndex), + getAnnotationsFromArray(handleCookieMethod, subParamField.paramAnnotationsField, + subParamField.paramIndex)); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleCookieDescriptor, paramValue); } else if (param.parameterType == ParameterType.FORM) { @@ -1430,9 +1442,10 @@ private void handleSubResourceMethod(List subMethodCreator.getMethodParam(paramIdx), jandexSubMethod.parameterType(paramIdx), index, subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), - subMethodCreator.readStaticField(subMethodGenericParametersField.get()), - subMethodCreator.readStaticField(subMethodParamAnnotationsField.get()), - paramIdx)); + getGenericTypeFromArray(subMethodCreator, subMethodGenericParametersField, + paramIdx), + getAnnotationsFromArray(subMethodCreator, subMethodParamAnnotationsField, + paramIdx))); } else if (param.parameterType == ParameterType.BEAN || param.parameterType == ParameterType.MULTI_PART_FORM) { // bean params require both, web-target and Invocation.Builder, modifications @@ -1445,20 +1458,23 @@ private void handleSubResourceMethod(List subMethod.getName() + "$$" + subMethodIndex + "$$handleBeanParam$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleBeanParamMethod = ownerContext.classCreator.getMethodCreator( + MethodCreator handleBeanParamMethod = subContext.classCreator.getMethodCreator( handleBeanParamDescriptor).setModifiers(Modifier.PRIVATE); + Supplier beanParamDescriptors = subContext + .getLazyBeanParameterDescriptors(beanParam.type); + AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); formParams = addBeanParamData(jandexMethod, subMethodCreator, handleBeanParamMethod, - invocationBuilderRef, beanParam.getItems(), + invocationBuilderRef, subContext, beanParam.getItems(), subMethodCreator.getMethodParam(paramIdx), methodTarget, index, interfaceClass.name().toString(), subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), handleBeanParamMethod.readInstanceField(clientField, handleBeanParamMethod.getThis()), formParams, - subMethodGenericParametersField, subMethodParamAnnotationsField, paramIdx, multipart, + beanParamDescriptors, multipart, beanParam.type); handleBeanParamMethod.returnValue(invocationBuilderRef); @@ -1468,9 +1484,8 @@ private void handleSubResourceMethod(List addPathParam(subMethodCreator, methodTarget, param.name, subMethodCreator.getMethodParam(paramIdx), param.type, subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), - subMethodCreator.readStaticField(subMethodGenericParametersField.get()), - subMethodCreator.readStaticField(subMethodParamAnnotationsField.get()), - paramIdx); + getGenericTypeFromArray(subMethodCreator, subMethodGenericParametersField, paramIdx), + getAnnotationsFromArray(subMethodCreator, subMethodParamAnnotationsField, paramIdx)); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterValue = subMethodCreator.getMethodParam(paramIdx); @@ -1489,7 +1504,8 @@ private void handleSubResourceMethod(List addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, handleHeaderMethod.getMethodParam(1), param.type, handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()), - subMethodGenericParametersField.get(), subMethodParamAnnotationsField.get(), paramIdx); + getGenericTypeFromArray(handleHeaderMethod, subMethodGenericParametersField, paramIdx), + getAnnotationsFromArray(handleHeaderMethod, subMethodParamAnnotationsField, paramIdx)); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.COOKIE) { @@ -1508,7 +1524,8 @@ private void handleSubResourceMethod(List handleCookieMethod.getMethodParam(1), param.type, handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis()), - subMethodGenericParametersField.get(), subMethodParamAnnotationsField.get(), paramIdx); + getGenericTypeFromArray(handleCookieMethod, subMethodGenericParametersField, paramIdx), + getAnnotationsFromArray(handleCookieMethod, subMethodParamAnnotationsField, paramIdx)); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleCookieDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.FORM) { @@ -1635,7 +1652,7 @@ private void handleMultipartField(String formParamName, String partType, String String type, String parameterGenericType, ResultHandle fieldValue, AssignableResultHandle multipartForm, BytecodeCreator methodCreator, - ResultHandle client, String restClientInterfaceClassName, ResultHandle parameterAnnotations, int methodIndex, + ResultHandle client, String restClientInterfaceClassName, ResultHandle parameterAnnotations, ResultHandle genericType, String errorLocation) { BytecodeCreator ifValueNotNull = methodCreator.ifNotNull(fieldValue).trueBranch(); @@ -1679,7 +1696,7 @@ private void handleMultipartField(String formParamName, String partType, String } else { // go via converter ResultHandle convertedFormParam = convertParamToString(ifValueNotNull, client, fieldValue, type, genericType, - parameterAnnotations, methodIndex); + parameterAnnotations); BytecodeCreator parameterIsStringBranch = checkStringParam(ifValueNotNull, convertedFormParam, restClientInterfaceClassName, errorLocation); addString(parameterIsStringBranch, multipartForm, formParamName, null, partFilename, convertedFormParam); @@ -2270,6 +2287,7 @@ private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) BytecodeCreator invocationBuilderEnricher, AssignableResultHandle invocationBuilder, + ClassRestClientContext classContext, List beanParamItems, ResultHandle param, // can only be used in the current method, not in `invocationBuilderEnricher` @@ -2280,18 +2298,17 @@ private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod, ResultHandle invocationEnricherClient, // this client or containing client if this is a sub-client AssignableResultHandle formParams, - Supplier methodGenericTypeField, - Supplier methodParamAnnotationsField, - int paramIdx, boolean multipart, String beanParamClass) { + Supplier descriptorsField, + boolean multipart, String beanParamClass) { // Form params collector must be initialized at method root level before any inner blocks that may use it if (areFormParamsDefinedIn(beanParamItems)) { formParams = createFormDataIfAbsent(methodCreator, formParams, multipart); } - addSubBeanParamData(jandexMethod, methodCreator, invocationBuilderEnricher, invocationBuilder, beanParamItems, param, - target, + addSubBeanParamData(jandexMethod, methodCreator, invocationBuilderEnricher, invocationBuilder, classContext, + beanParamItems, param, target, index, restClientInterfaceClassName, client, invocationEnricherClient, formParams, - methodGenericTypeField, methodParamAnnotationsField, paramIdx, multipart, beanParamClass); + descriptorsField, multipart, beanParamClass); return formParams; } @@ -2300,6 +2317,7 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) BytecodeCreator invocationBuilderEnricher, AssignableResultHandle invocationBuilder, + ClassRestClientContext classContext, List beanParamItems, ResultHandle param, // can only be used in the current method, not in `invocationBuilderEnricher` @@ -2310,9 +2328,8 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method // this client or containing client if this is a sub-client ResultHandle invocationEnricherClient, AssignableResultHandle formParams, - Supplier methodGenericTypeField, - Supplier methodParamAnnotationsField, - int paramIdx, boolean multipart, String beanParamClass) { + Supplier beanParamDescriptorField, + boolean multipart, String beanParamClass) { BytecodeCreator creator = methodCreator.ifNotNull(param).trueBranch(); BytecodeCreator invoEnricher = invocationBuilderEnricher.ifNotNull(invocationBuilderEnricher.getMethodParam(1)) .trueBranch(); @@ -2322,10 +2339,12 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method case BEAN_PARAM: BeanParamItem beanParamItem = (BeanParamItem) item; ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param); - addSubBeanParamData(jandexMethod, creator, invoEnricher, invocationBuilder, beanParamItem.items(), - beanParamElementHandle, target, index, restClientInterfaceClassName, client, + Supplier newBeanParamDescriptorField = classContext + .getLazyBeanParameterDescriptors(beanParamItem.className()); + addSubBeanParamData(jandexMethod, creator, invoEnricher, invocationBuilder, classContext, + beanParamItem.items(), beanParamElementHandle, target, index, restClientInterfaceClassName, client, invocationEnricherClient, formParams, - methodGenericTypeField, methodParamAnnotationsField, paramIdx, multipart, + newBeanParamDescriptorField, multipart, beanParamItem.className()); break; case QUERY_PARAM: @@ -2335,9 +2354,8 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method queryParam.extract(creator, param), queryParam.getValueType(), index, client, - creator.readStaticField(methodGenericTypeField.get()), - creator.readStaticField(methodParamAnnotationsField.get()), - paramIdx)); + getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()), + getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName()))); break; case COOKIE: CookieParamItem cookieParam = (CookieParamItem) item; @@ -2345,33 +2363,34 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method cookieParam.getCookieName(), cookieParam.extract(invoEnricher, invoEnricher.getMethodParam(1)), cookieParam.getParamType(), invocationEnricherClient, - methodGenericTypeField.get(), methodParamAnnotationsField.get(), paramIdx); + getGenericTypeFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()), + getAnnotationsFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName())); break; case HEADER_PARAM: HeaderParamItem headerParam = (HeaderParamItem) item; addHeaderParam(invoEnricher, invocationBuilder, headerParam.getHeaderName(), headerParam.extract(invoEnricher, invoEnricher.getMethodParam(1)), - headerParam.getParamType(), invocationEnricherClient, methodGenericTypeField.get(), - methodParamAnnotationsField.get(), paramIdx); + headerParam.getParamType(), invocationEnricherClient, + getGenericTypeFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()), + getAnnotationsFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName())); break; case PATH_PARAM: PathParamItem pathParam = (PathParamItem) item; addPathParam(creator, target, pathParam.getPathParamName(), pathParam.extract(creator, param), pathParam.getParamType(), client, - creator.readStaticField(methodGenericTypeField.get()), - creator.readStaticField(methodParamAnnotationsField.get()), - paramIdx); + getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()), + getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName())); break; case FORM_PARAM: FormParamItem formParam = (FormParamItem) item; addFormParam(creator, formParam.getFormParamName(), formParam.extract(creator, param), formParam.getParamType(), formParam.getParamSignature(), restClientInterfaceClassName, client, formParams, - creator.readStaticField(methodGenericTypeField.get()), - creator.readStaticField(methodParamAnnotationsField.get()), - paramIdx, multipart, formParam.getMimeType(), formParam.getFileName(), + getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()), + getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName()), + multipart, formParam.getMimeType(), formParam.getFileName(), beanParamClass + "." + formParam.getSourceName()); break; default: @@ -2380,6 +2399,64 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method } } + private ResultHandle getGenericTypeFromParameter(BytecodeCreator creator, Supplier supplier, + String name) { + // Will return Map + ResultHandle map = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class), + creator.readStaticField(supplier.get())); + // Will return ParameterDescriptorFromClassSupplier.ParameterDescriptor; + ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Map.class, "get", Object.class, Object.class), + map, creator.load(name)); + // if (value != null) return value.genericType; + AssignableResultHandle genericType = creator.createVariable(java.lang.reflect.Type.class); + BranchResult ifBranch = creator.ifNotNull(value); + BytecodeCreator ifNotNull = ifBranch.trueBranch(); + ifNotNull.assign(genericType, ifNotNull.readInstanceField( + FieldDescriptor.of(ParameterDescriptorFromClassSupplier.ParameterDescriptor.class, "genericType", + java.lang.reflect.Type.class), + value)); + // if (value == null) return null; + BytecodeCreator ifNull = ifBranch.falseBranch(); + ifNull.assign(genericType, ifNull.loadNull()); + return genericType; + } + + private ResultHandle getGenericTypeFromArray(BytecodeCreator creator, Supplier supplier, + int paramIdx) { + ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class), + creator.readStaticField(supplier.get())); + return creator.readArrayValue(creator.checkCast(value, java.lang.reflect.Type[].class), paramIdx); + } + + private ResultHandle getAnnotationsFromParameter(BytecodeCreator creator, Supplier supplier, + String name) { + // Will return Map + ResultHandle map = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class), + creator.readStaticField(supplier.get())); + // Will return ParameterDescriptorFromClassSupplier.ParameterDescriptor; + ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Map.class, "get", Object.class, Object.class), + map, creator.load(name)); + // if (value != null) return value.genericType; + AssignableResultHandle annotations = creator.createVariable(Annotation[].class); + BranchResult ifBranch = creator.ifNotNull(value); + BytecodeCreator ifNotNull = ifBranch.trueBranch(); + ifNotNull.assign(annotations, ifNotNull.readInstanceField( + FieldDescriptor.of(ParameterDescriptorFromClassSupplier.ParameterDescriptor.class, "annotations", + Annotation[].class), + value)); + // if (value == null) return null; + BytecodeCreator ifNull = ifBranch.falseBranch(); + ifNull.assign(annotations, ifNull.loadNull()); + return annotations; + } + + private ResultHandle getAnnotationsFromArray(BytecodeCreator creator, Supplier supplier, + int paramIdx) { + ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class), + creator.readStaticField(supplier.get())); + return creator.readArrayValue(creator.checkCast(value, Annotation[][].class), paramIdx); + } + private boolean areFormParamsDefinedIn(List beanParamItems) { for (Item item : beanParamItems) { switch (item.type()) { @@ -2406,7 +2483,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth // this client or containing client if we're in a subresource ResultHandle client, ResultHandle genericType, - ResultHandle paramAnnotations, int paramIndex) { + ResultHandle paramAnnotations) { AssignableResultHandle result = methodCreator.createVariable(WebTarget.class); BranchResult isParamNull = methodCreator.ifNull(queryParamHandle); @@ -2453,7 +2530,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth } // get the new WebTarget addQueryParamToWebTarget(loopCreator, key, result, client, genericType, paramAnnotations, - paramIndex, paramArray, componentType, result); + paramArray, componentType, result); } else { ResultHandle paramArray; String componentType = null; @@ -2481,8 +2558,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth } addQueryParamToWebTarget(notNullParam, notNullParam.load(paramName), webTarget, client, genericType, - paramAnnotations, paramIndex, - paramArray, componentType, result); + paramAnnotations, paramArray, componentType, result); } isParamNull.trueBranch().assign(result, webTarget); @@ -2521,14 +2597,13 @@ private BranchResult iteratorHasNext(BytecodeCreator creator, ResultHandle itera private void addQueryParamToWebTarget(BytecodeCreator creator, ResultHandle paramName, ResultHandle webTarget, ResultHandle client, ResultHandle genericType, - ResultHandle paramAnnotations, int paramIndex, ResultHandle paramArray, + ResultHandle paramAnnotations, ResultHandle paramArray, String componentType, AssignableResultHandle resultVariable) { ResultHandle convertedParamArray = creator.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "convertParamArray", Object[].class, Object[].class, - Class.class, Supplier.class, Supplier.class, int.class), - client, paramArray, creator.loadClassFromTCCL(componentType), genericType, paramAnnotations, - creator.load(paramIndex)); + Class.class, java.lang.reflect.Type.class, Annotation[].class), + client, paramArray, creator.loadClassFromTCCL(componentType), genericType, paramAnnotations); creator.assign(resultVariable, creator.invokeInterfaceMethod( MethodDescriptor.ofMethod(WebTarget.class, "queryParam", WebTarget.class, @@ -2563,19 +2638,15 @@ private boolean isMap(Type type, IndexView index) { private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, String paramName, ResultHandle headerParamHandle, String paramType, ResultHandle client, - FieldDescriptor methodGenericTypeField, FieldDescriptor methodParamAnnotationsField, - int paramIdx) { + ResultHandle genericType, ResultHandle annotations) { BytecodeCreator notNullValue = invoBuilderEnricher.ifNull(headerParamHandle).falseBranch(); - ResultHandle genericType = notNullValue.readStaticField(methodGenericTypeField); - - ResultHandle parameterAnnotations = notNullValue.readStaticField(methodParamAnnotationsField); headerParamHandle = notNullValue.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, - Object.class, Class.class, Supplier.class, Supplier.class, int.class), + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, headerParamHandle, - notNullValue.loadClassFromTCCL(paramType), genericType, parameterAnnotations, notNullValue.load(paramIdx)); + notNullValue.loadClassFromTCCL(paramType), genericType, annotations); notNullValue.assign(invocationBuilder, notNullValue.invokeInterfaceMethod( @@ -2586,13 +2657,12 @@ private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResul private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle methodTarget, String paramName, ResultHandle pathParamHandle, String parameterType, ResultHandle client, - ResultHandle genericType, ResultHandle parameterAnnotations, int paramIndex) { + ResultHandle genericType, ResultHandle parameterAnnotations) { ResultHandle handle = methodCreator.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, - Object.class, Class.class, Supplier.class, Supplier.class, int.class), + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, pathParamHandle, - methodCreator.loadClassFromTCCL(parameterType), genericType, parameterAnnotations, - methodCreator.load(paramIndex)); + methodCreator.loadClassFromTCCL(parameterType), genericType, parameterAnnotations); methodCreator.assign(methodTarget, methodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD, methodTarget, @@ -2603,17 +2673,17 @@ private void addFormParam(BytecodeCreator methodCreator, String paramName, Resul String parameterType, String parameterGenericType, String restClientInterfaceClassName, ResultHandle client, AssignableResultHandle formParams, ResultHandle genericType, - ResultHandle parameterAnnotations, int methodIndex, boolean multipart, + ResultHandle parameterAnnotations, boolean multipart, String partType, String partFilename, String errorLocation) { if (multipart) { handleMultipartField(paramName, partType, partFilename, parameterType, parameterGenericType, formParamHandle, formParams, methodCreator, - client, restClientInterfaceClassName, parameterAnnotations, methodIndex, genericType, + client, restClientInterfaceClassName, parameterAnnotations, genericType, errorLocation); } else { BytecodeCreator notNullValue = methodCreator.ifNull(formParamHandle).falseBranch(); ResultHandle convertedFormParam = convertParamToString(notNullValue, client, formParamHandle, parameterType, - genericType, parameterAnnotations, methodIndex); + genericType, parameterAnnotations); BytecodeCreator parameterIsStringBranch = checkStringParam(notNullValue, convertedFormParam, restClientInterfaceClassName, errorLocation); parameterIsStringBranch.invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams, @@ -2637,30 +2707,25 @@ private BytecodeCreator checkStringParam(BytecodeCreator notNullValue, ResultHan private ResultHandle convertParamToString(BytecodeCreator notNullValue, ResultHandle client, ResultHandle formParamHandle, String parameterType, - ResultHandle genericType, ResultHandle parameterAnnotations, int methodIndex) { + ResultHandle genericType, ResultHandle parameterAnnotations) { return notNullValue.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, - Object.class, Class.class, Supplier.class, Supplier.class, int.class), + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, formParamHandle, - notNullValue.loadClassFromTCCL(parameterType), genericType, parameterAnnotations, - notNullValue.load(methodIndex)); + notNullValue.loadClassFromTCCL(parameterType), genericType, parameterAnnotations); } private void addCookieParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, String paramName, ResultHandle cookieParamHandle, String paramType, ResultHandle client, - FieldDescriptor methodGenericTypeField, FieldDescriptor methodParamAnnotationsField, int paramIdx) { + ResultHandle genericType, ResultHandle annotations) { BytecodeCreator notNullValue = invoBuilderEnricher.ifNull(cookieParamHandle).falseBranch(); - ResultHandle genericType = notNullValue.readStaticField(methodGenericTypeField); - - ResultHandle parameterAnnotations = notNullValue.readStaticField(methodParamAnnotationsField); - cookieParamHandle = notNullValue.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, - Object.class, Class.class, Supplier.class, Supplier.class, int.class), + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, cookieParamHandle, - notNullValue.loadClassFromTCCL(paramType), genericType, parameterAnnotations, notNullValue.load(paramIdx)); + notNullValue.loadClassFromTCCL(paramType), genericType, annotations); notNullValue.assign(invocationBuilder, notNullValue.invokeInterfaceMethod( MethodDescriptor.ofMethod(Invocation.Builder.class, "cookie", Invocation.Builder.class, String.class, diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java new file mode 100644 index 00000000000000..72cb181e8f2957 --- /dev/null +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java @@ -0,0 +1,52 @@ +package io.quarkus.jaxrs.client.reactive.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class ParameterDescriptorFromClassSupplier + implements Supplier> { + + private final Class clazz; + private volatile Map value; + + public ParameterDescriptorFromClassSupplier(Class clazz) { + this.clazz = clazz; + } + + @Override + public Map get() { + if (value == null) { + value = new HashMap<>(); + Class currentClass = clazz; + while (currentClass != null && currentClass != Object.class) { + for (Field field : currentClass.getDeclaredFields()) { + ParameterDescriptor descriptor = new ParameterDescriptor(); + descriptor.annotations = field.getAnnotations(); + descriptor.genericType = field.getGenericType(); + value.put(field.getName(), descriptor); + } + + for (Method method : currentClass.getDeclaredMethods()) { + ParameterDescriptor descriptor = new ParameterDescriptor(); + descriptor.annotations = method.getAnnotations(); + descriptor.genericType = method.getGenericReturnType(); + value.put(method.getName(), descriptor); + } + + currentClass = currentClass.getSuperclass(); + } + } + + return value; + } + + public static class ParameterDescriptor { + public Annotation[] annotations; + public Type genericType; + } +} diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java index c7f70ad6d0854e..45c8aef084b2c5 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java @@ -7,7 +7,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; import jakarta.ws.rs.ext.ParamConverter; import jakarta.ws.rs.ext.ParamConverterProvider; @@ -122,9 +121,8 @@ public RestClientBase(List providers) { } @SuppressWarnings("unused") // used by generated code - public Object[] convertParamArray(T[] value, Class type, Supplier genericType, - Supplier methodAnnotations, int paramIndex) { - ParamConverter converter = getConverter(type, genericType, methodAnnotations, paramIndex); + public Object[] convertParamArray(T[] value, Class type, Type genericType, Annotation[] annotations) { + ParamConverter converter = getConverter(type, genericType, annotations); if (converter == null) { return value; @@ -139,10 +137,8 @@ public Object[] convertParamArray(T[] value, Class type, Supplier } @SuppressWarnings("unused") // used by generated code - public Object convertParam(T value, Class type, Supplier genericType, - Supplier methodAnnotations, - int paramIndex) { - ParamConverter converter = getConverter(type, genericType, methodAnnotations, paramIndex); + public Object convertParam(T value, Class type, Type genericType, Annotation[] annotations) { + ParamConverter converter = getConverter(type, genericType, annotations); if (converter != null) { return converter.toString(value); } else { @@ -154,30 +150,26 @@ public Object convertParam(T value, Class type, Supplier genericT } } - private ParamConverter getConverter(Class type, Supplier genericType, - Supplier methodAnnotations, - int paramIndex) { + private ParamConverter getConverter(Class type, Type genericType, Annotation[] annotations) { ParamConverterProvider converterProvider = providerForClass.get(type); if (converterProvider == null) { for (ParamConverterProvider provider : paramConverterProviders) { - ParamConverter converter = provider.getConverter(type, genericType.get()[paramIndex], - methodAnnotations.get()[paramIndex]); + ParamConverter converter = provider.getConverter(type, genericType, annotations); if (converter != null) { providerForClass.put(type, provider); return converter; } } // FIXME: this should go in favour of generating them, so we can generate them only if used for dead-code elimination - ParamConverter converter = DEFAULT_PROVIDER.getConverter(type, genericType.get()[paramIndex], - methodAnnotations.get()[paramIndex]); + ParamConverter converter = DEFAULT_PROVIDER.getConverter(type, genericType, annotations); if (converter != null) { providerForClass.put(type, DEFAULT_PROVIDER); return converter; } providerForClass.put(type, NO_PROVIDER); } else if (converterProvider != NO_PROVIDER) { - return converterProvider.getConverter(type, genericType.get()[paramIndex], methodAnnotations.get()[paramIndex]); + return converterProvider.getConverter(type, genericType, annotations); } return null; } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java index 535ea3ebe20656..b56ac980cadb0b 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java @@ -2,10 +2,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.Type; import java.net.URI; +import java.util.Arrays; import jakarta.ws.rs.BeanParam; import jakarta.ws.rs.CookieParam; @@ -74,7 +80,7 @@ void shouldConvertHeaderParams() { void shouldConvertCookieParams() { Client client = RestClientBuilder.newBuilder().baseUri(baseUri) .build(Client.class); - assertThat(client.getWithHeader(Param.FIRST)).isEqualTo("1"); + assertThat(client.getWithCookie(Param.FIRST)).isEqualTo("1"); assertThat(client.sub().getWithCookie(Param.SECOND)).isEqualTo("2"); Bean bean = new Bean(); @@ -93,7 +99,7 @@ interface Client { @GET @Path("/param/{param}") - String get(@PathParam("param") Param param); + String get(@MyAnnotation("myValue") @PathParam("param") Param param); @GET @Path("/param/{param}") @@ -101,7 +107,7 @@ interface Client { @GET @Path("/query") - String getWithQuery(@QueryParam("param") Param param); + String getWithQuery(@MyAnnotation("myValue") @QueryParam("param") Param param); @GET @Path("/query") @@ -109,7 +115,7 @@ interface Client { @GET @Path("/header") - String getWithHeader(@HeaderParam("param") Param param); + String getWithHeader(@MyAnnotation("myValue") @HeaderParam("param") Param param); @GET @Path("/header") @@ -117,7 +123,7 @@ interface Client { @GET @Path("/cookie") - String getWithCookie(@HeaderParam("cookie-param") Param param); + String getWithCookie(@MyAnnotation("myValue") @CookieParam("cookie-param") Param param); @GET @Path("/cookie") @@ -127,28 +133,32 @@ interface Client { interface SubClient { @GET @Path("/param/{param}") - String get(@PathParam("param") Param param); + String get(@MyAnnotation("myValue") @PathParam("param") Param param); @GET @Path("/query") - String getWithQuery(@QueryParam("param") Param param); + String getWithQuery(@MyAnnotation("myValue") @QueryParam("param") Param param); @GET @Path("/header") - String getWithHeader(@HeaderParam("param") Param param); + String getWithHeader(@MyAnnotation("myValue") @HeaderParam("param") Param param); @GET @Path("cookie") - String getWithCookie(@CookieParam("cookie-param") Param param); + String getWithCookie(@MyAnnotation("myValue") @CookieParam("cookie-param") Param param); } public static class Bean { + @MyAnnotation("myValue") @PathParam("param") public Param param; + @MyAnnotation("myValue") @QueryParam("param") public Param queryParam; + @MyAnnotation("myValue") @HeaderParam("param") public Param headerParam; + @MyAnnotation("myValue") @CookieParam("cookie-param") public Param cookieParam; } @@ -158,6 +168,12 @@ enum Param { SECOND } + @Target({ ElementType.FIELD, ElementType.PARAMETER }) + @Retention(RetentionPolicy.RUNTIME) + public @interface MyAnnotation { + String value() default ""; + } + public static class ParamConverter implements ParamConverterProvider { @SuppressWarnings("unchecked") @Override @@ -171,6 +187,9 @@ public jakarta.ws.rs.ext.ParamConverter getConverter(Class rawType, Ty fail("Annotations cannot be null!"); } + assertTrue(Arrays.stream(annotations) + .anyMatch(a -> a instanceof MyAnnotation && ((MyAnnotation) a).value().equals("myValue"))); + if (rawType == Param.class) { return (jakarta.ws.rs.ext.ParamConverter) new jakarta.ws.rs.ext.ParamConverter() { @Override diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamItem.java index 4a31a0346ef47f..35abc4465f858a 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamItem.java @@ -6,6 +6,12 @@ public class BeanParamItem extends Item { private final List items; private final String className; + public BeanParamItem(String fieldName, List items, String className, ValueExtractor extractor) { + super(fieldName, ItemType.BEAN_PARAM, extractor); + this.items = items; + this.className = className; + } + public String className() { return className; } @@ -13,10 +19,4 @@ public String className() { public List items() { return items; } - - public BeanParamItem(List items, String className, ValueExtractor extractor) { - super(ItemType.BEAN_PARAM, extractor); - this.items = items; - this.className = className; - } } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java index d14a3f22ced9dc..b85ccd9813516b 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java @@ -61,18 +61,19 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde } resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, QUERY_PARAM, - (annotationValue, fieldInfo) -> new QueryParamItem(annotationValue, + (annotationValue, fieldInfo) -> new QueryParamItem(fieldInfo.name(), annotationValue, new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), fieldInfo.type()), - (annotationValue, getterMethod) -> new QueryParamItem(annotationValue, new GetterExtractor(getterMethod), + (annotationValue, getterMethod) -> new QueryParamItem(getterMethod.name(), annotationValue, + new GetterExtractor(getterMethod), getterMethod.returnType()))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_QUERY_PARAM, - (annotationValue, fieldInfo) -> new QueryParamItem( + (annotationValue, fieldInfo) -> new QueryParamItem(fieldInfo.name(), annotationValue != null ? annotationValue : fieldInfo.name(), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), fieldInfo.type()), - (annotationValue, getterMethod) -> new QueryParamItem( + (annotationValue, getterMethod) -> new QueryParamItem(getterMethod.name(), annotationValue != null ? annotationValue : getterName(getterMethod), new GetterExtractor(getterMethod), getterMethod.returnType()))); @@ -84,7 +85,7 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde DotName beanParamClassName = type.asClassType().name(); List subBeanParamItems = parseInternal(index.getClassByName(beanParamClassName), index, processedBeanParamClasses); - return new BeanParamItem(subBeanParamItems, beanParamClassName.toString(), + return new BeanParamItem(fieldInfo.name(), subBeanParamItems, beanParamClassName.toString(), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())); } else { throw new IllegalArgumentException("BeanParam annotation used on a field that is not an object: " @@ -95,69 +96,71 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde Type returnType = getterMethod.returnType(); List items = parseInternal(index.getClassByName(returnType.name()), index, processedBeanParamClasses); - return new BeanParamItem(items, beanParamClass.name().toString(), new GetterExtractor(getterMethod)); + return new BeanParamItem(getterMethod.name(), items, beanParamClass.name().toString(), + new GetterExtractor(getterMethod)); })); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, COOKIE_PARAM, - (annotationValue, fieldInfo) -> new CookieParamItem(annotationValue, + (annotationValue, fieldInfo) -> new CookieParamItem(fieldInfo.name(), annotationValue, new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), fieldInfo.type().name().toString()), - (annotationValue, getterMethod) -> new CookieParamItem(annotationValue, + (annotationValue, getterMethod) -> new CookieParamItem(getterMethod.name(), annotationValue, new GetterExtractor(getterMethod), getterMethod.returnType().name().toString()))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_COOKIE_PARAM, - (annotationValue, fieldInfo) -> new CookieParamItem( + (annotationValue, fieldInfo) -> new CookieParamItem(fieldInfo.name(), annotationValue != null ? annotationValue : fieldInfo.name(), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), fieldInfo.type().name().toString()), - (annotationValue, getterMethod) -> new CookieParamItem( + (annotationValue, getterMethod) -> new CookieParamItem(getterMethod.name(), annotationValue != null ? annotationValue : getterName(getterMethod), new GetterExtractor(getterMethod), getterMethod.returnType().name().toString()))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, HEADER_PARAM, - (annotationValue, fieldInfo) -> new HeaderParamItem(annotationValue, + (annotationValue, fieldInfo) -> new HeaderParamItem(fieldInfo.name(), annotationValue, new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), fieldInfo.type().name().toString()), - (annotationValue, getterMethod) -> new HeaderParamItem(annotationValue, + (annotationValue, getterMethod) -> new HeaderParamItem(getterMethod.name(), annotationValue, new GetterExtractor(getterMethod), getterMethod.returnType().name().toString()))); // @RestHeader with no explicit value are hyphenated resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_HEADER_PARAM, - (annotationValue, fieldInfo) -> new HeaderParamItem( + (annotationValue, fieldInfo) -> new HeaderParamItem(fieldInfo.name(), annotationValue != null ? annotationValue : StringUtil.hyphenateWithCapitalFirstLetter(fieldInfo.name()), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), fieldInfo.type().name().toString()), - (annotationValue, getterMethod) -> new HeaderParamItem( + (annotationValue, getterMethod) -> new HeaderParamItem(getterMethod.name(), annotationValue != null ? annotationValue : StringUtil.hyphenateWithCapitalFirstLetter(getterName(getterMethod)), new GetterExtractor(getterMethod), getterMethod.returnType().name().toString()))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, PATH_PARAM, - (annotationValue, fieldInfo) -> new PathParamItem(annotationValue, fieldInfo.type().name().toString(), + (annotationValue, fieldInfo) -> new PathParamItem(fieldInfo.name(), annotationValue, + fieldInfo.type().name().toString(), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())), - (annotationValue, getterMethod) -> new PathParamItem(annotationValue, + (annotationValue, getterMethod) -> new PathParamItem(getterMethod.name(), annotationValue, getterMethod.returnType().name().toString(), new GetterExtractor(getterMethod)))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_PATH_PARAM, - (annotationValue, fieldInfo) -> new PathParamItem( + (annotationValue, fieldInfo) -> new PathParamItem(fieldInfo.name(), annotationValue != null ? annotationValue : fieldInfo.name(), fieldInfo.type().name().toString(), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())), - (annotationValue, getterMethod) -> new PathParamItem( + (annotationValue, getterMethod) -> new PathParamItem(getterMethod.name(), annotationValue != null ? annotationValue : getterName(getterMethod), getterMethod.returnType().name().toString(), new GetterExtractor(getterMethod)))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, FORM_PARAM, - (annotationValue, fieldInfo) -> new FormParamItem(annotationValue, + (annotationValue, fieldInfo) -> new FormParamItem(fieldInfo.name(), annotationValue, fieldInfo.type().name().toString(), AsmUtil.getSignature(fieldInfo.type()), fieldInfo.name(), partType(fieldInfo), fileName(fieldInfo), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())), - (annotationValue, getterMethod) -> new FormParamItem(annotationValue, + (annotationValue, getterMethod) -> new FormParamItem(getterMethod.name(), annotationValue, getterMethod.returnType().name().toString(), AsmUtil.getSignature(getterMethod.returnType()), getterMethod.name(), @@ -165,13 +168,13 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde new GetterExtractor(getterMethod)))); resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_FORM_PARAM, - (annotationValue, fieldInfo) -> new FormParamItem( + (annotationValue, fieldInfo) -> new FormParamItem(fieldInfo.name(), annotationValue != null ? annotationValue : fieldInfo.name(), fieldInfo.type().name().toString(), AsmUtil.getSignature(fieldInfo.type()), fieldInfo.name(), partType(fieldInfo), fileName(fieldInfo), new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())), - (annotationValue, getterMethod) -> new FormParamItem( + (annotationValue, getterMethod) -> new FormParamItem(getterMethod.name(), annotationValue != null ? annotationValue : getterName(getterMethod), getterMethod.returnType().name().toString(), AsmUtil.getSignature(getterMethod.returnType()), diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java index c865b7dc475e97..ba068e0ad463f2 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java @@ -4,8 +4,8 @@ public class CookieParamItem extends Item { private final String cookieName; private final String paramType; - public CookieParamItem(String cookieName, ValueExtractor extractor, String paramType) { - super(ItemType.COOKIE, extractor); + public CookieParamItem(String fieldName, String cookieName, ValueExtractor extractor, String paramType) { + super(fieldName, ItemType.COOKIE, extractor); this.cookieName = cookieName; this.paramType = paramType; } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/FormParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/FormParamItem.java index ebf9f08ccad238..60c2fe9d3ccb01 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/FormParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/FormParamItem.java @@ -9,11 +9,11 @@ public class FormParamItem extends Item { private final String fileName; private final String sourceName; - public FormParamItem(String formParamName, String paramType, String paramSignature, + public FormParamItem(String fieldName, String formParamName, String paramType, String paramSignature, String sourceName, String mimeType, String fileName, ValueExtractor valueExtractor) { - super(ItemType.FORM_PARAM, valueExtractor); + super(fieldName, ItemType.FORM_PARAM, valueExtractor); this.formParamName = formParamName; this.paramType = paramType; this.paramSignature = paramSignature; diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java index b47110b960d4b4..61b417b3865ff5 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java @@ -4,8 +4,8 @@ public class HeaderParamItem extends Item { private final String headerName; private final String paramType; - public HeaderParamItem(String headerName, ValueExtractor extractor, String paramType) { - super(ItemType.HEADER_PARAM, extractor); + public HeaderParamItem(String fieldName, String headerName, ValueExtractor extractor, String paramType) { + super(fieldName, ItemType.HEADER_PARAM, extractor); this.headerName = headerName; this.paramType = paramType; } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java index 4bda26033130d3..1ef10511912ad6 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java @@ -5,14 +5,20 @@ public abstract class Item { + private final String fieldName; private final ItemType type; private final ValueExtractor valueExtractor; - public Item(ItemType type, ValueExtractor valueExtractor) { + public Item(String fieldName, ItemType type, ValueExtractor valueExtractor) { + this.fieldName = fieldName; this.type = type; this.valueExtractor = valueExtractor; } + public String fieldName() { + return fieldName; + } + public ItemType type() { return type; } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java index 4db45bd16baafe..474052a4f0a393 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java @@ -5,8 +5,8 @@ public class PathParamItem extends Item { private final String pathParamName; private final String paramType; - public PathParamItem(String pathParamName, String paramType, ValueExtractor valueExtractor) { - super(ItemType.PATH_PARAM, valueExtractor); + public PathParamItem(String fieldName, String pathParamName, String paramType, ValueExtractor valueExtractor) { + super(fieldName, ItemType.PATH_PARAM, valueExtractor); this.pathParamName = pathParamName; this.paramType = paramType; } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/QueryParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/QueryParamItem.java index 8fb3cc58a2e0fc..a3b83a979d817b 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/QueryParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/QueryParamItem.java @@ -7,8 +7,8 @@ public class QueryParamItem extends Item { private final String name; private final Type valueType; - public QueryParamItem(String name, ValueExtractor extractor, Type valueType) { - super(ItemType.QUERY_PARAM, extractor); + public QueryParamItem(String fieldName, String name, ValueExtractor extractor, Type valueType) { + super(fieldName, ItemType.QUERY_PARAM, extractor); this.name = name; this.valueType = valueType; } From 272532591b2bd437ed91dbb8e73d6336d65ea781 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 24 Apr 2023 10:55:35 +0200 Subject: [PATCH 132/333] Fix inconsistent behaviour in REST Client reactive when using contexts Fix https://github.com/quarkusio/quarkus/issues/32839 --- .../error/BlockingExceptionMapperTest.java | 12 ++---- .../handlers/ClientSendRequestHandler.java | 28 +------------ ...ientSwitchToRequestContextRestHandler.java | 42 +++++++++++++++++++ .../reactive/client/impl/HandlerChain.java | 8 +++- 4 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSwitchToRequestContextRestHandler.java diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java index 47b82d59933302..db74c6dae81ca2 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java @@ -22,7 +22,6 @@ import org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -75,7 +74,6 @@ public void setup() { EVENT_LOOP_THREAD_USED_BY_MAPPER.set(false); } - @Disabled("This test randomly fails because https://github.com/quarkusio/quarkus/issues/32839") @Test public void shouldUseEventLoopByDefault() { assertThrows(BlockingNotAllowedException.class, clientUsingNotBlockingExceptionMapper::nonBlocking); @@ -91,9 +89,8 @@ public void shouldUseWorkerThreadIfExceptionMapperIsAnnotatedWithBlocking() { @Test public void shouldUseWorkerThreadOnlyIfExceptionMapperIsAnnotatedWithBlockingIsUsed() { - // To be uncommented after https://github.com/quarkusio/quarkus/issues/32839 is fixed: - // assertThrows(BlockingNotAllowedException.class, clientUsingBothExceptionMappers::nonBlocking); - // assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); + assertThrows(BlockingNotAllowedException.class, clientUsingBothExceptionMappers::nonBlocking); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); RuntimeException exception = assertThrows(RuntimeException.class, clientUsingBothExceptionMappers::blocking); assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); @@ -102,9 +99,8 @@ public void shouldUseWorkerThreadOnlyIfExceptionMapperIsAnnotatedWithBlockingIsU @Test public void shouldUseWorkerThreadWhenClientIsInjected() { - // To be uncommented after https://github.com/quarkusio/quarkus/issues/32839 is fixed: - // given().get("/client/non-blocking").then().statusCode(500); - // assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); + given().get("/client/non-blocking").then().statusCode(500); + assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue(); given().get("/client/blocking").then().statusCode(500); assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse(); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java index 2e534c08d8cd05..21d2bb60df41bc 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Function; @@ -28,7 +27,6 @@ import org.jboss.resteasy.reactive.client.api.LoggingScope; import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties; import org.jboss.resteasy.reactive.client.impl.AsyncInvokerImpl; -import org.jboss.resteasy.reactive.client.impl.ClientRequestContextImpl; import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; import org.jboss.resteasy.reactive.client.impl.multipart.PausableHttpPostRequestEncoder; import org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartForm; @@ -46,7 +44,6 @@ import io.smallrye.mutiny.Uni; import io.smallrye.stork.api.ServiceInstance; import io.vertx.core.AsyncResult; -import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; @@ -88,30 +85,7 @@ public void handle(RestClientRequestContext requestContext) { return; } requestContext.suspend(); - Uni future = createRequest(requestContext) - .runSubscriptionOn(new Executor() { - @Override - public void execute(Runnable command) { - Context current = Vertx.currentContext(); - ClientRequestContextImpl clientRequestContext = requestContext.getClientRequestContext(); - Context captured = null; - if (clientRequestContext != null) { - captured = clientRequestContext.getContext(); - } - if (current == captured || captured == null) { - // No need to switch to another context. - command.run(); - } else { - // Switch back to the captured context - captured.runOnContext(new Handler() { - @Override - public void handle(Void ignored) { - command.run(); - } - }); - } - } - }); + Uni future = createRequest(requestContext); // DNS failures happen before we send the request future.subscribe().with(new Consumer<>() { diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSwitchToRequestContextRestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSwitchToRequestContextRestHandler.java new file mode 100644 index 00000000000000..98f24545466594 --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSwitchToRequestContextRestHandler.java @@ -0,0 +1,42 @@ +package org.jboss.resteasy.reactive.client.handlers; + +import java.util.concurrent.Executor; + +import org.jboss.resteasy.reactive.client.impl.ClientRequestContextImpl; +import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; + +import io.vertx.core.Context; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; + +/** + * This handler ensures that the context to use is the same as the client request context, which is important to keep the + * request context in sync when updating the response. + */ +public class ClientSwitchToRequestContextRestHandler implements ClientRestHandler { + @Override + public void handle(RestClientRequestContext requestContext) throws Exception { + Context current = Vertx.currentContext(); + ClientRequestContextImpl clientRequestContext = requestContext.getClientRequestContext(); + if (clientRequestContext == null) { + return; + } + + Context captured = clientRequestContext.getContext(); + if (captured != null && current != captured) { + requestContext.suspend(); + requestContext.resume(new Executor() { + @Override + public void execute(Runnable command) { + captured.runOnContext(new Handler() { + @Override + public void handle(Void unused) { + command.run(); + } + }); + } + }); + } + } +} diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java index af7e1f76ae8819..d2247999624440 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/HandlerChain.java @@ -15,6 +15,7 @@ import org.jboss.resteasy.reactive.client.handlers.ClientResponseFilterRestHandler; import org.jboss.resteasy.reactive.client.handlers.ClientSendRequestHandler; import org.jboss.resteasy.reactive.client.handlers.ClientSetResponseEntityRestHandler; +import org.jboss.resteasy.reactive.client.handlers.ClientSwitchToRequestContextRestHandler; import org.jboss.resteasy.reactive.client.handlers.PreResponseFilterHandler; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.client.spi.MultipartResponseData; @@ -25,6 +26,7 @@ class HandlerChain { private static final ClientRestHandler[] EMPTY_REST_HANDLERS = new ClientRestHandler[0]; + private final ClientRestHandler clientSwitchToRequestContextRestHandler; private final ClientRestHandler clientSendHandler; private final ClientRestHandler clientSetResponseEntityRestHandler; private final ClientRestHandler clientResponseCompleteRestHandler; @@ -34,6 +36,7 @@ class HandlerChain { public HandlerChain(boolean followRedirects, LoggingScope loggingScope, Map, MultipartResponseData> multipartData, ClientLogger clientLogger) { + this.clientSwitchToRequestContextRestHandler = new ClientSwitchToRequestContextRestHandler(); this.clientSendHandler = new ClientSendRequestHandler(followRedirects, loggingScope, clientLogger, multipartData); this.clientSetResponseEntityRestHandler = new ClientSetResponseEntityRestHandler(); this.clientResponseCompleteRestHandler = new ClientResponseCompleteRestHandler(); @@ -49,7 +52,9 @@ ClientRestHandler[] createHandlerChain(ConfigurationImpl configuration) { List requestFilters = configuration.getRequestFilters(); List responseFilters = configuration.getResponseFilters(); if (requestFilters.isEmpty() && responseFilters.isEmpty()) { - return new ClientRestHandler[] { clientSendHandler, clientSetResponseEntityRestHandler, + return new ClientRestHandler[] { clientSwitchToRequestContextRestHandler, + clientSendHandler, + clientSetResponseEntityRestHandler, clientResponseCompleteRestHandler }; } List result = new ArrayList<>( @@ -60,6 +65,7 @@ ClientRestHandler[] createHandlerChain(ConfigurationImpl configuration) { for (int i = 0; i < requestFilters.size(); i++) { result.add(new ClientRequestFilterRestHandler(requestFilters.get(i))); } + result.add(clientSwitchToRequestContextRestHandler); result.add(clientSendHandler); result.add(clientSetResponseEntityRestHandler); result.add(new PreResponseFilterHandler()); From bb93e90665f3f68348df095b217b005aa52f01ef Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 25 Apr 2023 00:17:06 +1000 Subject: [PATCH 133/333] Make dev-services hot reload Signed-off-by: Phillip Kruger --- .../main/resources/dev-ui/qwc/qwc-dev-services.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js index 16113ef4d330e2..b5475445e49312 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-dev-services.js @@ -1,5 +1,5 @@ -import {LitElement, html, css} from 'lit'; -import {devServices} from 'devui-data'; +import { QwcHotReloadElement, html, css } from 'qwc-hot-reload-element'; +import { devServices } from 'devui-data'; import '@vaadin/icon'; import 'qui-code-block'; import 'qui-card'; @@ -7,11 +7,14 @@ import 'qui-card'; /** * This component shows the Dev Services Page */ -export class QwcDevServices extends LitElement { +export class QwcDevServices extends QwcHotReloadElement { static styles = css` .cards { height: 100%; padding-right: 10px; + display: flex; + flex-direction: column; + gap: 10px; } .configHeader { @@ -58,6 +61,12 @@ export class QwcDevServices extends LitElement { this._services = devServices; } + hotReload(){ + import(`devui/devui-data.js?${Date.now()}`).then(newDevUIData => { + this._services = newDevUIData.devServices; + }); + } + render() { if (this._services && this._services.length>0) { return html`

    From 737925a31fcafe152c6160c2f921543e418cc23a Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 23 Apr 2023 22:13:01 +0100 Subject: [PATCH 134/333] Upgrade to Hibernate Reactive 2.0.0.CR1 --- bom/application/pom.xml | 2 +- .../SettingsSpyingIdentifierGenerator.java | 1 - ...QuarkusReactiveConnectionPoolInitiator.java | 10 ++-------- .../common/runtime/AbstractJpaOperations.java | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index a8a60570e7f4ef..5e7201718a1f7b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -100,7 +100,7 @@ 6.2.1.Final 1.12.18 6.0.6.Final - 2.0.0.Beta2 + 2.0.0.CR1 8.0.0.Final 6.1.7.Final 6.0.0.Final diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java index f1d1f4fe70293a..920269e71b9f58 100644 --- a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java @@ -23,7 +23,6 @@ public class SettingsSpyingIdentifierGenerator implements IdentifierGenerator { public static final List> collectedSettings = new ArrayList<>(); @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { collectedSettings.add(new HashMap<>(serviceRegistry.getService(ConfigurationService.class).getSettings())); } diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java index 0aa11409ddabb6..e1611e2bcbeb6c 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java @@ -3,13 +3,9 @@ import java.util.Map; import org.hibernate.boot.registry.StandardServiceInitiator; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.reactive.pool.ReactiveConnectionPool; import org.hibernate.reactive.pool.impl.ExternalSqlClientPool; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.service.spi.ServiceRegistryImplementor; import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; @@ -37,10 +33,8 @@ public ReactiveConnectionPool initiateService(Map configurationValues, ServiceRe // nothing to do, but given the separate hierarchies have to handle this here. return null; } - SqlStatementLogger sqlStatementLogger = registry.getService(JdbcServices.class).getSqlStatementLogger(); - final Dialect dialect = registry.getService(JdbcEnvironment.class).getDialect(); - Parameters parameters = Parameters.instance(dialect); - return new ExternalSqlClientPool(pool, sqlStatementLogger, parameters); + final JdbcServices jdbcService = registry.getService(JdbcServices.class); + return new ExternalSqlClientPool(pool, jdbcService.getSqlStatementLogger(), jdbcService.getSqlExceptionHelper()); } } diff --git a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java index 8cc1c504042829..e3f5b77ffcf00b 100644 --- a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java +++ b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java @@ -360,6 +360,15 @@ public static Mutiny.Query bindParameters(Mutiny.Query query, Object[] par return query; } + public static Mutiny.SelectionQuery bindParameters(Mutiny.SelectionQuery query, Object[] params) { + if (params == null || params.length == 0) + return query; + for (int i = 0; i < params.length; i++) { + query.setParameter(i + 1, params[i]); + } + return query; + } + public static Mutiny.Query bindParameters(Mutiny.Query query, Map params) { if (params == null || params.size() == 0) return query; @@ -369,6 +378,15 @@ public static Mutiny.Query bindParameters(Mutiny.Query query, Map bindParameters(Mutiny.SelectionQuery query, Map params) { + if (params == null || params.size() == 0) + return query; + for (Entry entry : params.entrySet()) { + query.setParameter(entry.getKey(), entry.getValue()); + } + return query; + } + public static Uni executeUpdate(String query, Object... params) { return getSession().chain(session -> { Mutiny.Query jpaQuery = session.createQuery(query); From 91bba01df6bfaeb805e6cbe18b3e992aed321d40 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 24 Apr 2023 15:51:04 +0100 Subject: [PATCH 135/333] Adapt security-jpa-reactive for Hibernate Reactive 2.0.0.CR1's API changes --- .../deployment/QuarkusSecurityJpaReactiveProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/security-jpa-reactive/deployment/src/main/java/io/quarkus/security/jpa/reactive/deployment/QuarkusSecurityJpaReactiveProcessor.java b/extensions/security-jpa-reactive/deployment/src/main/java/io/quarkus/security/jpa/reactive/deployment/QuarkusSecurityJpaReactiveProcessor.java index 9bd10abe9df6d4..0ac604a2e5d275 100644 --- a/extensions/security-jpa-reactive/deployment/src/main/java/io/quarkus/security/jpa/reactive/deployment/QuarkusSecurityJpaReactiveProcessor.java +++ b/extensions/security-jpa-reactive/deployment/src/main/java/io/quarkus/security/jpa/reactive/deployment/QuarkusSecurityJpaReactiveProcessor.java @@ -210,12 +210,13 @@ private static ResultHandle lookupUserById(JpaSecurityDefinition jpaSecurityDefi // session.createQuery("<>", UserEntity.class) ResultHandle query1 = methodCreator.invokeInterfaceMethod( - ofMethod(Mutiny.Session.class, "createQuery", Mutiny.Query.class, String.class, Class.class), + ofMethod(Mutiny.Session.class, "createQuery", Mutiny.SelectionQuery.class, String.class, Class.class), session, methodCreator.load(hql), userEntityClass); // .setParameter("name", username) ResultHandle query2 = methodCreator.invokeInterfaceMethod( - ofMethod(Mutiny.Query.class, "setParameter", Mutiny.Query.class, String.class, Object.class), + ofMethod(Mutiny.SelectionQuery.class, "setParameter", Mutiny.SelectionQuery.class, String.class, + Object.class), query1, methodCreator.load("name"), username); // .getSingleResultOrNull() From a9d1e88aa6e30b7a23c9b473733b2a7859543a8b Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 25 Apr 2023 02:57:35 +1000 Subject: [PATCH 136/333] implement the config filter by extension Signed-off-by: Phillip Kruger --- .../devui/deployment/DevUIProcessor.java | 2 +- .../devui/deployment/extension/Extension.java | 7 ----- .../dev-ui/controller/router-controller.js | 9 ++++--- .../resources/dev-ui/qwc/qwc-configuration.js | 27 ++++++++++++++++--- .../resources/dev-ui/qwc/qwc-extension.js | 8 +++++- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java index 4c2e6707cb3d3f..9dbaa2463aeb38 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/DevUIProcessor.java @@ -421,7 +421,7 @@ void getAllExtensions(List cardPageBuildItems, if (metaData.containsKey(CAPABILITIES)) { Map capabilities = (Map) metaData.get(CAPABILITIES); - extension.setConfigFilter((List) capabilities.getOrDefault(PROVIDES, null)); + extension.setProvidesCapabilities((List) capabilities.getOrDefault(PROVIDES, null)); } if (metaData.containsKey(CODESTART)) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/extension/Extension.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/extension/Extension.java index af1056cd1896b1..a51a528f569c85 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/extension/Extension.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/extension/Extension.java @@ -8,9 +8,6 @@ import io.quarkus.devui.spi.page.Page; public class Extension { - private static final String SPACE = " "; - private static final String DASH = "-"; - private String namespace; private String artifact; private String name; @@ -59,10 +56,6 @@ public void setName(String name) { this.name = name; } - // public String getPathName() { - // return name.toLowerCase().replaceAll(SPACE, DASH); - // } - public String getShortName() { return shortName; } diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js index a0b6070bce0c9e..0b17656c7e4fa0 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js @@ -154,10 +154,11 @@ export class RouterController { RouterController.router.addRoutes(routes); } - // TODO: Pass the other parameters along ? + var currentSelection = window.location.pathname; + var currentQueryString = window.location.search; - var relocationRequest = this.getFrom(); + var relocationRequest = this.getQueryParameter("from"); if (relocationRequest) { // We know and already loaded the requested location if (relocationRequest === path) { @@ -188,10 +189,10 @@ export class RouterController { return params; } - getFrom(){ + getQueryParameter(param){ var params = this.getQueryParameters(); if(params){ - return params.from; + return params[param] || null; }else { return null; } diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js index 1e41312e103393..114c84c75e9247 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js @@ -1,5 +1,6 @@ import { LitElement, html, css } from 'lit'; import { JsonRpc } from 'jsonrpc'; +import { RouterController } from 'router-controller'; import { until } from 'lit/directives/until.js'; import '@vaadin/grid'; import 'qui/qui-alert.js'; @@ -18,6 +19,7 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { gridRowDetailsRenderer } from '@vaadin/grid/lit.js'; import { observeState } from 'lit-element-state'; import { devuiState } from 'devui-state'; +import 'qui-badge'; /** * This component allows users to change the configuration @@ -25,6 +27,7 @@ import { devuiState } from 'devui-state'; export class QwcConfiguration extends observeState(LitElement) { jsonRpc = new JsonRpc(this); + routerController = new RouterController(this); static styles = css` .conf { @@ -87,6 +90,13 @@ export class QwcConfiguration extends observeState(LitElement) { constructor() { super(); + + this._filteredValue = this.routerController.getQueryParameter("filter"); + + if(this._filteredValue){ + this._filteredValue = this._filteredValue.replaceAll(",", " OR "); + } + this._filtered = devuiState.allConfiguration; this.jsonRpc.getAllValues().then(e => { this._values = e.result; @@ -103,10 +113,19 @@ export class QwcConfiguration extends observeState(LitElement) { if (! value) { return false; } + if(term.includes(" OR ")){ + let terms = term.split(" OR "); + for (let t of terms) { + if(value.toLowerCase().includes(t.toLowerCase())){ + return true; + } + } + return false; + } return value.toLowerCase().includes(term.toLowerCase()); } - _filter(e) { + _filterTextChanged(e) { const searchTerm = (e.detail.value || '').trim(); if (searchTerm === '') { this._filtered = devuiState.allConfiguration @@ -119,13 +138,15 @@ export class QwcConfiguration extends observeState(LitElement) { } _render() { - if (this._filtered && this._values) { + if (this._filtered && this._values) { return html`
    + @value-changed="${(e) => this._filterTextChanged(e)}"> + ${this._filtered.length} ${this._renderGrid()}
    `; diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js index 0da0c18e84e41c..931ad190ec6be8 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js @@ -44,6 +44,10 @@ export class QwcExtension extends LitElement { visibility:hidden; } + .card-footer a { + color: var(--lumo-contrast-50pct); + } + .active:hover { box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); } @@ -137,7 +141,9 @@ export class QwcExtension extends LitElement { _footerTemplate() { return html` `; From e21aa727867afed63e948c2eea57dcea6db8bb54 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Sun, 23 Apr 2023 21:37:15 +0100 Subject: [PATCH 137/333] Allow access token verification for Google,Github --- .../main/java/io/quarkus/oidc/OidcTenantConfig.java | 12 ++++++++++-- .../quarkus/oidc/runtime/OidcIdentityProvider.java | 6 +++--- .../java/io/quarkus/oidc/runtime/OidcRecorder.java | 9 +++++++-- .../main/java/io/quarkus/oidc/runtime/OidcUtils.java | 3 +++ .../oidc/runtime/providers/KnownOidcProviders.java | 2 ++ .../java/io/quarkus/oidc/runtime/OidcUtilsTest.java | 6 ++++++ .../src/main/resources/application.properties | 1 + 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index 63cac12d78d680..dffe360fb5e0a3 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -1256,8 +1256,16 @@ public static Token fromAudience(String... audience) { * provider does not have a token introspection endpoint. * This property will have no effect when JWT tokens have to be verified. */ - @ConfigItem(defaultValue = "false") - public boolean verifyAccessTokenWithUserInfo; + @ConfigItem(defaultValueDocumentation = "false") + public Optional verifyAccessTokenWithUserInfo = Optional.empty(); + + public Optional isVerifyAccessTokenWithUserInfo() { + return verifyAccessTokenWithUserInfo; + } + + public void setVerifyAccessTokenWithUserInfo(boolean verify) { + this.verifyAccessTokenWithUserInfo = Optional.of(verify); + } public Optional getIssuer() { return issuer; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 28764fc7ad49c0..432dff425676db 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -100,7 +100,7 @@ private Uni validateAllTokensWithOidcServer(RoutingContext ver TokenAuthenticationRequest request, TenantConfigContext resolvedContext) { - if (resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo + if (resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false) && isOpaqueAccessToken(vertxContext, request, resolvedContext)) { // UserInfo has to be acquired first as a precondition for verifying opaque access tokens. // Typically it will be done for bearer access tokens therefore even if the access token has expired @@ -269,7 +269,7 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { final String userName; if (result.introspectionResult == null) { if (resolvedContext.oidcConfig.token.allowOpaqueTokenIntrospection && - resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo) { + resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false)) { userName = ""; } else { // we don't expect this to ever happen @@ -386,7 +386,7 @@ private Uni verifyTokenUni(TenantConfigContext resolved throw new AuthenticationFailedException(); } // verify opaque access token with UserInfo if enabled and introspection URI is absent - if (resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo + if (resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false) && resolvedContext.provider.getMetadata().getIntrospectionUri() == null) { if (userInfo == null) { return Uni.createFrom().failure( diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index f5fd7d69857104..2b23b7609c4b92 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -176,7 +176,8 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf throw new ConfigurationException( "UserInfo is not required but UserInfo is expected to be the source of authorization roles"); } - if (oidcConfig.token.verifyAccessTokenWithUserInfo && !enableUserInfo(oidcConfig)) { + if (oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false) && !isWebApp(oidcConfig) + && !enableUserInfo(oidcConfig)) { throw new ConfigurationException( "UserInfo is not required but 'verifyAccessTokenWithUserInfo' is enabled"); } @@ -238,7 +239,7 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf } } - if (oidcConfig.token.verifyAccessTokenWithUserInfo) { + if (oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false)) { if (!oidcConfig.isDiscoveryEnabled().orElse(true)) { if (oidcConfig.userInfoPath.isEmpty()) { throw new ConfigurationException( @@ -443,6 +444,10 @@ private static boolean isServiceApp(OidcTenantConfig oidcConfig) { return ApplicationType.SERVICE.equals(oidcConfig.applicationType.orElse(ApplicationType.SERVICE)); } + private static boolean isWebApp(OidcTenantConfig oidcConfig) { + return ApplicationType.WEB_APP.equals(oidcConfig.applicationType.orElse(ApplicationType.SERVICE)); + } + private static void verifyAuthServerUrl(OidcCommonConfig oidcConfig) { if (!oidcConfig.getAuthServerUrl().isPresent()) { throw new ConfigurationException("'quarkus.oidc.auth-server-url' property must be configured"); 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 bf5e70200cf6a6..8aa82db7da36bd 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 @@ -432,6 +432,9 @@ static OidcTenantConfig mergeTenantConfig(OidcTenantConfig tenant, OidcTenantCon if (tenant.token.principalClaim.isEmpty()) { tenant.token.principalClaim = provider.token.principalClaim; } + if (tenant.token.verifyAccessTokenWithUserInfo.isEmpty()) { + tenant.token.verifyAccessTokenWithUserInfo = provider.token.verifyAccessTokenWithUserInfo; + } return tenant; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java index 38909dfce88412..e3e84dc1720410 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java @@ -39,6 +39,7 @@ private static OidcTenantConfig github() { ret.getAuthentication().setScopes(List.of("user:email")); ret.getAuthentication().setUserInfoRequired(true); ret.getAuthentication().setIdTokenRequired(false); + ret.getToken().setVerifyAccessTokenWithUserInfo(true); return ret; } @@ -64,6 +65,7 @@ private static OidcTenantConfig google() { ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); ret.getAuthentication().setScopes(List.of("openid", "email", "profile")); ret.getToken().setPrincipalClaim("name"); + ret.getToken().setVerifyAccessTokenWithUserInfo(true); return ret; } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java index e82bea9dd13688..8b6ab511cc7337 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java @@ -49,6 +49,7 @@ public void testAcceptGitHubProperties() throws Exception { assertFalse(config.authentication.idTokenRequired.get()); assertTrue(config.authentication.userInfoRequired.get()); + assertTrue(config.token.verifyAccessTokenWithUserInfo.get()); assertEquals(List.of("user:email"), config.authentication.scopes.get()); } @@ -66,6 +67,7 @@ public void testOverrideGitHubProperties() throws Exception { tenant.authentication.setIdTokenRequired(true); tenant.authentication.setUserInfoRequired(false); + tenant.token.setVerifyAccessTokenWithUserInfo(false); tenant.authentication.setScopes(List.of("write")); OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.GITHUB)); @@ -80,6 +82,7 @@ public void testOverrideGitHubProperties() throws Exception { assertTrue(config.authentication.idTokenRequired.get()); assertFalse(config.authentication.userInfoRequired.get()); + assertFalse(config.token.verifyAccessTokenWithUserInfo.get()); assertEquals(List.of("write"), config.authentication.scopes.get()); } @@ -197,6 +200,7 @@ public void testAcceptGoogleProperties() throws Exception { assertEquals("https://accounts.google.com", config.getAuthServerUrl().get()); assertEquals("name", config.getToken().getPrincipalClaim().get()); assertEquals(List.of("openid", "email", "profile"), config.authentication.scopes.get()); + assertTrue(config.token.verifyAccessTokenWithUserInfo.get()); } @Test @@ -208,6 +212,7 @@ public void testOverrideGoogleProperties() throws Exception { tenant.setAuthServerUrl("http://localhost/wiremock"); tenant.authentication.setScopes(List.of("write")); tenant.token.setPrincipalClaim("firstname"); + tenant.token.setVerifyAccessTokenWithUserInfo(false); OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.GOOGLE)); @@ -216,6 +221,7 @@ public void testOverrideGoogleProperties() throws Exception { assertEquals("http://localhost/wiremock", config.getAuthServerUrl().get()); assertEquals("firstname", config.getToken().getPrincipalClaim().get()); assertEquals(List.of("write"), config.authentication.scopes.get()); + assertFalse(config.token.verifyAccessTokenWithUserInfo.get()); } @Test diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index e90a280bead002..c7871fe4c7d1c9 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -97,6 +97,7 @@ quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.credentials.secret=AyM quarkus.oidc.code-flow-token-introspection.provider=github +quarkus.oidc.code-flow-token-introspection.token.verify-access-token-with-user-info=false quarkus.oidc.code-flow-token-introspection.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.code-flow-token-introspection.authorization-path=/ quarkus.oidc.code-flow-token-introspection.user-info-path=protocol/openid-connect/userinfo From 4cd3d4242dc233cd5629bf1a2b109f8315f5c992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crocquesel?= <88554524+scrocquesel@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:03:06 +0200 Subject: [PATCH 138/333] Fix generateReflectConfig step --- .../deployment/steps/NativeImageReflectConfigStep.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java index 69476a4af5fb28..cd04106f20330f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java @@ -70,13 +70,13 @@ void generateReflectConfig(BuildProducer reflectConf json.put("name", entry.getKey()); ReflectionInfo info = entry.getValue(); + JsonArrayBuilder methodsArray = Json.array(); if (info.typeReachable != null) { json.put("condition", Json.object().put("typeReachable", info.typeReachable)); } if (info.constructors) { json.put("allDeclaredConstructors", true); } else if (!info.ctorSet.isEmpty()) { - JsonArrayBuilder methodsArray = Json.array(); for (ReflectiveMethodBuildItem ctor : info.ctorSet) { JsonObjectBuilder methodObject = Json.object(); methodObject.put("name", ctor.getName()); @@ -87,12 +87,10 @@ void generateReflectConfig(BuildProducer reflectConf methodObject.put("parameterTypes", paramsArray); methodsArray.add(methodObject); } - json.put("methods", methodsArray); } if (info.methods) { json.put("allDeclaredMethods", true); } else if (!info.methodSet.isEmpty()) { - JsonArrayBuilder methodsArray = Json.array(); for (ReflectiveMethodBuildItem method : info.methodSet) { JsonObjectBuilder methodObject = Json.object(); methodObject.put("name", method.getName()); @@ -103,8 +101,11 @@ void generateReflectConfig(BuildProducer reflectConf methodObject.put("parameterTypes", paramsArray); methodsArray.add(methodObject); } + } + if (!methodsArray.isEmpty()) { json.put("methods", methodsArray); } + if (info.fields) { json.put("allDeclaredFields", true); } else if (!info.fieldSet.isEmpty()) { From e3e44887e34db643d43a6178dd6dff4abc28d38d Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 25 Apr 2023 07:41:51 +0200 Subject: [PATCH 139/333] Provide new API to programmatically create REST Client reactive The new API includes these additional options: - verifyHost - trustStore by truststore and password (it was requested in #31891) - proxyUser - proxyPassword - nonProxyHosts Fix https://github.com/quarkusio/quarkus/issues/32856 --- .../main/asciidoc/rest-client-reactive.adoc | 46 +++- ...entObjectMapperForClientAndServerTest.java | 6 +- .../reactive/jackson/test/MultiSseTest.java | 8 +- .../reactive/jaxb/test/SimpleJaxbTest.java | 6 +- .../reactive/ArrayPairsQueryParamTest.java | 3 +- .../ClientAndServerSharingResponseTest.java | 4 +- .../reactive/ConnectionPoolSizeTest.java | 9 +- ...sViaProgrammaticallyClientCreatedTest.java | 5 +- .../rest/client/reactive/EmptyPostTest.java | 3 +- .../rest/client/reactive/FormTest.java | 3 +- .../rest/client/reactive/InvalidHostTest.java | 3 +- .../rest/client/reactive/InvalidURITest.java | 3 +- .../rest/client/reactive/MapParamsTest.java | 5 +- .../rest/client/reactive/MultiNdjsonTest.java | 3 +- .../client/reactive/NoPathInTheAppTest.java | 3 +- .../timeout/BuilderReadTimeoutTest.java | 8 +- .../reactive/QuarkusRestClientBuilder.java | 232 ++++++++++++++++++ .../runtime/QuarkusRestClientBuilderImpl.java | 186 ++++++++++++++ .../ReactiveRestClientBuilderFactory.java | 3 +- .../runtime/RestClientCDIDelegateBuilder.java | 38 ++- .../RestClientCDIDelegateBuilderTest.java | 4 +- .../client/main/ClientCallingResource.java | 18 +- .../ClientWithCustomObjectMapperTest.java | 6 +- 23 files changed, 521 insertions(+), 84 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index f4ae3aef37ec6a..fc5d15b90b510c 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -351,11 +351,11 @@ There are two interesting parts in this listing: <1> the client stub is injected with the `@RestClient` annotation instead of the usual CDI `@Inject` -== Programmatic client creation with RestClientBuilder +== Programmatic client creation with QuarkusRestClientBuilder Instead of annotating the client with `@RegisterRestClient`, and injecting a client with `@RestClient`, you can also create REST Client programmatically. -You do that with `RestClientBuilder`. +You do that with the `QuarkusRestClientBuilder`. With this approach the client interface could look as follows: @@ -381,7 +381,7 @@ And the service as follows: ---- package org.acme.rest.client; -import org.eclipse.microprofile.rest.client.RestClientBuilder; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -394,7 +394,7 @@ public class ExtensionsResource { private final ExtensionsService extensionsService; public ExtensionsResource() { - extensionsService = RestClientBuilder.newBuilder() + extensionsService = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create("https://stage.code.quarkus.io/api")) .build(ExtensionsService.class); } @@ -407,6 +407,38 @@ public class ExtensionsResource { } ---- +[TIP] +==== +The `QuarkusRestClientBuilder` interface is a Quarkus-specific API to programmatically create clients with additional configuration options. Otherwise, you can also use the `RestClientBuilder` interface from the Microprofile API: + +[source,java] +---- +package org.acme.rest.client; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import java.net.URI; +import java.util.Set; + +@Path("/extension") +public class ExtensionsResource { + + private final ExtensionsService extensionsService; + + public ExtensionsResource() { + extensionsService = RestClientBuilder.newBuilder() + .baseUri(URI.create("https://stage.code.quarkus.io/api")) + .build(ExtensionsService.class); + } + + // ... +} +---- + +==== + == Use Custom HTTP Options The REST Client Reactive internally uses https://vertx.io/docs/apidocs/io/vertx/core/http/HttpClient.html[the Vert.x HTTP Client] to make the network connections. The REST Client Reactive extensions allows configuring some settings via properties, for example: @@ -450,7 +482,7 @@ Another approach is to provide the custom HTTP Client options when creating the ---- package org.acme.rest.client; -import org.eclipse.microprofile.rest.client.RestClientBuilder; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -465,7 +497,7 @@ public class ExtensionsResource { private final ExtensionsService extensionsService; public ExtensionsResource() { - extensionsService = RestClientBuilder.newBuilder() + extensionsService = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create("https://stage.code.quarkus.io/api")) .register(CustomHttpClientOptions.class) <1> .build(ExtensionsService.class); @@ -562,7 +594,7 @@ public class ExtensionsResource { private final ExtensionsService extensionsService; public ExtensionsResource() { - extensionsService = RestClientBuilder.newBuilder() + extensionsService = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create("https://stage.code.quarkus.io/api")) .register(AlwaysRedirectHandler.class) <1> .build(ExtensionsService.class); diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java index fb887c900e5991..8652317dc36059 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/DifferentObjectMapperForClientAndServerTest.java @@ -19,7 +19,6 @@ import jakarta.ws.rs.ext.ContextResolver; import org.apache.http.HttpStatus; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +29,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import io.quarkus.jackson.ObjectMapperCustomizer; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; @@ -45,10 +45,10 @@ public class DifferentObjectMapperForClientAndServerTest { @BeforeEach public void setup() { - clientUnwrappingRootElement = RestClientBuilder.newBuilder().baseUri(uri) + clientUnwrappingRootElement = QuarkusRestClientBuilder.newBuilder().baseUri(uri) .build(MyClientUnwrappingRootElement.class); - clientNotUnwrappingRootElement = RestClientBuilder.newBuilder().baseUri(uri) + clientNotUnwrappingRootElement = QuarkusRestClientBuilder.newBuilder().baseUri(uri) .build(MyClientNotUnwrappingRootElement.class); } diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java index df9f9b662d31bc..aa715e04fb9482 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java @@ -15,7 +15,6 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.jboss.resteasy.reactive.RestStreamElementType; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; @@ -24,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; import io.smallrye.mutiny.Multi; @@ -101,7 +101,7 @@ void shouldSendPayloadAndConsumeAsParametrizedType() { @Test void shouldRestStreamElementTypeOverwriteProducesAtClassLevel() { var resultList = new CopyOnWriteArrayList<>(); - RestClientBuilder.newBuilder().baseUri(uri) + QuarkusRestClientBuilder.newBuilder().baseUri(uri) .build(SeeWithRestStreamElementTypeClient.class) .getJson() .subscribe() @@ -113,7 +113,9 @@ void shouldRestStreamElementTypeOverwriteProducesAtClassLevel() { } private SseClient createClient() { - return RestClientBuilder.newBuilder().baseUri(uri).register(new JacksonBasicMessageBodyReader(new ObjectMapper())) + return QuarkusRestClientBuilder.newBuilder() + .baseUri(uri) + .register(new JacksonBasicMessageBodyReader(new ObjectMapper())) .build(SseClient.class); } diff --git a/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java b/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java index 601bbb85580832..4efea63bf3fe6d 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java @@ -11,10 +11,10 @@ import jakarta.ws.rs.core.MediaType; import jakarta.xml.bind.annotation.XmlRootElement; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; @@ -28,14 +28,14 @@ public class SimpleJaxbTest { @Test void shouldConsumeXMLEntity() { - var dto = RestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class) + var dto = QuarkusRestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class) .dto(); assertThat(dto).isEqualTo(new Dto("foo", "bar")); } @Test void shouldConsumePlainXMLEntity() { - var dto = RestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class) + var dto = QuarkusRestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class) .plain(); assertThat(dto).isEqualTo(new Dto("foo", "bar")); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java index 7771e5c0a39320..e1d2d3fdc3f649 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java @@ -16,7 +16,6 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -87,7 +86,7 @@ private void setupServer() throws InterruptedException, ExecutionException { } private Client createClient() { - return RestClientBuilder.newBuilder() + return QuarkusRestClientBuilder.newBuilder() .queryParamStyle(QueryParamStyle.ARRAY_PAIRS) .baseUri(URI.create("http://localhost:8082")) .build(Client.class); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClientAndServerSharingResponseTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClientAndServerSharingResponseTest.java index c154d7e6e2dd72..a250d8c370d25a 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClientAndServerSharingResponseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClientAndServerSharingResponseTest.java @@ -19,7 +19,6 @@ import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -65,11 +64,10 @@ public Endpoint(ObjectMapper mapper, @ConfigProperty(name = "quarkus.http.test-port", defaultValue = "8081") Integer testPort) throws MalformedURLException { this.mapper = mapper; - this.headersService = RestClientBuilder.newBuilder() + this.headersService = QuarkusRestClientBuilder.newBuilder() .baseUrl(new URL(String.format("http://localhost:%d", testPort))) .readTimeout(1, TimeUnit.SECONDS) .build(HeadersService.class); - ; } @POST diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConnectionPoolSizeTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConnectionPoolSizeTest.java index d2042f8782ada5..2a1c23e0659893 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConnectionPoolSizeTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConnectionPoolSizeTest.java @@ -12,7 +12,6 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -37,7 +36,7 @@ public class ConnectionPoolSizeTest { @Test void shouldPerform20CallsWithoutQueuing() throws InterruptedException { - Client client = RestClientBuilder.newBuilder().baseUri(uri) + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(uri) .build(Client.class); CountDownLatch latch = executeCalls(client, 20); @@ -50,7 +49,7 @@ void shouldPerform20CallsWithoutQueuing() throws InterruptedException { @Test @Timeout(5) void shouldPerform21CallsWithQueuing() throws InterruptedException { - Client client = RestClientBuilder.newBuilder().baseUri(uri) + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(uri) .build(Client.class); long start = System.currentTimeMillis(); @@ -63,7 +62,7 @@ void shouldPerform21CallsWithQueuing() throws InterruptedException { @Test @Timeout(5) void shouldPerform5CallsWithoutQueueingOnQueue6() throws InterruptedException { - Client client = RestClientBuilder.newBuilder().baseUri(uri) + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(uri) .property(QuarkusRestClientProperties.CONNECTION_POOL_SIZE, 6) .build(Client.class); @@ -77,7 +76,7 @@ void shouldPerform5CallsWithoutQueueingOnQueue6() throws InterruptedException { @Test @Timeout(5) void shouldPerform5CallsWithQueueingOnQueue4() throws InterruptedException { - Client client = RestClientBuilder.newBuilder().baseUri(uri) + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(uri) .property(QuarkusRestClientProperties.CONNECTION_POOL_SIZE, 4) .build(Client.class); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java index e95793ef8915e1..f5d36c89e09faa 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java @@ -10,7 +10,6 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.ext.ContextResolver; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.resteasy.reactive.RestResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -33,12 +32,12 @@ public class CustomHttpOptionsViaProgrammaticallyClientCreatedTest { @Test void shouldUseCustomHttpOptions() { // First verify the standard configuration - assertThat(RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class).get()) + assertThat(QuarkusRestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class).get()) .isEqualTo(EXPECTED_VALUE); // Now, it should fail if we use a custom http client options with a very limited max header size: - Client client = RestClientBuilder.newBuilder().baseUri(baseUri) + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(baseUri) .register(CustomHttpClientOptionsWithLimit.class) .build(Client.class); assertThatThrownBy(() -> client.get()).hasMessageContaining("HTTP header is larger than 1 bytes."); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/EmptyPostTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/EmptyPostTest.java index 72d3b069f470c1..197602e8ec99d8 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/EmptyPostTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/EmptyPostTest.java @@ -9,7 +9,6 @@ import jakarta.ws.rs.Path; import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -29,7 +28,7 @@ void shouldWork() { } private Client clientWithUri(String uri) { - return RestClientBuilder.newBuilder().baseUri(URI.create(uri)).build(Client.class); + return QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(uri)).build(Client.class); } @Path("/foo") diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/FormTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/FormTest.java index 4c7426ff74168d..0692a32a3f1d89 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/FormTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/FormTest.java @@ -12,7 +12,6 @@ import jakarta.ws.rs.core.Form; import jakarta.ws.rs.core.MediaType; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -29,7 +28,7 @@ public class FormTest { @Test void shouldPassUrlEncodedAsForm() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); assertThat(client.echo(new Form().param("name", "World"))).isEqualTo("Hello, World!"); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidHostTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidHostTest.java index bb5550b2c5e8e5..2280f60b333520 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidHostTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidHostTest.java @@ -8,7 +8,6 @@ import jakarta.ws.rs.core.Response; import org.assertj.core.api.Assertions; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,7 +21,7 @@ public class InvalidHostTest { @Test void shouldThrowDummyException() { - Client client = RestClientBuilder.newBuilder().baseUri(URI.create("http://localhost2:1234/")) + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create("http://localhost2:1234/")) .register(DummyExceptionMapper.class).build(Client.class); Assertions.assertThatThrownBy(client::get).isInstanceOf(DummyException.class); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidURITest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidURITest.java index 5abe15c964d57d..63e1a520ba5895 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidURITest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/InvalidURITest.java @@ -9,7 +9,6 @@ import org.assertj.core.api.Assertions; import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -57,7 +56,7 @@ void shouldWork() { } private Client clientWithUri(String uri) { - return RestClientBuilder.newBuilder().baseUri(URI.create(uri)).build(Client.class); + return QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(uri)).build(Client.class); } @Path("/foo") diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MapParamsTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MapParamsTest.java index 58ce21d52f25c3..840ddf52fa94a9 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MapParamsTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MapParamsTest.java @@ -14,7 +14,6 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.UriInfo; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.resteasy.reactive.RestQuery; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -33,14 +32,14 @@ public class MapParamsTest { @Test void testQueryParamsWithRegularMap() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); String response = client.regularMap(Map.of("foo", "bar", "k", "v")); assertThat(response).contains("foo=bar").contains("k=v"); } @Test void testQueryParamsWithMultiMap() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); MultivaluedMap map = new MultivaluedHashMap<>(); map.putSingle("first", 1); map.put("second", List.of(2, 4, 6)); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java index 55e116ad49c4fc..10f85b0505755f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java @@ -17,7 +17,6 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.resteasy.reactive.RestStreamElementType; import org.jboss.resteasy.reactive.common.util.RestMediaType; import org.junit.jupiter.api.Test; @@ -110,7 +109,7 @@ void shouldReadNdjsonFromSingleMessage() throws InterruptedException { } private Client createClient(URI uri) { - return RestClientBuilder.newBuilder().baseUri(uri).register(new TestJacksonBasicMessageBodyReader()) + return QuarkusRestClientBuilder.newBuilder().baseUri(uri).register(new TestJacksonBasicMessageBodyReader()) .build(Client.class); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/NoPathInTheAppTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/NoPathInTheAppTest.java index 4e0790b5aff0a8..697a0afa6efcc2 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/NoPathInTheAppTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/NoPathInTheAppTest.java @@ -5,7 +5,6 @@ import java.net.URI; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,7 +39,7 @@ public void shutdown() { @Test void shouldGet() { - PlaylistService client = RestClientBuilder.newBuilder().baseUri(URI.create("http://localhost:8181/hello")) + PlaylistService client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create("http://localhost:8181/hello")) .build(PlaylistService.class); assertThat(client.get()).isEqualTo("hello, world!"); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/timeout/BuilderReadTimeoutTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/timeout/BuilderReadTimeoutTest.java index e870bcf7b64eaf..d8429e7334243a 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/timeout/BuilderReadTimeoutTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/timeout/BuilderReadTimeoutTest.java @@ -15,10 +15,10 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; @@ -34,7 +34,7 @@ public class BuilderReadTimeoutTest { @Test void shouldTimeoutIfReadTimeoutSetShort() { - Client client = RestClientBuilder.newBuilder() + Client client = QuarkusRestClientBuilder.newBuilder() .baseUri(uri) .readTimeout(1, TimeUnit.SECONDS) .build(Client.class); @@ -45,7 +45,7 @@ void shouldTimeoutIfReadTimeoutSetShort() { @Test void shouldNotTimeoutOnFastResponse() { - Client client = RestClientBuilder.newBuilder() + Client client = QuarkusRestClientBuilder.newBuilder() .baseUri(uri) .readTimeout(1, TimeUnit.SECONDS) .build(Client.class); @@ -55,7 +55,7 @@ void shouldNotTimeoutOnFastResponse() { @Test void shouldNotTimeoutOnDefaultTimeout() { - Client client = RestClientBuilder.newBuilder() + Client client = QuarkusRestClientBuilder.newBuilder() .baseUri(uri) .build(Client.class); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java new file mode 100644 index 00000000000000..8b8ed80329f44d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java @@ -0,0 +1,232 @@ +package io.quarkus.rest.client.reactive; + +import java.net.URI; +import java.net.URL; +import java.security.KeyStore; +import java.util.ServiceLoader; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; + +import jakarta.ws.rs.core.Configurable; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.RestClientDefinitionException; +import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; +import org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener; + +import io.quarkus.rest.client.reactive.runtime.QuarkusRestClientBuilderImpl; +import io.quarkus.rest.client.reactive.runtime.RestClientBuilderImpl; + +/** + * This is the main entry point for creating a Type Safe Quarkus Rest Client. + *

    + * Invoking {@link #newBuilder()} is intended to always create a new instance, not use a cached version. + *

    + *

    + * The QuarkusRestClientBuilder is based on {@link RestClientBuilder} class but Quarkus specific. + *

    + */ +public interface QuarkusRestClientBuilder extends Configurable { + + static QuarkusRestClientBuilder newBuilder() { + RestClientBuilderImpl proxy = new RestClientBuilderImpl(); + for (RestClientBuilderListener listener : ServiceLoader.load(RestClientBuilderListener.class)) { + listener.onNewBuilder(proxy); + } + + return new QuarkusRestClientBuilderImpl(proxy); + } + + /** + * Specifies the base URL to be used when making requests. Assuming that the interface has a + * @Path("/api") at the interface level and a url is given with + * http://my-service:8080/service then all REST calls will be invoked with a url of + * http://my-service:8080/service/api in addition to any @Path annotations included on the + * method. + * + * Subsequent calls to this method will replace the previously specified baseUri/baseUrl. + * + * @param url the base Url for the service. + * @return the current builder with the baseUrl set. + */ + QuarkusRestClientBuilder baseUrl(URL url); + + /** + * Specifies the base URI to be used when making requests. Assuming that the interface has a + * @Path("/api") at the interface level and a uri is given with + * http://my-service:8080/service then all REST calls will be invoked with a uri of + * http://my-service:8080/service/api in addition to any @Path annotations included on the + * method. + * + * Subsequent calls to this method will replace the previously specified baseUri/baseUrl. + * + * @param uri the base URI for the service. + * @return the current builder with the baseUri set + * @throws IllegalArgumentException if the passed in URI is invalid + */ + QuarkusRestClientBuilder baseUri(URI uri); + + /** + * Set the connect timeout. + *

    + * Like JAX-RS's jakarta.ws.rs.client.ClientBuilder's connectTimeout method, specifying a + * timeout of 0 represents infinity, and negative values are not allowed. + *

    + *

    + * If the client instance is injected via CDI and the + * "fully.qualified.InterfaceName/mp-rest/connectTimeout" property is set via MicroProfile + * Config, that property's value will override, the value specified to this method. + *

    + * + * @param timeout the maximum time to wait. + * @param unit the time unit of the timeout argument. + * @return the current builder with the connect timeout set. + * @throws IllegalArgumentException if the value of timeout is negative. + */ + QuarkusRestClientBuilder connectTimeout(long timeout, TimeUnit unit); + + /** + * Set the read timeout. + *

    + * Like JAX-RS's jakarta.ws.rs.client.ClientBuilder's readTimeout method, specifying a + * timeout of 0 represents infinity, and negative values are not allowed. + *

    + *

    + * Also like the JAX-RS Client API, if the read timeout is reached, the client interface method will throw a + * jakarta.ws.rs.ProcessingException. + *

    + *

    + * If the client instance is injected via CDI and the + * "fully.qualified.InterfaceName/mp-rest/readTimeout" property is set via MicroProfile Config, + * that property's value will override, the value specified to this method. + *

    + * + * @param timeout the maximum time to wait. + * @param unit the time unit of the timeout argument. + * @return the current builder with the connect timeout set. + * @throws IllegalArgumentException if the value of timeout is negative. + */ + QuarkusRestClientBuilder readTimeout(long timeout, TimeUnit unit); + + /** + * Specifies the SSL context to use when creating secured transport connections to server endpoints from web targets + * created by the client instance that is using this SSL context. + * + * @param sslContext the ssl context + * @return the current builder with ssl context set + * @throws NullPointerException if the sslContext parameter is null. + */ + QuarkusRestClientBuilder sslContext(SSLContext sslContext); + + /** + * Set whether hostname verification is enabled. + * + * @param verifyHost whether the hostname verification is enabled. + * @return the current builder with the hostname verification set. + */ + QuarkusRestClientBuilder verifyHost(boolean verifyHost); + + /** + * Set the client-side trust store. + * + * @param trustStore key store + * @return the current builder with the trust store set + * @throws NullPointerException if the trustStore parameter is null. + */ + QuarkusRestClientBuilder trustStore(KeyStore trustStore); + + /** + * Set the client-side trust store. + * + * @param trustStore key store + * @param trustStorePassword the password for the specified trustStore + * @return the current builder with the trust store set + * @throws NullPointerException if the trustStore parameter is null. + */ + QuarkusRestClientBuilder trustStore(KeyStore trustStore, String trustStorePassword); + + /** + * Set the client-side key store. + * + * @param keyStore key store + * @param keystorePassword the password for the specified keyStore + * @return the current builder with the key store set + * @throws NullPointerException if the keyStore parameter is null. + */ + QuarkusRestClientBuilder keyStore(KeyStore keyStore, String keystorePassword); + + /** + * Set the hostname verifier to verify the endpoint's hostname + * + * @param hostnameVerifier the hostname verifier + * @return the current builder with hostname verifier set + * @throws NullPointerException if the hostnameVerifier parameter is null. + */ + QuarkusRestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier); + + /** + * Specifies whether client built by this builder should follow HTTP redirect responses (30x) or not. + * + * @param follow true if the client should follow HTTP redirects, false if not. + * @return the current builder with the followRedirect property set. + */ + QuarkusRestClientBuilder followRedirects(boolean follow); + + /** + * Specifies the HTTP proxy hostname/IP address and port to use for requests from client instances. + * + * @param proxyHost hostname or IP address of proxy server - must be non-null + * @param proxyPort port of proxy server + * @throws IllegalArgumentException if the proxyHost is null or the proxyPort is invalid + * @return the current builder with the proxy host set + */ + QuarkusRestClientBuilder proxyAddress(String proxyHost, int proxyPort); + + /** + * Specifies the proxy username. + * + * @param proxyUser the proxy username. + * @return the current builder + */ + QuarkusRestClientBuilder proxyUser(String proxyUser); + + /** + * Specifies the proxy password. + * + * @param proxyPassword the proxy password. + * @return the current builder + */ + QuarkusRestClientBuilder proxyPassword(String proxyPassword); + + /** + * Specifies the hosts to access without proxy. + * + * @param nonProxyHosts the hosts to access without proxy. + * @return the current builder + */ + QuarkusRestClientBuilder nonProxyHosts(String nonProxyHosts); + + /** + * Specifies the URI formatting style to use when multiple query parameter values are passed to the client. + * + * @param style the URI formatting style to use for multiple query parameter values + * @return the current builder with the style of query params set + */ + QuarkusRestClientBuilder queryParamStyle(QueryParamStyle style); + + /** + * Based on the configured QuarkusRestClientBuilder, creates a new instance of the given REST interface to invoke API calls + * against. + * + * @param clazz the interface that defines REST API methods for use + * @param the type of the interface + * @return a new instance of an implementation of this REST interface that + * @throws IllegalStateException + * if not all pre-requisites are satisfied for the builder, this exception may get thrown. For instance, + * if the base URI/URL has not been set. + * @throws RestClientDefinitionException if the passed-in interface class is invalid. + */ + T build(Class clazz) throws IllegalStateException, RestClientDefinitionException; +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java new file mode 100644 index 00000000000000..0a6a18756dfa0e --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java @@ -0,0 +1,186 @@ +package io.quarkus.rest.client.reactive.runtime; + +import java.net.URI; +import java.net.URL; +import java.security.KeyStore; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; + +import jakarta.ws.rs.core.Configuration; + +import org.eclipse.microprofile.rest.client.RestClientDefinitionException; +import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; + +public class QuarkusRestClientBuilderImpl implements QuarkusRestClientBuilder { + + private final RestClientBuilderImpl proxy; + + public QuarkusRestClientBuilderImpl(RestClientBuilderImpl proxy) { + this.proxy = proxy; + } + + @Override + public QuarkusRestClientBuilder baseUrl(URL url) { + proxy.baseUrl(url); + return this; + } + + @Override + public QuarkusRestClientBuilder baseUri(URI uri) { + proxy.baseUri(uri); + return this; + } + + @Override + public QuarkusRestClientBuilder connectTimeout(long timeout, TimeUnit unit) { + proxy.connectTimeout(timeout, unit); + return this; + } + + @Override + public QuarkusRestClientBuilder readTimeout(long timeout, TimeUnit unit) { + proxy.readTimeout(timeout, unit); + return this; + } + + @Override + public QuarkusRestClientBuilder sslContext(SSLContext sslContext) { + proxy.sslContext(sslContext); + return this; + } + + @Override + public QuarkusRestClientBuilder verifyHost(boolean verifyHost) { + proxy.verifyHost(verifyHost); + return this; + } + + @Override + public QuarkusRestClientBuilder trustStore(KeyStore trustStore) { + proxy.trustStore(trustStore); + return this; + } + + @Override + public QuarkusRestClientBuilder trustStore(KeyStore trustStore, String trustStorePassword) { + proxy.trustStore(trustStore, trustStorePassword); + return this; + } + + @Override + public QuarkusRestClientBuilder keyStore(KeyStore keyStore, String keystorePassword) { + proxy.keyStore(keyStore, keystorePassword); + return this; + } + + @Override + public QuarkusRestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) { + proxy.hostnameVerifier(hostnameVerifier); + return this; + } + + @Override + public QuarkusRestClientBuilder followRedirects(boolean follow) { + proxy.followRedirects(follow); + return this; + } + + @Override + public QuarkusRestClientBuilder proxyAddress(String proxyHost, int proxyPort) { + proxy.proxyAddress(proxyHost, proxyPort); + return this; + } + + @Override + public QuarkusRestClientBuilder proxyPassword(String proxyPassword) { + proxy.proxyPassword(proxyPassword); + return this; + } + + @Override + public QuarkusRestClientBuilder proxyUser(String proxyUser) { + proxy.proxyUser(proxyUser); + return this; + } + + @Override + public QuarkusRestClientBuilder nonProxyHosts(String nonProxyHosts) { + proxy.nonProxyHosts(nonProxyHosts); + return this; + } + + @Override + public QuarkusRestClientBuilder queryParamStyle(QueryParamStyle style) { + proxy.queryParamStyle(style); + return this; + } + + @Override + public Configuration getConfiguration() { + return proxy.getConfiguration(); + } + + @Override + public QuarkusRestClientBuilder property(String name, Object value) { + proxy.property(name, value); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Class componentClass) { + proxy.register(componentClass); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Class componentClass, int priority) { + proxy.register(componentClass, priority); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Class componentClass, Class... contracts) { + proxy.register(componentClass, contracts); + return null; + } + + @Override + public QuarkusRestClientBuilder register(Class componentClass, Map, Integer> contracts) { + proxy.register(componentClass, contracts); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Object component) { + proxy.register(component); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Object component, int priority) { + proxy.register(component, priority); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Object component, Class... contracts) { + proxy.register(component, contracts); + return this; + } + + @Override + public QuarkusRestClientBuilder register(Object component, Map, Integer> contracts) { + proxy.register(component, contracts); + return this; + } + + @Override + public T build(Class clazz) throws IllegalStateException, RestClientDefinitionException { + return proxy.build(clazz); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ReactiveRestClientBuilderFactory.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ReactiveRestClientBuilderFactory.java index d055bda239e18d..5a0a1c1481dc5e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ReactiveRestClientBuilderFactory.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ReactiveRestClientBuilderFactory.java @@ -18,9 +18,10 @@ public RestClientBuilder newBuilder(Class proxyType, RestClientsConfig restCl } RestClientBuilderImpl restClientBuilder = new RestClientBuilderImpl(); + QuarkusRestClientBuilderImpl quarkusRestClientBuilder = new QuarkusRestClientBuilderImpl(restClientBuilder); RestClientCDIDelegateBuilder restClientBase = new RestClientCDIDelegateBuilder<>(proxyType, baseUri, configKey, restClientsConfigRoot); - restClientBase.configureBuilder(restClientBuilder); + restClientBase.configureBuilder(quarkusRestClientBuilder); return restClientBuilder; } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index 88d27766b88747..2173915ef00544 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -19,11 +19,11 @@ import javax.net.ssl.HostnameVerifier; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties; import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.restclient.config.RestClientConfig; import io.quarkus.restclient.config.RestClientsConfig; @@ -55,16 +55,12 @@ private RestClientCDIDelegateBuilder(Class jaxrsInterface, String baseUriFrom } private T build() { - RestClientBuilder builder = RestClientBuilder.newBuilder(); - if (!(builder instanceof RestClientBuilderImpl)) { - throw new IllegalStateException("Expected RestClientBuilder to be an instance of " - + RestClientBuilderImpl.class.getName() + ", got " + builder.getClass().getName()); - } - configureBuilder((RestClientBuilderImpl) builder); + QuarkusRestClientBuilder builder = QuarkusRestClientBuilder.newBuilder(); + configureBuilder(builder); return builder.build(jaxrsInterface); } - void configureBuilder(RestClientBuilderImpl builder) { + void configureBuilder(QuarkusRestClientBuilder builder) { configureBaseUrl(builder); configureTimeouts(builder); configureProviders(builder); @@ -76,7 +72,7 @@ void configureBuilder(RestClientBuilderImpl builder) { configureCustomProperties(builder); } - private void configureCustomProperties(RestClientBuilder builder) { + private void configureCustomProperties(QuarkusRestClientBuilder builder) { Optional encoder = configRoot.multipartPostEncoderMode; if (encoder != null && encoder.isPresent()) { HttpPostRequestEncoder.EncoderMode mode = HttpPostRequestEncoder.EncoderMode @@ -135,7 +131,7 @@ private void configureCustomProperties(RestClientBuilder builder) { } } - private void configureProxy(RestClientBuilderImpl builder) { + private void configureProxy(QuarkusRestClientBuilder builder) { Optional maybeProxy = oneOf(clientConfigByClassName().proxyAddress, clientConfigByConfigKey().proxyAddress, configRoot.proxyAddress); if (maybeProxy.isEmpty()) { @@ -158,7 +154,7 @@ private void configureProxy(RestClientBuilderImpl builder) { } } - private void configureQueryParamStyle(RestClientBuilder builder) { + private void configureQueryParamStyle(QuarkusRestClientBuilder builder) { Optional maybeQueryParamStyle = oneOf(clientConfigByClassName().queryParamStyle, clientConfigByConfigKey().queryParamStyle, configRoot.queryParamStyle); if (maybeQueryParamStyle.isPresent()) { @@ -167,7 +163,7 @@ private void configureQueryParamStyle(RestClientBuilder builder) { } } - private void configureRedirects(RestClientBuilder builder) { + private void configureRedirects(QuarkusRestClientBuilder builder) { Optional maxRedirects = oneOf(clientConfigByClassName().maxRedirects, clientConfigByConfigKey().maxRedirects, configRoot.maxRedirects); if (maxRedirects.isPresent()) { @@ -181,7 +177,7 @@ private void configureRedirects(RestClientBuilder builder) { } } - private void configureShared(RestClientBuilder builder) { + private void configureShared(QuarkusRestClientBuilder builder) { Optional shared = oneOf(clientConfigByClassName().shared, clientConfigByConfigKey().shared); if (shared.isPresent()) { @@ -198,7 +194,7 @@ private void configureShared(RestClientBuilder builder) { } } - private void configureSsl(RestClientBuilderImpl builder) { + private void configureSsl(QuarkusRestClientBuilder builder) { Optional maybeTrustStore = oneOf(clientConfigByClassName().trustStore, clientConfigByConfigKey().trustStore, configRoot.trustStore); @@ -222,7 +218,7 @@ private void configureSsl(RestClientBuilderImpl builder) { .ifPresent(builder::verifyHost); } - private void registerHostnameVerifier(String verifier, RestClientBuilder builder) { + private void registerHostnameVerifier(String verifier, QuarkusRestClientBuilder builder) { try { Class verifierClass = Thread.currentThread().getContextClassLoader().loadClass(verifier); builder.hostnameVerifier((HostnameVerifier) verifierClass.getDeclaredConstructor().newInstance()); @@ -242,7 +238,7 @@ private void registerHostnameVerifier(String verifier, RestClientBuilder builder } } - private void registerKeyStore(String keyStorePath, RestClientBuilder builder) { + private void registerKeyStore(String keyStorePath, QuarkusRestClientBuilder builder) { Optional keyStorePassword = oneOf(clientConfigByClassName().keyStorePassword, clientConfigByConfigKey().keyStorePassword, configRoot.keyStorePassword); Optional keyStoreType = oneOf(clientConfigByClassName().keyStoreType, @@ -268,7 +264,7 @@ private void registerKeyStore(String keyStorePath, RestClientBuilder builder) { } } - private void registerTrustStore(String trustStorePath, RestClientBuilderImpl builder) { + private void registerTrustStore(String trustStorePath, QuarkusRestClientBuilder builder) { Optional maybeTrustStorePassword = oneOf(clientConfigByClassName().trustStorePassword, clientConfigByConfigKey().trustStorePassword, configRoot.trustStorePassword); Optional maybeTrustStoreType = oneOf(clientConfigByClassName().trustStoreType, @@ -319,7 +315,7 @@ private InputStream locateStream(String path) throws FileNotFoundException { } } - private void configureProviders(RestClientBuilder builder) { + private void configureProviders(QuarkusRestClientBuilder builder) { Optional maybeProviders = oneOf(clientConfigByClassName().providers, clientConfigByConfigKey().providers, configRoot.providers); if (maybeProviders.isPresent()) { @@ -327,7 +323,7 @@ private void configureProviders(RestClientBuilder builder) { } } - private void registerProviders(RestClientBuilder builder, String providersAsString) { + private void registerProviders(QuarkusRestClientBuilder builder, String providersAsString) { for (String s : providersAsString.split(",")) { builder.register(providerClassForName(s.trim())); } @@ -341,7 +337,7 @@ private Class providerClassForName(String name) { } } - private void configureTimeouts(RestClientBuilder builder) { + private void configureTimeouts(QuarkusRestClientBuilder builder) { Long connectTimeout = oneOf(clientConfigByClassName().connectTimeout, clientConfigByConfigKey().connectTimeout).orElse(this.configRoot.connectTimeout); if (connectTimeout != null) { @@ -355,7 +351,7 @@ private void configureTimeouts(RestClientBuilder builder) { } } - private void configureBaseUrl(RestClientBuilder builder) { + private void configureBaseUrl(QuarkusRestClientBuilder builder) { Optional propertyOptional = oneOf(clientConfigByClassName().uri, clientConfigByConfigKey().uri); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index 058e6043593b51..b5a42ff8518379 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -79,7 +79,7 @@ public void testClientSpecificConfigs() { // when - RestClientBuilderImpl restClientBuilderMock = Mockito.mock(RestClientBuilderImpl.class); + QuarkusRestClientBuilderImpl restClientBuilderMock = Mockito.mock(QuarkusRestClientBuilderImpl.class); new RestClientCDIDelegateBuilder<>(TestClient.class, "http://localhost:8080", "test-client", @@ -123,7 +123,7 @@ public void testGlobalConfigs() { // when - RestClientBuilderImpl restClientBuilderMock = Mockito.mock(RestClientBuilderImpl.class); + QuarkusRestClientBuilderImpl restClientBuilderMock = Mockito.mock(QuarkusRestClientBuilderImpl.class); new RestClientCDIDelegateBuilder<>(TestClient.class, "http://localhost:8080", "test-client", diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 3aa318a5a515d1..6c4f6498d007b7 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -11,7 +11,6 @@ import jakarta.inject.Inject; import jakarta.ws.rs.core.MediaType; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.jboss.resteasy.reactive.RestResponse; @@ -23,6 +22,7 @@ import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; import io.quarkus.it.rest.client.main.selfsigned.ExternalSelfSignedClient; import io.quarkus.it.rest.client.main.wronghost.WrongHostClient; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.smallrye.mutiny.Uni; import io.vertx.core.Future; import io.vertx.core.json.Json; @@ -62,7 +62,7 @@ void init(@Observes Router router) { router.post("/call-client-with-exception-mapper").blockingHandler(rc -> { String url = rc.getBody().toString(); - ClientWithExceptionMapper client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + ClientWithExceptionMapper client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .register(MyResponseExceptionMapper.class) .build(ClientWithExceptionMapper.class); callGet(rc, client); @@ -78,7 +78,7 @@ void init(@Observes Router router) { router.route("/call-client").blockingHandler(rc -> { String url = rc.getBody().toString(); - AppleClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + AppleClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .build(AppleClient.class); Uni apple1 = Uni.createFrom().item(client.swapApple(new Apple("lobo"))); Uni apple2 = Uni.createFrom().completionStage(client.completionSwapApple(new Apple("lobo2"))); @@ -107,7 +107,7 @@ void init(@Observes Router router) { router.route("/call-client-retry").blockingHandler(rc -> { String url = rc.getBody().toString(); - AppleClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url + "/does-not-exist")) + AppleClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url + "/does-not-exist")) .build(AppleClient.class); AtomicInteger count = new AtomicInteger(0); client.uniSwapApple(new Apple("lobo")).onFailure().retry().until(t -> count.incrementAndGet() <= 3) @@ -123,7 +123,7 @@ void init(@Observes Router router) { router.route("/call-hello-client").blockingHandler(rc -> { String url = rc.getBody().toString(); - HelloClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + HelloClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .build(HelloClient.class); String greeting = client.greeting("John", 2); rc.response().end(greeting); @@ -131,7 +131,7 @@ void init(@Observes Router router) { router.route("/call-hello-client-trace").blockingHandler(rc -> { String url = rc.getBody().toString(); - HelloClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + HelloClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .build(HelloClient.class); String greeting = client.greeting("Mary", 3); rc.response().end(greeting); @@ -139,7 +139,7 @@ void init(@Observes Router router) { router.route("/call-helloFromMessage-client").blockingHandler(rc -> { String url = rc.getBody().toString(); - HelloClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + HelloClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .build(HelloClient.class); String greeting = client.fromMessage(new HelloClient.Message("Hello world")); rc.response().end(greeting); @@ -150,7 +150,7 @@ void init(@Observes Router router) { router.route("/call-params-client-with-param-first").blockingHandler(rc -> { String url = rc.getBody().toString(); - ParamClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + ParamClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .build(ParamClient.class); String result = client.getParam(Param.FIRST); rc.response().end(result); @@ -158,7 +158,7 @@ void init(@Observes Router router) { router.route("/rest-response").blockingHandler(rc -> { String url = rc.getBody().toString(); - RestResponseClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + RestResponseClient client = QuarkusRestClientBuilder.newBuilder().baseUri(URI.create(url)) .property("microprofile.rest.client.disable.default.mapper", true) .build(RestResponseClient.class); RestResponse restResponse = client.response(); diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/ClientWithCustomObjectMapperTest.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/ClientWithCustomObjectMapperTest.java index f925704718c3b3..ffa07885371392 100644 --- a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/ClientWithCustomObjectMapperTest.java +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/ClientWithCustomObjectMapperTest.java @@ -19,7 +19,6 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.ext.ContextResolver; -import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.resteasy.reactive.ClientWebApplicationException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -31,6 +30,7 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.junit.QuarkusTest; import io.smallrye.mutiny.Uni; @@ -48,12 +48,12 @@ public void setUp() throws MalformedURLException { wireMockServer = new WireMockServer(options().port(20001)); wireMockServer.start(); - clientAllowsUnknown = RestClientBuilder.newBuilder() + clientAllowsUnknown = QuarkusRestClientBuilder.newBuilder() .baseUrl(new URL(wireMockServer.baseUrl())) .register(ClientObjectMapperUnknown.class) .build(MyClient.class); - clientDisallowsUnknown = RestClientBuilder.newBuilder() + clientDisallowsUnknown = QuarkusRestClientBuilder.newBuilder() .baseUrl(new URL(wireMockServer.baseUrl())) .register(ClientObjectMapperNoUnknown.class) .build(MyClient.class); From c034ca57858689709da60bd24dbbd48391313d88 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 25 Apr 2023 09:28:45 +0200 Subject: [PATCH 140/333] Scheduler Dev UI - always log id of programmatically scheduled jobs --- .../scheduler/runtime/devui/SchedulerJsonRPCService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/devui/SchedulerJsonRPCService.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/devui/SchedulerJsonRPCService.java index cde1a07f4f5a3e..331261700c7cc4 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/devui/SchedulerJsonRPCService.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/devui/SchedulerJsonRPCService.java @@ -235,12 +235,15 @@ private JsonObject newExecutionLog(Trigger trigger, boolean success, String mess JsonObject log = new JsonObject() .put("timestamp", LocalDateTime.now().toString()) .put("success", success); - if (userDefinedIdentity) { - log.put("triggerIdentity", trigger.getId()); - } String description = trigger.getMethodDescription(); if (description != null) { log.put("triggerMethodDescription", description); + if (userDefinedIdentity) { + log.put("triggerIdentity", trigger.getId()); + } + } else { + // Always add identity if no method description is available + log.put("triggerIdentity", trigger.getId()); } if (message != null) { log.put("message", message); From de0aa85aa9b1a392c80d8bcb7694d9bd4a509a41 Mon Sep 17 00:00:00 2001 From: Radovan Synek Date: Tue, 25 Apr 2023 11:36:31 +0200 Subject: [PATCH 141/333] PLANNER-2861 Upgrate to OptaPlanner 9 --- docs/src/main/asciidoc/optaplanner.adoc | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/src/main/asciidoc/optaplanner.adoc b/docs/src/main/asciidoc/optaplanner.adoc index 4b0a9bfc77c7ee..75332ca53242cf 100644 --- a/docs/src/main/asciidoc/optaplanner.adoc +++ b/docs/src/main/asciidoc/optaplanner.adoc @@ -280,9 +280,9 @@ public class Lesson { private String teacher; private String studentGroup; - @PlanningVariable(valueRangeProviderRefs = "timeslotRange") + @PlanningVariable private Timeslot timeslot; - @PlanningVariable(valueRangeProviderRefs = "roomRange") + @PlanningVariable private Room room; public Lesson() { @@ -339,11 +339,8 @@ The `Lesson` class has an `@PlanningEntity` annotation, so OptaPlanner knows that this class changes during solving because it contains one or more planning variables. -The `timeslot` field has an `@PlanningVariable` annotation, -so OptaPlanner knows that it can change its value. -In order to find potential `Timeslot` instances to assign to this field, -OptaPlanner uses the `valueRangeProviderRefs` property to connect to a value range provider -(explained later) that provides a `List` to pick from. +The `timeslot` field has an `@PlanningVariable` annotation, so OptaPlanner knows that it can change its value. +In order to find potential Timeslot instances to assign to this field, OptaPlanner uses the variable type to connect to a value range provider that provides a List to pick from. The `room` field also has an `@PlanningVariable` annotation, for the same reasons. @@ -468,7 +465,7 @@ public class TimeTableConstraintProvider implements ConstraintProvider { Joiners.equal(Lesson::getTeacher), Joiners.lessThan(Lesson::getId)) .penalize(HardSoftScore.ONE_HARD) - .asConstraint(""Teacher conflict"); + .asConstraint("Teacher conflict"); } Constraint studentGroupConflict(ConstraintFactory constraintFactory) { @@ -479,7 +476,7 @@ public class TimeTableConstraintProvider implements ConstraintProvider { Joiners.equal(Lesson::getStudentGroup), Joiners.lessThan(Lesson::getId)) .penalize(HardSoftScore.ONE_HARD) - .asConstraint(""Student group conflict"); + .asConstraint("Student group conflict"); } } @@ -518,10 +515,10 @@ import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; @PlanningSolution public class TimeTable { - @ValueRangeProvider(id = "timeslotRange") + @ValueRangeProvider @ProblemFactCollectionProperty private List timeslotList; - @ValueRangeProvider(id = "roomRange") + @ValueRangeProvider @ProblemFactCollectionProperty private List roomList; @PlanningEntityCollectionProperty @@ -585,7 +582,7 @@ However, this class is also the output of the solution: The `timeslotList` field is a value range provider. It holds the `Timeslot` instances which OptaPlanner can pick from to assign to the `timeslot` field of `Lesson` instances. The `timeslotList` field has an `@ValueRangeProvider` annotation to connect the `@PlanningVariable` with the `@ValueRangeProvider`, -by matching the value of the `id` property with the value of the `valueRangeProviderRefs` property of the `@PlanningVariable` annotation in the `Lesson` class. +by matching the type of the planning variable with the type returned by the value range provider. Following the same logic, the `roomList` field also has an `@ValueRangeProvider` annotation. From b770e4b14d9bee9b5a247ed8423b3bf37ac3c680 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 25 Apr 2023 12:57:10 +0200 Subject: [PATCH 142/333] Support registering a ClientHeadersFactory in REST Client Reactive API Fix https://github.com/quarkusio/quarkus/issues/32189 --- .../main/asciidoc/rest-client-reactive.adoc | 1 + .../ReactiveClientHeadersFromBuilderTest.java | 132 ++++++++++++++++++ .../reactive/QuarkusRestClientBuilder.java | 17 +++ .../MicroProfileRestClientRequestFilter.java | 14 ++ .../runtime/QuarkusRestClientBuilderImpl.java | 19 +++ .../ClientHeadersFactoryContextResolver.java | 23 +++ 6 files changed, 206 insertions(+) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromBuilderTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/ClientHeadersFactoryContextResolver.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index fc5d15b90b510c..d2f0f4d0c5705d 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -818,6 +818,7 @@ More details about this can be found in https://smallrye.io/smallrye-mutiny/late There are a few ways in which you can specify custom headers for your REST calls: - by registering a `ClientHeadersFactory` or a `ReactiveClientHeadersFactory` with the `@RegisterClientHeaders` annotation +- by programmatically registering a `ClientHeadersFactory` or a `ReactiveClientHeadersFactory` with the `QuarkusRestClientBuilder.clientHeadersFactory(factory)` method - by specifying the value of the header with `@ClientHeaderParam` - by specifying the value of the header by `@HeaderParam` diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromBuilderTest.java new file mode 100644 index 00000000000000..e8151bc3fdcedc --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromBuilderTest.java @@ -0,0 +1,132 @@ +package io.quarkus.rest.client.reactive.headers; + +import static io.restassured.RestAssured.given; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Unremovable; +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.rest.client.reactive.ReactiveClientHeadersFactory; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Vertx; + +public class ReactiveClientHeadersFromBuilderTest { + private static final String HEADER_NAME = "my-header"; + private static final String HEADER_VALUE = "oifajrofijaeoir5gjaoasfaxcvcz"; + public static final String COPIED_INCOMING_HEADER = "copied-incoming-header"; + public static final String INCOMING_HEADER = "incoming-header"; + public static final String DIRECT_HEADER_PARAM = "direct-header-param"; + public static final String DIRECT_HEADER_PARAM_VAL = "direct-header-param-val"; + + @TestHTTPResource + URI baseUri; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(Client.class, TestJacksonBasicMessageBodyReader.class) + .addAsResource( + new StringAsset("my.property-value=" + HEADER_VALUE), + "application.properties")); + + @Test + void shouldPropagateHeaders() { + // we're calling a resource that sets "incoming-header" header + // this header should be dropped by the client and its value should be put into copied-incoming-header + String propagatedHeaderValue = "propag8ed header"; + // @formatter:off + var response = + given() + .header(INCOMING_HEADER, propagatedHeaderValue) + .body(baseUri.toString()) + .when() + .post("/call-client") + .thenReturn(); + // @formatter:on + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.jsonPath().getString(INCOMING_HEADER)).isNull(); + assertThat(response.jsonPath().getString(COPIED_INCOMING_HEADER)).isEqualTo(format("[%s]", propagatedHeaderValue)); + assertThat(response.jsonPath().getString(HEADER_NAME)).isEqualTo(format("[%s]", HEADER_VALUE)); + assertThat(response.jsonPath().getString(DIRECT_HEADER_PARAM)).isEqualTo(format("[%s]", DIRECT_HEADER_PARAM_VAL)); + } + + @Path("/") + @ApplicationScoped + public static class Resource { + + @GET + @Produces("application/json") + public Map> returnHeaderValues(@Context HttpHeaders headers) { + return headers.getRequestHeaders(); + } + + @Path("/call-client") + @POST + public Map> callClient(String uri) { + ReactiveClientHeadersFromBuilderTest.Client client = QuarkusRestClientBuilder.newBuilder() + .baseUri(URI.create(uri)) + .clientHeadersFactory(CustomReactiveClientHeadersFactory.class) + .register(new TestJacksonBasicMessageBodyReader()) + .build(ReactiveClientHeadersFromBuilderTest.Client.class); + return client.getWithHeader(DIRECT_HEADER_PARAM_VAL); + } + } + + @ApplicationScoped + public static class Service { + @Blocking + public String getValue() { + return HEADER_VALUE; + } + } + + public interface Client { + @GET + Map> getWithHeader(@HeaderParam(DIRECT_HEADER_PARAM) String directHeaderParam); + } + + @Unremovable + @ApplicationScoped + public static class CustomReactiveClientHeadersFactory extends ReactiveClientHeadersFactory { + + @Inject + Service service; + + @Inject + Vertx vertx; + + @Override + public Uni> getHeaders(MultivaluedMap incomingHeaders, + MultivaluedMap clientOutgoingHeaders) { + return Uni.createFrom().emitter(e -> { + MultivaluedHashMap newHeaders = new MultivaluedHashMap<>(); + newHeaders.add(HEADER_NAME, service.getValue()); + newHeaders.add(COPIED_INCOMING_HEADER, incomingHeaders.getFirst(INCOMING_HEADER)); + vertx.setTimer(100L, id -> e.complete(newHeaders)); + }); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java index 8b8ed80329f44d..d06170a928b3fe 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java @@ -13,6 +13,7 @@ import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.RestClientDefinitionException; +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener; @@ -216,6 +217,22 @@ static QuarkusRestClientBuilder newBuilder() { */ QuarkusRestClientBuilder queryParamStyle(QueryParamStyle style); + /** + * Specifies the client headers factory to use. + * + * @param clientHeadersFactoryClass the client headers factory class to use. + * @return the current builder + */ + QuarkusRestClientBuilder clientHeadersFactory(Class clientHeadersFactoryClass); + + /** + * Specifies the client headers factory to use. + * + * @param clientHeadersFactory the client headers factory to use. + * @return the current builder + */ + QuarkusRestClientBuilder clientHeadersFactory(ClientHeadersFactory clientHeadersFactory); + /** * Based on the configured QuarkusRestClientBuilder, creates a new instance of the given REST interface to invoke API calls * against. diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java index b1e37e9e898a86..f460370b98e465 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java @@ -13,6 +13,7 @@ import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; +import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; import io.quarkus.arc.Arc; import io.quarkus.rest.client.reactive.HeaderFiller; @@ -65,6 +66,7 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue())); } + ClientHeadersFactory clientHeadersFactory = clientHeadersFactory(requestContext); if (clientHeadersFactory != null) { if (clientHeadersFactory instanceof ReactiveClientHeadersFactory) { // reactive @@ -87,6 +89,18 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { } } + private ClientHeadersFactory clientHeadersFactory(ResteasyReactiveClientRequestContext requestContext) { + if (requestContext.getConfiguration() instanceof ConfigurationImpl) { + ConfigurationImpl configuration = (ConfigurationImpl) requestContext.getConfiguration(); + ClientHeadersFactory localHeadersFactory = configuration.getFromContext(ClientHeadersFactory.class); + if (localHeadersFactory != null) { + return localHeadersFactory; + } + } + + return clientHeadersFactory; + } + private static List castToListOfStrings(Collection values) { List result = new ArrayList<>(); for (Object value : values) { diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java index 0a6a18756dfa0e..f901090f2f652b 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java @@ -12,9 +12,11 @@ import jakarta.ws.rs.core.Configuration; import org.eclipse.microprofile.rest.client.RestClientDefinitionException; +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.rest.client.reactive.runtime.context.ClientHeadersFactoryContextResolver; public class QuarkusRestClientBuilderImpl implements QuarkusRestClientBuilder { @@ -179,6 +181,23 @@ public QuarkusRestClientBuilder register(Object component, Map, Integer return this; } + @Override + public QuarkusRestClientBuilder clientHeadersFactory(Class clientHeadersFactoryClass) { + ClientHeadersFactory bean = BeanGrabber.getBeanIfDefined(clientHeadersFactoryClass); + if (bean == null) { + throw new IllegalArgumentException("Failed to instantiate the client headers factory " + clientHeadersFactoryClass + + ". Make sure the bean is properly configured for CDI injection."); + } + + return clientHeadersFactory(bean); + } + + @Override + public QuarkusRestClientBuilder clientHeadersFactory(ClientHeadersFactory clientHeadersFactory) { + proxy.register(new ClientHeadersFactoryContextResolver(clientHeadersFactory)); + return this; + } + @Override public T build(Class clazz) throws IllegalStateException, RestClientDefinitionException { return proxy.build(clazz); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/ClientHeadersFactoryContextResolver.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/ClientHeadersFactoryContextResolver.java new file mode 100644 index 00000000000000..eadda671008470 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/ClientHeadersFactoryContextResolver.java @@ -0,0 +1,23 @@ +package io.quarkus.rest.client.reactive.runtime.context; + +import jakarta.ws.rs.ext.ContextResolver; + +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; + +public class ClientHeadersFactoryContextResolver implements ContextResolver { + + private final ClientHeadersFactory component; + + public ClientHeadersFactoryContextResolver(ClientHeadersFactory component) { + this.component = component; + } + + @Override + public ClientHeadersFactory getContext(Class wantedClass) { + if (wantedClass.equals(ClientHeadersFactory.class)) { + return component; + } + + return null; + } +} From 2fa3fefccda6cd188022cffed8a247f788b97d12 Mon Sep 17 00:00:00 2001 From: xstefank Date: Fri, 17 Mar 2023 13:27:57 +0100 Subject: [PATCH 143/333] Allow to run MP LRA TCK with externally managed coordinator --- .../tck/lra/LRACoordinatorManager.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java index 03d62f97fdd9ba..69cd7662cb01b0 100644 --- a/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java +++ b/tcks/microprofile-lra/src/main/java/io/quarkus/tck/lra/LRACoordinatorManager.java @@ -6,6 +6,7 @@ import org.jboss.arquillian.core.api.annotation.Observes; import org.jboss.logging.Logger; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; public class LRACoordinatorManager { @@ -18,16 +19,20 @@ public class LRACoordinatorManager { public void beforeClass( @Observes(precedence = DEFAULT_PRECEDENCE) org.jboss.arquillian.test.spi.event.suite.BeforeSuite event) { - LOGGER.debug("Starting LRA coordinator on port " + coordinatorPort); - coordinatorContainer = new GenericContainer<>(DockerImageName.parse("quay.io/jbosstm/lra-coordinator:latest")) - // lra-coordinator is a Quarkus service - .withEnv("QUARKUS_HTTP_PORT", String.valueOf(coordinatorPort)) - // need to run with host network because coordinator calls the TCK services from the container - .withNetworkMode("host"); + if (System.getProperty("lra.coordinator.url") == null) { + LOGGER.debug("Starting LRA coordinator on port " + coordinatorPort); + coordinatorContainer = new GenericContainer<>(DockerImageName.parse("quay.io/jbosstm/lra-coordinator:latest")) + // lra-coordinator is a Quarkus service + .withEnv("QUARKUS_HTTP_PORT", String.valueOf(coordinatorPort)) + // need to run with host network because coordinator calls the TCK services from the container + .withNetworkMode("host") + .waitingFor(Wait.forLogMessage(".*lra-coordinator-quarkus.*", 1)); + ; - coordinatorContainer.start(); + coordinatorContainer.start(); - System.setProperty("lra.coordinator.url", String.format("http://localhost:%d/lra-coordinator", coordinatorPort)); + System.setProperty("lra.coordinator.url", String.format("http://localhost:%d/lra-coordinator", coordinatorPort)); + } } public void afterClass( From 1ff505aea37deef3bfd22183eca68598729efda0 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 25 Apr 2023 14:30:37 +0200 Subject: [PATCH 144/333] Support registering a HttpClientOptions in REST Client Reactive API Similar to https://github.com/quarkusio/quarkus/pull/32880, we can extend the new REST Client Reactive API to provide a custom HttpClientOptions. --- .../main/asciidoc/rest-client-reactive.adoc | 15 ++++-------- ...sViaProgrammaticallyClientCreatedTest.java | 17 +++++++++++++- .../reactive/QuarkusRestClientBuilder.java | 17 ++++++++++++++ .../runtime/QuarkusRestClientBuilderImpl.java | 19 +++++++++++++++ .../HttpClientOptionsContextResolver.java | 23 +++++++++++++++++++ 5 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/HttpClientOptionsContextResolver.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index d2f0f4d0c5705d..83a97652fc6864 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -497,24 +497,17 @@ public class ExtensionsResource { private final ExtensionsService extensionsService; public ExtensionsResource() { + HttpClientOptions options = new HttpClientOptions(); + // ... + extensionsService = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create("https://stage.code.quarkus.io/api")) - .register(CustomHttpClientOptions.class) <1> + .httpClientOptions(options) <1> .build(ExtensionsService.class); } // ... } - -public class CustomHttpClientOptions implements ContextResolver { - - @Override - public HttpClientOptions getContext(Class aClass) { - HttpClientOptions options = new HttpClientOptions(); - // ... - return options; - } -} ---- <1> the client will use the registered HTTP Client options over the HTTP Client options provided via CDI if any. diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java index f5d36c89e09faa..6c742f2405913d 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/CustomHttpOptionsViaProgrammaticallyClientCreatedTest.java @@ -30,7 +30,7 @@ public class CustomHttpOptionsViaProgrammaticallyClientCreatedTest { .withApplicationRoot((jar) -> jar.addClasses(Client.class)); @Test - void shouldUseCustomHttpOptions() { + void shouldUseCustomHttpOptionsUsingProvider() { // First verify the standard configuration assertThat(QuarkusRestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class).get()) .isEqualTo(EXPECTED_VALUE); @@ -43,6 +43,21 @@ void shouldUseCustomHttpOptions() { assertThatThrownBy(() -> client.get()).hasMessageContaining("HTTP header is larger than 1 bytes."); } + @Test + void shouldUseCustomHttpOptionsUsingAPI() { + // First verify the standard configuration + assertThat(QuarkusRestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class).get()) + .isEqualTo(EXPECTED_VALUE); + + // Now, it should fail if we use a custom http client options with a very limited max header size: + HttpClientOptions options = new HttpClientOptions(); + options.setMaxHeaderSize(1); // this is just to verify that this HttpClientOptions is indeed used. + Client client = QuarkusRestClientBuilder.newBuilder().baseUri(baseUri) + .httpClientOptions(options) + .build(Client.class); + assertThatThrownBy(() -> client.get()).hasMessageContaining("HTTP header is larger than 1 bytes."); + } + @Path("/") @ApplicationScoped public static class Resource { diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java index d06170a928b3fe..e562dd9a1ccbdf 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/QuarkusRestClientBuilder.java @@ -19,6 +19,7 @@ import io.quarkus.rest.client.reactive.runtime.QuarkusRestClientBuilderImpl; import io.quarkus.rest.client.reactive.runtime.RestClientBuilderImpl; +import io.vertx.core.http.HttpClientOptions; /** * This is the main entry point for creating a Type Safe Quarkus Rest Client. @@ -233,6 +234,22 @@ static QuarkusRestClientBuilder newBuilder() { */ QuarkusRestClientBuilder clientHeadersFactory(ClientHeadersFactory clientHeadersFactory); + /** + * Specifies the HTTP client options to use. + * + * @param httpClientOptionsClass the HTTP client options to use. + * @return the current builder + */ + QuarkusRestClientBuilder httpClientOptions(Class httpClientOptionsClass); + + /** + * Specifies the HTTP client options to use. + * + * @param httpClientOptions the HTTP client options to use. + * @return the current builder + */ + QuarkusRestClientBuilder httpClientOptions(HttpClientOptions httpClientOptions); + /** * Based on the configured QuarkusRestClientBuilder, creates a new instance of the given REST interface to invoke API calls * against. diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java index f901090f2f652b..81062447ce18aa 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/QuarkusRestClientBuilderImpl.java @@ -17,6 +17,8 @@ import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.rest.client.reactive.runtime.context.ClientHeadersFactoryContextResolver; +import io.quarkus.rest.client.reactive.runtime.context.HttpClientOptionsContextResolver; +import io.vertx.core.http.HttpClientOptions; public class QuarkusRestClientBuilderImpl implements QuarkusRestClientBuilder { @@ -198,6 +200,23 @@ public QuarkusRestClientBuilder clientHeadersFactory(ClientHeadersFactory client return this; } + @Override + public QuarkusRestClientBuilder httpClientOptions(Class httpClientOptionsClass) { + HttpClientOptions bean = BeanGrabber.getBeanIfDefined(httpClientOptionsClass); + if (bean == null) { + throw new IllegalArgumentException("Failed to instantiate the HTTP client options " + httpClientOptionsClass + + ". Make sure the bean is properly configured for CDI injection."); + } + + return httpClientOptions(bean); + } + + @Override + public QuarkusRestClientBuilder httpClientOptions(HttpClientOptions httpClientOptions) { + proxy.register(new HttpClientOptionsContextResolver(httpClientOptions)); + return this; + } + @Override public T build(Class clazz) throws IllegalStateException, RestClientDefinitionException { return proxy.build(clazz); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/HttpClientOptionsContextResolver.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/HttpClientOptionsContextResolver.java new file mode 100644 index 00000000000000..c3765ce1a71829 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/context/HttpClientOptionsContextResolver.java @@ -0,0 +1,23 @@ +package io.quarkus.rest.client.reactive.runtime.context; + +import jakarta.ws.rs.ext.ContextResolver; + +import io.vertx.core.http.HttpClientOptions; + +public class HttpClientOptionsContextResolver implements ContextResolver { + + private final HttpClientOptions component; + + public HttpClientOptionsContextResolver(HttpClientOptions component) { + this.component = component; + } + + @Override + public HttpClientOptions getContext(Class wantedClass) { + if (wantedClass.equals(HttpClientOptions.class)) { + return component; + } + + return null; + } +} From a13ef46f81b7f147b569d5c4df8e0aa85773daac Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 09:13:24 +0200 Subject: [PATCH 145/333] Replace deprecated classAnnotation with declaredAnnotation --- .../dev/testing/JunitTestRunner.java | 6 +++--- .../deployment/AmazonLambdaProcessor.java | 2 +- .../CustomScopeAnnotationsBuildItem.java | 2 +- .../deployment/LookupConditionsProcessor.java | 4 ++-- .../ReflectiveBeanClassesProcessor.java | 2 +- .../cache/deployment/CacheProcessor.java | 2 +- .../deployment/FunctionScannerBuildStep.java | 2 +- .../grpc/deployment/GrpcInterceptors.java | 2 +- .../grpc/deployment/GrpcServerProcessor.java | 6 +++--- .../orm/deployment/HibernateOrmProcessor.java | 8 ++++---- .../orm/deployment/JpaJandexScavenger.java | 2 +- .../orm/HibernateOrmAnnotationsTest.java | 2 +- .../binder/mpmetrics/AnnotationHandler.java | 2 +- .../ResourcePropertiesProvider.java | 4 ++-- .../deployment/ReactiveRoutesProcessor.java | 2 +- .../deployment/RestClientProcessor.java | 4 ++-- .../deployment/ResteasyCommonProcessor.java | 2 +- .../ResteasyServerCommonProcessor.java | 10 +++++----- .../JaxrsClientReactiveProcessor.java | 6 +++--- .../ResteasyReactiveJacksonProcessor.java | 2 +- .../deployment/ResteasyReactiveProcessor.java | 2 +- .../ResteasyReactiveScanningProcessor.java | 2 +- .../MicroProfileRestClientEnricher.java | 6 +++--- .../RestClientReactiveProcessor.java | 6 +++--- .../deployment/SmallRyeHealthProcessor.java | 2 +- .../deployment/SmallRyeOpenApiProcessor.java | 2 +- .../deployment/WiringHelper.java | 2 +- .../data/deployment/MethodNameParser.java | 6 +++--- .../deployment/generate/GenerationUtil.java | 4 ++-- .../generate/SpringDataRepositoryCreator.java | 2 +- .../ResourcePropertiesProvider.java | 8 ++++---- .../di/deployment/SpringDIProcessor.java | 12 +++++------ .../deployment/SpringSecurityProcessor.java | 2 +- .../ResponseStatusOnExceptionGenerator.java | 3 ++- .../SpringWebResteasyClassicProcessor.java | 4 ++-- .../SpringWebResteasyReactiveProcessor.java | 4 ++-- .../deployment/UndertowBuildStep.java | 2 +- .../common/processor/NameBindingUtil.java | 2 +- .../scanning/ApplicationScanningResult.java | 2 +- .../ResteasyReactiveInterceptorScanner.java | 6 +++--- .../scanning/ResteasyReactiveScanner.java | 20 ++++++++++--------- .../ResteasyReactiveDeploymentManager.java | 2 +- .../generation/filters/FilterGeneration.java | 4 ++-- .../scanning/CacheControlScanner.java | 4 ++-- ...esteasyReactiveContextResolverScanner.java | 2 +- ...steasyReactiveExceptionMappingScanner.java | 2 +- ...ResteasyReactiveParamConverterScanner.java | 2 +- 47 files changed, 95 insertions(+), 92 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java index 3391d59f0494dd..c2375688871855 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java @@ -581,7 +581,7 @@ private DiscoveryResult discoverTestClasses() { if (instance.target().kind() == AnnotationTarget.Kind.METHOD) { ClassInfo classInfo = instance.target().asMethod().declaringClass(); allTestClasses.add(classInfo.name()); - if (classInfo.classAnnotation(NESTED) != null) { + if (classInfo.declaredAnnotation(NESTED) != null) { var enclosing = classInfo.enclosingClass(); if (enclosing != null) { enclosingClasses.put(classInfo.name(), enclosing); @@ -590,7 +590,7 @@ private DiscoveryResult discoverTestClasses() { } else if (instance.target().kind() == AnnotationTarget.Kind.FIELD) { ClassInfo classInfo = instance.target().asField().declaringClass(); allTestClasses.add(classInfo.name()); - if (classInfo.classAnnotation(NESTED) != null) { + if (classInfo.declaredAnnotation(NESTED) != null) { var enclosing = classInfo.enclosingClass(); if (enclosing != null) { enclosingClasses.put(classInfo.name(), enclosing); @@ -639,7 +639,7 @@ private DiscoveryResult discoverTestClasses() { @Override public String apply(Class aClass) { ClassInfo def = index.getClassByName(DotName.createSimple(aClass.getName())); - AnnotationInstance testProfile = def.classAnnotation(TEST_PROFILE); + AnnotationInstance testProfile = def.declaredAnnotation(TEST_PROFILE); if (testProfile == null) { return "$$" + aClass.getName(); } diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java index 09085bcd335cf7..7463692b17d42d 100644 --- a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java @@ -103,7 +103,7 @@ List discover(CombinedIndexBuildItem combinedIndexBuildIt .produce(ReflectiveClassBuildItem.builder(lambda).methods().build()); String cdiName = null; - AnnotationInstance named = info.classAnnotation(NAMED); + AnnotationInstance named = info.declaredAnnotation(NAMED); if (named != null) { cdiName = named.value().asString(); } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CustomScopeAnnotationsBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CustomScopeAnnotationsBuildItem.java index 4c138604980caa..07f7ddd0bba700 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CustomScopeAnnotationsBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/CustomScopeAnnotationsBuildItem.java @@ -43,7 +43,7 @@ public Collection getCustomScopeNames() { */ public boolean isCustomScopeDeclaredOn(ClassInfo clazz) { for (DotName scope : customScopeNames) { - if (clazz.classAnnotation(scope) != null) { + if (clazz.declaredAnnotation(scope) != null) { return true; } } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java index d2f66dccebc8c7..8c9ea6f1ba07f1 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LookupConditionsProcessor.java @@ -103,8 +103,8 @@ List findAnnotations(AnnotationTarget target, DotName annota AnnotationInstance container; switch (target.kind()) { case CLASS: - annotation = target.asClass().classAnnotation(annotationName); - container = target.asClass().classAnnotation(containingAnnotationName); + annotation = target.asClass().declaredAnnotation(annotationName); + container = target.asClass().declaredAnnotation(containingAnnotationName); break; case FIELD: annotation = target.asField().annotation(annotationName); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ReflectiveBeanClassesProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ReflectiveBeanClassesProcessor.java index 9af9059e1cec4e..358fb65da66cfd 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ReflectiveBeanClassesProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ReflectiveBeanClassesProcessor.java @@ -19,7 +19,7 @@ void implicitReflectiveBeanClasses(BuildProducer r for (BeanInfo classBean : beanDiscoveryFinished.beanStream().classBeans()) { ClassInfo beanClass = classBean.getTarget().get().asClass(); - AnnotationInstance annotation = beanClass.classAnnotation(registerForReflection); + AnnotationInstance annotation = beanClass.declaredAnnotation(registerForReflection); if (annotation != null) { Type[] targets = annotation.value("targets") != null ? annotation.value("targets").asClassArray() : new Type[] {}; diff --git a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java index 020f15d8b51307..53502e49760024 100644 --- a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java +++ b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java @@ -140,7 +140,7 @@ void validateCacheAnnotationsAndProduceCacheNames(CombinedIndexBuildItem combine */ if (container.target().kind() == METHOD) { MethodInfo methodInfo = container.target().asMethod(); - if (methodInfo.declaringClass().classAnnotation(REGISTER_REST_CLIENT) != null) { + if (methodInfo.declaringClass().declaredAnnotation(REGISTER_REST_CLIENT) != null) { throwables.add(new UnsupportedRepeatedAnnotationException(methodInfo)); } } diff --git a/extensions/funqy/funqy-server-common/deployment/src/main/java/io/quarkus/funqy/deployment/FunctionScannerBuildStep.java b/extensions/funqy/funqy-server-common/deployment/src/main/java/io/quarkus/funqy/deployment/FunctionScannerBuildStep.java index 7d01b12a7e0601..344c3de220eddc 100644 --- a/extensions/funqy/funqy-server-common/deployment/src/main/java/io/quarkus/funqy/deployment/FunctionScannerBuildStep.java +++ b/extensions/funqy/funqy-server-common/deployment/src/main/java/io/quarkus/funqy/deployment/FunctionScannerBuildStep.java @@ -137,7 +137,7 @@ public void transform(TransformationContext transformationContext) { } Transformation transformation = transformationContext.transform(); transformation.add(BuiltinScope.DEPENDENT.getName()); - if (clazz.classAnnotation(DotNames.TYPED) == null) { + if (clazz.declaredAnnotation(DotNames.TYPED) == null) { // Add @Typed(MySubresource.class) transformation.add(createTypedAnnotationInstance(clazz)); } diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcInterceptors.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcInterceptors.java index 0722cf9d44598c..8b436cae4e1ca4 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcInterceptors.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcInterceptors.java @@ -36,7 +36,7 @@ static GrpcInterceptors gatherInterceptors(IndexView index, DotName interceptorI || Modifier.isInterface(interceptorImplClass.flags())) { continue; } - if (interceptorImplClass.classAnnotation(GLOBAL_INTERCEPTOR) == null) { + if (interceptorImplClass.declaredAnnotation(GLOBAL_INTERCEPTOR) == null) { nonGlobalInterceptors.add(interceptorImplClass.name().toString()); } else { globalInterceptors.add(interceptorImplClass.name().toString()); diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java index ff3792f1e293e9..e216ad7fa2bf92 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java @@ -134,7 +134,7 @@ void processGeneratedBeans(CombinedIndexBuildItem index, BuildProducer classes, String methodNa BlockingMode methodMode = BlockingMode.UNDEFINED; for (int i = 0; i < classes.size(); i++) { ClassInfo ci = classes.get(i); - Predicate annotationOnClass = annotationName -> ci.classAnnotation(annotationName) != null; + Predicate annotationOnClass = annotationName -> ci.declaredAnnotation(annotationName) != null; MethodInfo method = ci.method(methodName, methodArgs); classModeInherited = inheritedBlockingMode(annotationOnClass, classModeInherited); @@ -334,7 +334,7 @@ private static boolean methodIsBlocking(List classes, String methodNa if (methodMode == BlockingMode.UNDEFINED) { for (i++; i < classes.size(); i++) { ClassInfo ci2 = classes.get(i); - annotationOnClass = annotationName -> ci2.classAnnotation(annotationName) != null; + annotationOnClass = annotationName -> ci2.declaredAnnotation(annotationName) != null; classModeInherited = inheritedBlockingMode(annotationOnClass, classModeInherited); } } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index a591aa91d221fa..2c994a2cf0f53f 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -1329,8 +1329,8 @@ public static Map> getModelClassesAndPackagesPerPersistenceU Set relatedModelClassNames = getRelatedModelClassNames(index, jpaModel.getAllModelClassNames(), modelClassInfo); - if (modelClassInfo != null && (modelClassInfo.classAnnotation(ClassNames.QUARKUS_PERSISTENCE_UNIT) != null - || modelClassInfo.classAnnotation(ClassNames.QUARKUS_PERSISTENCE_UNIT_REPEATABLE_CONTAINER) != null)) { + if (modelClassInfo != null && (modelClassInfo.declaredAnnotation(ClassNames.QUARKUS_PERSISTENCE_UNIT) != null + || modelClassInfo.declaredAnnotation(ClassNames.QUARKUS_PERSISTENCE_UNIT_REPEATABLE_CONTAINER) != null)) { modelClassesWithPersistenceUnitAnnotations.add(modelClassInfo.name().toString()); } @@ -1390,8 +1390,8 @@ private static Set getRelatedModelClassNames(IndexView index, Set relatedModelClassNames = new HashSet<>(); // for now we only deal with entities and mapped super classes - if (modelClassInfo.classAnnotation(ClassNames.JPA_ENTITY) == null && - modelClassInfo.classAnnotation(ClassNames.MAPPED_SUPERCLASS) == null) { + if (modelClassInfo.declaredAnnotation(ClassNames.JPA_ENTITY) == null && + modelClassInfo.declaredAnnotation(ClassNames.MAPPED_SUPERCLASS) == null) { return Collections.emptySet(); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java index 05c97b7fe85e72..004eea2b6af15c 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java @@ -480,7 +480,7 @@ private static void collectPackage(Collector collector, ClassInfo classOrPackage private static void collectModelType(Collector collector, ClassInfo modelClass) { String name = modelClass.name().toString(); collector.modelTypes.add(name); - if (modelClass.classAnnotation(ClassNames.JPA_ENTITY) != null) { + if (modelClass.declaredAnnotation(ClassNames.JPA_ENTITY) != null) { collector.entityTypes.add(name); } } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java index 03a1fa4e4acc07..397b417c66da6f 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateOrmAnnotationsTest.java @@ -139,7 +139,7 @@ private Set findClassesWithMethodsAnnotatedWith(Index index, DotName an } private boolean allowsTargetType(ClassInfo annotation, ElementType targetType) { - AnnotationInstance targetAnnotation = annotation.classAnnotation(TARGET); + AnnotationInstance targetAnnotation = annotation.declaredAnnotation(TARGET); if (targetAnnotation == null) { // Can target anything return true; diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java index 435b94757b4dd7..37bdc49a2ffca3 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java @@ -52,7 +52,7 @@ public void transform(TransformationContext ctx) { } else if (ctx.isClass()) { classInfo = target.asClass(); // skip @Interceptor - if (target.asClass().classAnnotation(DotNames.INTERCEPTOR) != null) { + if (target.asClass().declaredAnnotation(DotNames.INTERCEPTOR) != null) { return; } } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java index b788844076289f..7c3236ef506f2a 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java @@ -76,8 +76,8 @@ private AnnotationInstance findResourcePropertiesAnnotation(DotName className) { if (classInfo == null) { return null; } - if (classInfo.classAnnotation(RESOURCE_PROPERTIES_ANNOTATION) != null) { - return classInfo.classAnnotation(RESOURCE_PROPERTIES_ANNOTATION); + if (classInfo.declaredAnnotation(RESOURCE_PROPERTIES_ANNOTATION) != null) { + return classInfo.declaredAnnotation(RESOURCE_PROPERTIES_ANNOTATION); } if (classInfo.superName() != null) { return findResourcePropertiesAnnotation(classInfo.superName()); diff --git a/extensions/reactive-routes/deployment/src/main/java/io/quarkus/vertx/web/deployment/ReactiveRoutesProcessor.java b/extensions/reactive-routes/deployment/src/main/java/io/quarkus/vertx/web/deployment/ReactiveRoutesProcessor.java index e39e5c3ec85c3b..3f6872a54ac88c 100644 --- a/extensions/reactive-routes/deployment/src/main/java/io/quarkus/vertx/web/deployment/ReactiveRoutesProcessor.java +++ b/extensions/reactive-routes/deployment/src/main/java/io/quarkus/vertx/web/deployment/ReactiveRoutesProcessor.java @@ -180,7 +180,7 @@ void validateBeanDeployment( // NOTE: inherited business methods are not taken into account ClassInfo beanClass = bean.getTarget().get().asClass(); AnnotationInstance routeBaseAnnotation = beanClass - .classAnnotation(DotNames.ROUTE_BASE); + .declaredAnnotation(DotNames.ROUTE_BASE); for (MethodInfo method : beanClass.methods()) { if (method.isSynthetic() || Modifier.isStatic(method.flags()) || method.name().equals("")) { continue; diff --git a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java index a4897469c29af1..77d719dc3ae746 100644 --- a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java +++ b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java @@ -333,7 +333,7 @@ private void warnAboutNotWorkingFeaturesInNative(PackageConfig packageConfig, Ma } Set dotNames = new HashSet<>(); for (ClassInfo interfaze : interfaces.values()) { - if (interfaze.classAnnotation(CLIENT_HEADER_PARAM) != null) { + if (interfaze.declaredAnnotation(CLIENT_HEADER_PARAM) != null) { boolean hasDefault = false; for (MethodInfo method : interfaze.methods()) { if (isDefault(method.flags())) { @@ -448,7 +448,7 @@ private ScopeInfo computeDefaultScope(Capabilities capabilities, Config config, } private String getAnnotationParameter(ClassInfo classInfo, String parameterName) { - AnnotationInstance instance = classInfo.classAnnotation(REGISTER_REST_CLIENT); + AnnotationInstance instance = classInfo.declaredAnnotation(REGISTER_REST_CLIENT); if (instance == null) { return ""; } diff --git a/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java b/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java index 9d4cc06aa1b131..de8247f62696b0 100644 --- a/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java +++ b/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java @@ -553,7 +553,7 @@ private static boolean collectDeclaredProvidersForMethodAndMediaTypeAnnotation(S if (mediaTypeMethodAnnotationInstance == null) { // no media types defined on the method, let's consider the class annotations AnnotationInstance mediaTypeClassAnnotationInstance = methodTarget.declaringClass() - .classAnnotation(mediaTypeAnnotation); + .declaredAnnotation(mediaTypeAnnotation); if (mediaTypeClassAnnotationInstance != null) { AnnotationValue mediaTypeClassValue = mediaTypeClassAnnotationInstance.value(); if ((mediaTypeClassValue != null) diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java index dd7cbc6f709cb7..daa4efb6514c33 100644 --- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java +++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -428,9 +428,9 @@ public boolean appliesTo(Kind kind) { @Override public void transform(TransformationContext context) { ClassInfo clazz = context.getTarget().asClass(); - if (clazz.classAnnotation(ResteasyDotNames.PATH) != null) { + if (clazz.declaredAnnotation(ResteasyDotNames.PATH) != null) { // Root resources - no need to add scope, @Path is a bean defining annotation - if (clazz.classAnnotation(DotNames.TYPED) == null) { + if (clazz.declaredAnnotation(DotNames.TYPED) == null) { // Add @Typed(MyResource.class) context.transform().add(createTypedAnnotationInstance(clazz)).done(); } @@ -440,14 +440,14 @@ public void transform(TransformationContext context) { // Skip classes annotated with built-in scope return; } - if (clazz.classAnnotation(ResteasyDotNames.PROVIDER) != null) { + if (clazz.declaredAnnotation(ResteasyDotNames.PROVIDER) != null) { Transformation transformation = null; if (clazz.annotationsMap().containsKey(DotNames.INJECT) || hasAutoInjectAnnotation(autoInjectAnnotationNames, clazz)) { // A provider with an injection point but no built-in scope is @Singleton transformation = context.transform().add(BuiltinScope.SINGLETON.getName()); } - if (clazz.classAnnotation(DotNames.TYPED) == null) { + if (clazz.declaredAnnotation(DotNames.TYPED) == null) { // Add @Typed(MyProvider.class) if (transformation == null) { transformation = context.transform(); @@ -462,7 +462,7 @@ public void transform(TransformationContext context) { Transformation transformation = context.transform() .add(resteasyConfig.singletonResources ? BuiltinScope.SINGLETON.getName() : BuiltinScope.DEPENDENT.getName()); - if (clazz.classAnnotation(DotNames.TYPED) == null) { + if (clazz.declaredAnnotation(DotNames.TYPED) == null) { // Add @Typed(MySubresource.class) transformation.add(createTypedAnnotationInstance(clazz)); } diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 9e410f88ad7487..4887dd1ae1a9a7 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -896,7 +896,7 @@ A more full example of generated client (with sub-resource) can is at the bottom Map invocationBuilderEnrichers = new HashMap<>(); String[] consumes = extractProducesConsumesValues( - jandexMethod.declaringClass().classAnnotation(CONSUMES), method.getConsumes()); + jandexMethod.declaringClass().declaredAnnotation(CONSUMES), method.getConsumes()); boolean multipart = isMultipart(consumes, method.getParameters()); AssignableResultHandle formParams = null; @@ -1274,7 +1274,7 @@ private void handleSubResourceMethod(List + ". It may have unresolved parameter types (generics)")); subMethodIndex++; String[] consumes = extractProducesConsumesValues( - jandexSubMethod.declaringClass().classAnnotation(CONSUMES), method.getConsumes()); + jandexSubMethod.declaringClass().declaredAnnotation(CONSUMES), method.getConsumes()); consumes = extractProducesConsumesValues(jandexSubMethod.annotation(CONSUMES), consumes); boolean multipart = isMultipart(consumes, subMethod.getParameters()); @@ -1916,7 +1916,7 @@ private String getHttpMethod(MethodInfo subMethod, String defaultMethod, Map annoToMethod : httpAnnotationToMethod.entrySet()) { - if (subMethod.declaringClass().classAnnotation(annoToMethod.getKey()) != null) { + if (subMethod.declaringClass().declaredAnnotation(annoToMethod.getKey()) != null) { return annoToMethod.getValue(); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index 4c272bf7316acf..679f4a3fbc915b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -279,7 +279,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r if (methodInfo.hasAnnotation(DISABLE_SECURE_SERIALIZATION)) { continue; } - if (entry.getActualClassInfo().classAnnotation(DISABLE_SECURE_SERIALIZATION) != null) { + if (entry.getActualClassInfo().declaredAnnotation(DISABLE_SECURE_SERIALIZATION) != null) { if (!methodInfo.hasAnnotation(ENABLE_SECURE_SERIALIZATION)) { continue; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 7d4574abb0d995..93eceec1e3b36b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -1560,7 +1560,7 @@ private String determineApplicationPath(ApplicationScanningResult appResult, return defaultPath.orElse("/"); } AnnotationInstance applicationPathValue = appResult.getSelectedAppClass() - .classAnnotation(ResteasyReactiveDotNames.APPLICATION_PATH); + .declaredAnnotation(ResteasyReactiveDotNames.APPLICATION_PATH); if (applicationPathValue == null) { return defaultPath.orElse("/"); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java index 2d4385c2579724..bd97894704a7fe 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java @@ -295,7 +295,7 @@ public void scanForParamConverters(CombinedIndexBuildItem combinedIndexBuildItem ApplicationScanningResult.KeepProviderResult keepProviderResult = applicationResultBuildItem.getResult() .keepProvider(converterClass); if (keepProviderResult != ApplicationScanningResult.KeepProviderResult.DISCARD) { - AnnotationInstance priorityInstance = converterClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + AnnotationInstance priorityInstance = converterClass.declaredAnnotation(ResteasyReactiveDotNames.PRIORITY); paramConverterBuildItemBuildProducer.produce(new ParamConverterBuildItem(converterClass.name().toString(), priorityInstance != null ? priorityInstance.value().asInt() : Priorities.USER, true)); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java index d3e9e0188c80b5..5f22e57c186b33 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java @@ -134,7 +134,7 @@ public void forClass(MethodCreator constructor, AssignableResultHandle webTarget ResultHandle clientHeadersFactory = null; - AnnotationInstance registerClientHeaders = interfaceClass.classAnnotation(REGISTER_CLIENT_HEADERS); + AnnotationInstance registerClientHeaders = interfaceClass.declaredAnnotation(REGISTER_CLIENT_HEADERS); if (registerClientHeaders != null) { String headersFactoryClass = registerClientHeaders.valueWithDefault(index) @@ -475,14 +475,14 @@ private void createAndReturnHeaderFiller(ClassCreator classCreator, MethodCreato private void collectHeaderFillers(ClassInfo interfaceClass, MethodInfo method, Map headerFillersByName) { - AnnotationInstance classLevelHeader = interfaceClass.classAnnotation(CLIENT_HEADER_PARAM); + AnnotationInstance classLevelHeader = interfaceClass.declaredAnnotation(CLIENT_HEADER_PARAM); if (classLevelHeader != null) { headerFillersByName.put(classLevelHeader.value("name").asString(), new HeaderData(classLevelHeader, interfaceClass)); } putAllHeaderAnnotations(headerFillersByName, interfaceClass, - extractAnnotations(interfaceClass.classAnnotation(CLIENT_HEADER_PARAMS))); + extractAnnotations(interfaceClass.declaredAnnotation(CLIENT_HEADER_PARAMS))); Map methodLevelHeadersByName = new HashMap<>(); AnnotationInstance methodLevelHeader = method.annotation(CLIENT_HEADER_PARAM); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index d456cbea6cdb46..0af9df3ca2dcb9 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -293,7 +293,7 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, // ignore providers annotated with `@ConstrainedTo(SERVER)` AnnotationInstance constrainedToInstance = providerClass - .classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); + .declaredAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); if (constrainedToInstance != null) { if (RuntimeType.valueOf(constrainedToInstance.value().asEnum()) == RuntimeType.SERVER) { continue; @@ -619,7 +619,7 @@ private int getAnnotatedPriority(IndexView index, String className, int defaultP log.warnv("Unindexed provider class {0}. The priority of the provider will be set to {1}. ", className, defaultPriority); } else { - AnnotationInstance priorityAnnoOnProvider = providerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + AnnotationInstance priorityAnnoOnProvider = providerClass.declaredAnnotation(ResteasyReactiveDotNames.PRIORITY); if (priorityAnnoOnProvider != null) { priority = priorityAnnoOnProvider.value().asInt(); } @@ -631,7 +631,7 @@ private int getAnnotatedPriority(IndexView index, String className, int defaultP // In order to avoid the extra complexity of having to deal with this mode, we simply fail the build when this situation is encountered // and provide an actionable error message on how to remedy the situation. private void validateKotlinDefaultMethods(ClassInfo jaxrsInterface, IndexView index) { - if (jaxrsInterface.classAnnotation(KOTLIN_METADATA_ANNOTATION) != null) { + if (jaxrsInterface.declaredAnnotation(KOTLIN_METADATA_ANNOTATION) != null) { var potentialDefaultImplClass = DotName .createSimple(jaxrsInterface.name().toString() + KOTLIN_INTERFACE_DEFAULT_IMPL_SUFFIX); if (index.getClassByName(potentialDefaultImplClass) != null) { diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java index 450d6ba69bed3e..26f570bb4b7000 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java @@ -317,7 +317,7 @@ private void warnIfJaxRsPathUsed(IndexView index, DotName healthAnnotation) { AnnotationTarget target = instance.target(); if (target.kind() == Kind.CLASS) { - if (target.asClass().classAnnotation(JAX_RS_PATH) != null) { + if (target.asClass().declaredAnnotation(JAX_RS_PATH) != null) { containsPath = true; } } else if (target.kind() == Kind.METHOD) { diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index b5d09571f7ed3b..9ccfa58654fa51 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -595,7 +595,7 @@ void addMethodImplementationClassNames(MethodInfo method, Type[] params, Collect private boolean isValidOpenAPIMethodForAutoAdd(MethodInfo method, DotName securityRequirement) { return isOpenAPIEndpoint(method) && !method.hasAnnotation(securityRequirement) - && method.declaringClass().classAnnotation(securityRequirement) == null; + && method.declaringClass().declaredAnnotation(securityRequirement) == null; } @BuildStep diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java index c09bffb28ef621..9da83cf47769a1 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java @@ -139,7 +139,7 @@ static List getConnectorAttributes(BeanInfo bi, CombinedInde .classAnnotationsWithRepeatable(ReactiveMessagingDotNames.CONNECTOR_ATTRIBUTES, index.getIndex()) .stream().flatMap(ai -> Arrays.stream(ai.value().asNestedArray())).collect(Collectors.toList()); if (attributes.isEmpty()) { - AnnotationInstance attribute = bi.getImplClazz().classAnnotation(ReactiveMessagingDotNames.CONNECTOR_ATTRIBUTE); + AnnotationInstance attribute = bi.getImplClazz().declaredAnnotation(ReactiveMessagingDotNames.CONNECTOR_ATTRIBUTE); if (attribute != null) { attributes = Collections.singletonList(attribute); } diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java index c5886556091ddb..29e0131fb2b9f2 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java @@ -504,7 +504,7 @@ private String lowerFirstLetter(String input) { } private String getEntityName() { - AnnotationInstance annotationInstance = entityClass.classAnnotation(DotNames.JPA_ENTITY); + AnnotationInstance annotationInstance = entityClass.declaredAnnotation(DotNames.JPA_ENTITY); if (annotationInstance != null && annotationInstance.value("name") != null) { AnnotationValue annotationValue = annotationInstance.value("name"); return annotationValue.asString().length() > 0 ? annotationValue.asString() : entityClass.simpleName(); @@ -548,9 +548,9 @@ private List getSuperClassInfos(IndexView indexView, ClassInfo entity Type superClassType = entityClass.superClassType(); while (superClassType != null && !superClassType.name().equals(DotNames.OBJECT)) { ClassInfo superClass = indexView.getClassByName(superClassType.name()); - if (superClass.classAnnotation(DotNames.JPA_MAPPED_SUPERCLASS) != null) { + if (superClass.declaredAnnotation(DotNames.JPA_MAPPED_SUPERCLASS) != null) { mappedSuperClassInfoElements.add(superClass); - } else if (superClass.classAnnotation(DotNames.JPA_INHERITANCE) != null) { + } else if (superClass.declaredAnnotation(DotNames.JPA_INHERITANCE) != null) { mappedSuperClassInfoElements.add(superClass); } diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/GenerationUtil.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/GenerationUtil.java index f85c020a2b950c..a6c36e801fa779 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/GenerationUtil.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/GenerationUtil.java @@ -81,7 +81,7 @@ static AnnotationInstance getNamedQueryForMethod(MethodInfo methodInfo, ClassInf private static AnnotationInstance getNamedQueryAnnotationForMethod(MethodInfo methodInfo, ClassInfo entityClassInfo) { String methodName = methodInfo.name(); - AnnotationInstance namedQueryAnnotation = entityClassInfo.classAnnotation(JPA_NAMED_QUERY); + AnnotationInstance namedQueryAnnotation = entityClassInfo.declaredAnnotation(JPA_NAMED_QUERY); if (namedQueryAnnotation != null && isMethodDeclaredInNamedQuery(entityClassInfo, methodName, namedQueryAnnotation)) { return namedQueryAnnotation; } @@ -91,7 +91,7 @@ private static AnnotationInstance getNamedQueryAnnotationForMethod(MethodInfo me private static AnnotationInstance getNamedQueriesAnnotationForMethod(MethodInfo methodInfo, ClassInfo entityClassInfo) { String methodName = methodInfo.name(); - AnnotationInstance namedQueriesAnnotation = entityClassInfo.classAnnotation(JPA_NAMED_QUERIES); + AnnotationInstance namedQueriesAnnotation = entityClassInfo.declaredAnnotation(JPA_NAMED_QUERIES); if (namedQueriesAnnotation != null) { for (AnnotationValue annotationInstanceValues : namedQueriesAnnotation.values()) { for (AnnotationInstance annotationInstance : annotationInstanceValues.asNestedArray()) { diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java index a54aab834ac5b1..ebf95e67283d30 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java @@ -125,7 +125,7 @@ public Result implementCrudRepository(ClassInfo repositoryToImplement, IndexView private Map.Entry extractIdAndEntityTypes(ClassInfo repositoryToImplement, IndexView indexView) { AnnotationInstance repositoryDefinitionInstance = repositoryToImplement - .classAnnotation(DotNames.SPRING_DATA_REPOSITORY_DEFINITION); + .declaredAnnotation(DotNames.SPRING_DATA_REPOSITORY_DEFINITION); if (repositoryDefinitionInstance != null) { return new AbstractMap.SimpleEntry<>(repositoryDefinitionInstance.value("idClass").asClass().name(), repositoryDefinitionInstance.value("domainClass").asClass().name()); diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java index bbdf467979e7b7..1b20fe94b1a58a 100644 --- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java @@ -100,11 +100,11 @@ private AnnotationInstance findClassAnnotation(DotName interfaceName) { if (classInfo == null) { return null; } - if (classInfo.classAnnotation(REPOSITORY_REST_RESOURCE_ANNOTATION) != null) { - return classInfo.classAnnotation(REPOSITORY_REST_RESOURCE_ANNOTATION); + if (classInfo.declaredAnnotation(REPOSITORY_REST_RESOURCE_ANNOTATION) != null) { + return classInfo.declaredAnnotation(REPOSITORY_REST_RESOURCE_ANNOTATION); } - if (classInfo.classAnnotation(REST_RESOURCE_ANNOTATION) != null) { - return classInfo.classAnnotation(REST_RESOURCE_ANNOTATION); + if (classInfo.declaredAnnotation(REST_RESOURCE_ANNOTATION) != null) { + return classInfo.declaredAnnotation(REST_RESOURCE_ANNOTATION); } if (classInfo.superName() != null) { return findClassAnnotation(classInfo.superName()); diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index 948279b18d92de..d8f9fc8cb76c27 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -210,10 +210,10 @@ Map> getStereotypeScopes(final IndexView index) { private DotName getScope(final AnnotationTarget target) { AnnotationValue value = null; if (target.kind() == AnnotationTarget.Kind.CLASS) { - if (target.asClass().classAnnotation(SPRING_SCOPE_ANNOTATION) != null) { - value = target.asClass().classAnnotation(SPRING_SCOPE_ANNOTATION).value(); + if (target.asClass().declaredAnnotation(SPRING_SCOPE_ANNOTATION) != null) { + value = target.asClass().declaredAnnotation(SPRING_SCOPE_ANNOTATION).value(); if ((value == null) || value.asString().isEmpty()) { - value = target.asClass().classAnnotation(SPRING_SCOPE_ANNOTATION).value("scopeName"); + value = target.asClass().declaredAnnotation(SPRING_SCOPE_ANNOTATION).value("scopeName"); } } } else if (target.kind() == AnnotationTarget.Kind.METHOD) { @@ -323,13 +323,13 @@ Set getAnnotationsToAdd( scopes.addAll(scopeNames); } if (SPRING_STEREOTYPE_ANNOTATIONS.contains(clazzAnnotation) && !classInfo.isAnnotation()) { - names.add(getBeanNameFromStereotypeInstance(classInfo.classAnnotation(clazzAnnotation))); + names.add(getBeanNameFromStereotypeInstance(classInfo.declaredAnnotation(clazzAnnotation))); } } } DotName declaredScope = getScope(classInfo); // @Named is a bean-defining annotation in Spring, but not in Arc. - if (declaredScope == null && classInfo.classAnnotation(CDI_NAMED_ANNOTATION) != null) { + if (declaredScope == null && classInfo.declaredAnnotation(CDI_NAMED_ANNOTATION) != null) { declaredScope = CDI_SINGLETON_ANNOTATION; // implicit default scope in spring } final boolean isAnnotation = classInfo.isAnnotation(); @@ -406,7 +406,7 @@ Set getAnnotationsToAdd( } else if (target.kind() == AnnotationTarget.Kind.METHOD) { final MethodInfo methodInfo = target.asMethod(); if (methodInfo.hasAnnotation(BEAN_ANNOTATION) - && methodInfo.declaringClass().classAnnotation(CONFIGURATION_ANNOTATION) != null) { + && methodInfo.declaringClass().declaredAnnotation(CONFIGURATION_ANNOTATION) != null) { annotationsToAdd.add(create( CDI_PRODUCES_ANNOTATION, target, diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java index d12ec9b0a6cba4..70d16090e00908 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java @@ -234,7 +234,7 @@ void locatePreAuthorizedInstances( } MethodInfo methodInfo = instance.target().asMethod(); checksStandardSecurity(instance, methodInfo); - result.put(methodInfo, metaAnnotation.classAnnotation(DotNames.SPRING_PRE_AUTHORIZE)); + result.put(methodInfo, metaAnnotation.declaredAnnotation(DotNames.SPRING_PRE_AUTHORIZE)); classesInNeedOfAnnotationTransformation.add(methodInfo.declaringClass().name()); } } diff --git a/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseStatusOnExceptionGenerator.java b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseStatusOnExceptionGenerator.java index cf6394e19969ad..0cb16f6ea52eda 100644 --- a/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseStatusOnExceptionGenerator.java +++ b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseStatusOnExceptionGenerator.java @@ -19,7 +19,8 @@ class ResponseStatusOnExceptionGenerator extends AbstractExceptionMapperGenerato } void generateMethodBody(MethodCreator toResponse) { - ResultHandle status = toResponse.load(getHttpStatusFromAnnotation(exceptionClassInfo.classAnnotation(RESPONSE_STATUS))); + ResultHandle status = toResponse + .load(getHttpStatusFromAnnotation(exceptionClassInfo.declaredAnnotation(RESPONSE_STATUS))); ResultHandle responseBuilder = toResponse.invokeStaticMethod( MethodDescriptor.ofMethod(Response.class, "status", Response.ResponseBuilder.class, int.class), status); diff --git a/extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java b/extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java index a1af08f6bc7fa0..e30a406ab65344 100644 --- a/extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java +++ b/extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java @@ -148,7 +148,7 @@ private void validateControllers(BeanArchiveIndexBuildItem beanArchiveIndexBuild continue; } - if (targetClass.classAnnotation(REST_CONTROLLER_ANNOTATION) == null) { + if (targetClass.declaredAnnotation(REST_CONTROLLER_ANNOTATION) == null) { classesWithoutRestController.add(targetClass.name()); } } @@ -248,7 +248,7 @@ public void registerWithDevModeNotFoundMapper(BeanArchiveIndexBuildItem beanArch String basePath = "/"; ClassInfo restControllerAnnotatedClass = restControllerInstance.target().asClass(); - AnnotationInstance requestMappingInstance = restControllerAnnotatedClass.classAnnotation(REQUEST_MAPPING); + AnnotationInstance requestMappingInstance = restControllerAnnotatedClass.declaredAnnotation(REQUEST_MAPPING); if (requestMappingInstance != null) { String basePathFromAnnotation = getMappingValue(requestMappingInstance); if (basePathFromAnnotation != null) { diff --git a/extensions/spring-web/resteasy-reactive/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyReactiveProcessor.java b/extensions/spring-web/resteasy-reactive/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyReactiveProcessor.java index caf747df1bc900..e33a2ab086355c 100644 --- a/extensions/spring-web/resteasy-reactive/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyReactiveProcessor.java +++ b/extensions/spring-web/resteasy-reactive/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyReactiveProcessor.java @@ -112,7 +112,7 @@ public void registerAdditionalResourceClasses(CombinedIndexBuildItem index, .getAnnotations(REST_CONTROLLER_ANNOTATION)) { ClassInfo targetClass = restController.target().asClass(); additionalResourceClassProducer.produce(new AdditionalResourceClassBuildItem(targetClass, - getSinglePathOfInstance(targetClass.classAnnotation(REQUEST_MAPPING), ""))); + getSinglePathOfInstance(targetClass.declaredAnnotation(REQUEST_MAPPING), ""))); } } @@ -133,7 +133,7 @@ private void validateControllers(IndexView index) { continue; } - if (targetClass.classAnnotation(REST_CONTROLLER_ANNOTATION) == null) { + if (targetClass.declaredAnnotation(REST_CONTROLLER_ANNOTATION) == null) { classesWithoutRestController.add(targetClass.name()); } } diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index cb16d4b1a3b93e..06fd8dd5d71698 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -277,7 +277,7 @@ public List servletContainerInitializer( beans.produce(AdditionalBeanBuildItem.unremovableOf(initializer)); ClassInfo sci = combinedIndexBuildItem.getIndex().getClassByName(DotName.createSimple(initializer)); if (sci != null) { - AnnotationInstance handles = sci.classAnnotation(HANDLES_TYPES); + AnnotationInstance handles = sci.declaredAnnotation(HANDLES_TYPES); Set handledTypes = new HashSet<>(); if (handles != null) { Type[] types = handles.value().asClassArray(); diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/NameBindingUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/NameBindingUtil.java index 0b860c23b4cd2a..d545a2cd5cf658 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/NameBindingUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/NameBindingUtil.java @@ -54,7 +54,7 @@ private static Set nameBindingNames(IndexView index, Collection if (classAnnotation == null) { continue; } - if (classAnnotation.classAnnotation(NAME_BINDING) != null) { + if (classAnnotation.declaredAnnotation(NAME_BINDING) != null) { result.add(classAnnotation.name().toString()); } } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ApplicationScanningResult.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ApplicationScanningResult.java index a881ada5317a21..c7fd2a5579ac18 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ApplicationScanningResult.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ApplicationScanningResult.java @@ -45,7 +45,7 @@ public KeepProviderResult keepProvider(ClassInfo providerClass) { : KeepProviderResult.DISCARD; } } - return providerClass.classAnnotation(ResteasyReactiveDotNames.PROVIDER) != null ? KeepProviderResult.NORMAL + return providerClass.declaredAnnotation(ResteasyReactiveDotNames.PROVIDER) != null ? KeepProviderResult.NORMAL : KeepProviderResult.DISCARD; } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java index a0d59b90321dca..ccc0cbcca9eecc 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java @@ -201,16 +201,16 @@ private static ResourceInterceptor handleDiscoveredInterceptor( ResourceInterceptor interceptor = interceptorContainer.create(); interceptor.setClassName(filterClass.name().toString()); interceptor.setNameBindingNames(NameBindingUtil.nameBindingNames(index, filterClass)); - AnnotationInstance priorityInstance = filterClass.classAnnotation(PRIORITY); + AnnotationInstance priorityInstance = filterClass.declaredAnnotation(PRIORITY); if (priorityInstance != null) { interceptor.setPriority(priorityInstance.value().asInt()); } - AnnotationInstance nonBlockingInstance = filterClass.classAnnotation(NON_BLOCKING); + AnnotationInstance nonBlockingInstance = filterClass.declaredAnnotation(NON_BLOCKING); if (nonBlockingInstance != null) { interceptor.setNonBlockingRequired(true); } if (interceptorContainer instanceof PreMatchInterceptorContainer - && filterClass.classAnnotation(PRE_MATCHING) != null) { + && filterClass.declaredAnnotation(PRE_MATCHING) != null) { ((PreMatchInterceptorContainer) interceptorContainer).addPreMatchInterceptor(interceptor); } else { Set nameBindingNames = interceptor.getNameBindingNames(); diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java index 5598c9ef650875..772015b57e6b6d 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java @@ -116,13 +116,13 @@ public static ApplicationScanningResult scanForApplicationClass(IndexView index, | InvocationTargetException e) { throw new RuntimeException("Unable to handle class: " + applicationClass, e); } - if (applicationClassInfo.classAnnotation(ResteasyReactiveDotNames.BLOCKING) != null) { - if (applicationClassInfo.classAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) { + if (applicationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.BLOCKING) != null) { + if (applicationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) { throw new DeploymentException("JAX-RS Application class '" + applicationClassInfo.name() + "' contains both @Blocking and @NonBlocking annotations."); } blocking = BlockingDefault.BLOCKING; - } else if (applicationClassInfo.classAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) { + } else if (applicationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.NON_BLOCKING) != null) { blocking = BlockingDefault.NON_BLOCKING; } } @@ -153,16 +153,17 @@ public static SerializerScanningResult scanForSerializers(IndexView index, runtimeType = RuntimeType.SERVER; } List mediaTypeStrings = Collections.emptyList(); - AnnotationInstance consumesAnnotation = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSUMES); + AnnotationInstance consumesAnnotation = readerClass.declaredAnnotation(ResteasyReactiveDotNames.CONSUMES); if (consumesAnnotation != null) { mediaTypeStrings = Arrays.asList(consumesAnnotation.value().asStringArray()); } - AnnotationInstance constrainedToInstance = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); + AnnotationInstance constrainedToInstance = readerClass + .declaredAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); if (constrainedToInstance != null) { runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); } int priority = Priorities.USER; - AnnotationInstance priorityInstance = readerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + AnnotationInstance priorityInstance = readerClass.declaredAnnotation(ResteasyReactiveDotNames.PRIORITY); if (priorityInstance != null) { priority = priorityInstance.value().asInt(); } @@ -184,19 +185,20 @@ public static SerializerScanningResult scanForSerializers(IndexView index, runtimeType = RuntimeType.SERVER; } List mediaTypeStrings = Collections.emptyList(); - AnnotationInstance producesAnnotation = writerClass.classAnnotation(ResteasyReactiveDotNames.PRODUCES); + AnnotationInstance producesAnnotation = writerClass.declaredAnnotation(ResteasyReactiveDotNames.PRODUCES); if (producesAnnotation != null) { mediaTypeStrings = Arrays.asList(producesAnnotation.value().asStringArray()); } List typeParameters = JandexUtil.resolveTypeParameters(writerClass.name(), ResteasyReactiveDotNames.MESSAGE_BODY_WRITER, index); - AnnotationInstance constrainedToInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); + AnnotationInstance constrainedToInstance = writerClass + .declaredAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); if (constrainedToInstance != null) { runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); } int priority = Priorities.USER; - AnnotationInstance priorityInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + AnnotationInstance priorityInstance = writerClass.declaredAnnotation(ResteasyReactiveDotNames.PRIORITY); if (priorityInstance != null) { priority = priorityInstance.value().asInt(); } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ResteasyReactiveDeploymentManager.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ResteasyReactiveDeploymentManager.java index b93204e85a9c93..cae40343c58aab 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ResteasyReactiveDeploymentManager.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ResteasyReactiveDeploymentManager.java @@ -451,7 +451,7 @@ private String getApplicationPath() { String path = "/"; if (sa.applicationScanningResult.getSelectedAppClass() != null) { var pathAn = sa.applicationScanningResult.getSelectedAppClass() - .classAnnotation(ResteasyReactiveDotNames.APPLICATION_PATH); + .declaredAnnotation(ResteasyReactiveDotNames.APPLICATION_PATH); if (pathAn != null) { path = pathAn.value().asString(); if (!path.startsWith("/")) { diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java index 7b6508ecc7713d..2127ea5744b93a 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java @@ -81,7 +81,7 @@ public static List generate(IndexView index, Set unwra if (annotationClassInfo == null) { continue; } - if ((annotationClassInfo.classAnnotation(ResteasyReactiveDotNames.NAME_BINDING) != null)) { + if ((annotationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.NAME_BINDING) != null)) { nameBindingNames.add(annotationDotName.toString()); } } @@ -116,7 +116,7 @@ public static List generate(IndexView index, Set unwra if (annotationClassInfo == null) { continue; } - if ((annotationClassInfo.classAnnotation(ResteasyReactiveDotNames.NAME_BINDING) != null)) { + if ((annotationClassInfo.declaredAnnotation(ResteasyReactiveDotNames.NAME_BINDING) != null)) { nameBindingNames.add(annotationDotName.toString()); } } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java index 42515769cea4fd..f89391bb8764ed 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java @@ -39,7 +39,7 @@ public List scan(MethodInfo method, ClassInfo actualEndp } else { cacheControl = noCacheToCacheControl(annotationStore.getAnnotation(actualEndpointClass, NO_CACHE)); if (cacheControl != null) { - if (actualEndpointClass.classAnnotation(CACHE) != null) { + if (actualEndpointClass.declaredAnnotation(CACHE) != null) { throw new IllegalStateException( "A resource class cannot be simultaneously annotated with '@Cache' and '@NoCache'. Offending class is '" + actualEndpointClass.name() + "'"); @@ -52,7 +52,7 @@ public List scan(MethodInfo method, ClassInfo actualEndp if (cacheControl != null) { return cacheControlToCustomizerList(cacheControl); } else { - cacheControl = cacheToCacheControl(actualEndpointClass.classAnnotation(CACHE)); + cacheControl = cacheToCacheControl(actualEndpointClass.declaredAnnotation(CACHE)); if (cacheControl != null) { return cacheControlToCustomizerList(cacheControl); } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java index a97d8ac090ba6e..ca4301b990c025 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java @@ -70,7 +70,7 @@ public static ContextResolvers scanForContextResolvers(IndexView index, Applicat } private static List getProducesMediaTypes(ClassInfo classInfo) { - AnnotationInstance produces = classInfo.classAnnotation(ResteasyReactiveDotNames.PRODUCES); + AnnotationInstance produces = classInfo.declaredAnnotation(ResteasyReactiveDotNames.PRODUCES); if (produces == null) { return Collections.emptyList(); } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java index 4c982d005a34bf..9b8e3c50dd0ec1 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java @@ -53,7 +53,7 @@ public static ExceptionMapping scanForExceptionMappers(IndexView index, Applicat ResteasyReactiveDotNames.EXCEPTION_MAPPER, index); DotName handledExceptionDotName = typeParameters.get(0).name(); - AnnotationInstance priorityInstance = mapperClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + AnnotationInstance priorityInstance = mapperClass.declaredAnnotation(ResteasyReactiveDotNames.PRIORITY); int priority = Priorities.USER; if (priorityInstance != null) { priority = priorityInstance.value().asInt(); diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java index 2c6f8e26543966..99dbea7645957f 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java @@ -46,7 +46,7 @@ public static ParamConverterProviders scanForParamConverters(IndexView index, Ap for (ClassInfo converterClass : paramConverterProviders) { ApplicationScanningResult.KeepProviderResult keepProviderResult = result.keepProvider(converterClass); if (keepProviderResult != ApplicationScanningResult.KeepProviderResult.DISCARD) { - AnnotationInstance priorityInstance = converterClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + AnnotationInstance priorityInstance = converterClass.declaredAnnotation(ResteasyReactiveDotNames.PRIORITY); int priority = priorityInstance != null ? priorityInstance.value().asInt() : Priorities.USER; ResourceParamConverterProvider provider = new ResourceParamConverterProvider(); provider.setPriority(priority); From 5a4c0e9a32f8b1376c6f00c5b77d438ea29ae1be Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 09:30:34 +0200 Subject: [PATCH 146/333] Replace deprecated classAnnotations with declaredAnnotations --- .../java/io/quarkus/deployment/dev/ClassComparisonUtil.java | 2 +- .../io/quarkus/arc/deployment/UnremovableBeanBuildItem.java | 2 +- .../deployment/binder/mpmetrics/AnnotationHandler.java | 4 ++-- .../deployment/properties/ResourcePropertiesProvider.java | 2 +- .../io/quarkus/restclient/deployment/RestClientProcessor.java | 2 +- .../common/deployment/ResteasyServerCommonProcessor.java | 2 +- .../smallrye/openapi/deployment/RESTEasyExtension.java | 2 +- .../data/rest/deployment/ResourcePropertiesProvider.java | 2 +- .../io/quarkus/spring/di/deployment/SpringDIProcessor.java | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/ClassComparisonUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/ClassComparisonUtil.java index ff51f50bea7d03..a7f18fb788e720 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/ClassComparisonUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/ClassComparisonUtil.java @@ -29,7 +29,7 @@ static boolean isSameStructure(ClassInfo clazz, ClassInfo old) { if (!clazz.interfaceNames().equals(old.interfaceNames())) { return false; } - if (!compareAnnotations(clazz.classAnnotations(), old.classAnnotations())) { + if (!compareAnnotations(clazz.declaredAnnotations(), old.declaredAnnotations())) { return false; } if (old.fields().size() != clazz.fields().size()) { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java index 098b5115a60e24..5984271398bf10 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java @@ -172,7 +172,7 @@ public static UnremovableBeanBuildItem targetWithAnnotation(DotName annotationNa @Override public boolean test(BeanInfo bean) { if (bean.isClassBean()) { - return Annotations.contains(bean.getTarget().get().asClass().classAnnotations(), annotationName); + return Annotations.contains(bean.getTarget().get().asClass().declaredAnnotations(), annotationName); } else if (bean.isProducerMethod()) { return !getAnnotations(Kind.METHOD, annotationName, bean.getTarget().get().asMethod().annotations()) .isEmpty(); diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java index 37bdc49a2ffca3..1645ffbe59ef5e 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/mpmetrics/AnnotationHandler.java @@ -84,8 +84,8 @@ static boolean removeCountedWhenTimed(DotName sourceAnnotation, AnnotationTarget MethodInfo methodInfo) { if (MetricDotNames.COUNTED_ANNOTATION.equals(sourceAnnotation)) { if (methodInfo == null) { - if (!Annotations.contains(classInfo.classAnnotations(), MetricDotNames.TIMED_ANNOTATION) && - !Annotations.contains(classInfo.classAnnotations(), MetricDotNames.SIMPLY_TIMED_ANNOTATION)) { + if (!Annotations.contains(classInfo.declaredAnnotations(), MetricDotNames.TIMED_ANNOTATION) && + !Annotations.contains(classInfo.declaredAnnotations(), MetricDotNames.SIMPLY_TIMED_ANNOTATION)) { return false; } log.warnf("Bean %s is both counted and timed. The @Counted annotation " + diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java index 7c3236ef506f2a..fa79af6f29d4ae 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/properties/ResourcePropertiesProvider.java @@ -58,7 +58,7 @@ private Collection collectAnnotationsToCopy(DotName classNam return annotations; } - for (AnnotationInstance annotation : classInfo.classAnnotations()) { + for (AnnotationInstance annotation : classInfo.declaredAnnotations()) { if (ANNOTATIONS_TO_COPY.stream().anyMatch(annotation.name().toString()::startsWith)) { annotations.add(annotation); } diff --git a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java index 77d719dc3ae746..798ee3ea100054 100644 --- a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java +++ b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java @@ -566,7 +566,7 @@ void unremovableInterceptors(List restClientInterfaces, Bea ClassInfo restClientClass = index.getClassByName(DotName.createSimple(restClient.getInterfaceName())); if (restClientClass != null) { Set classLevelBindings = new HashSet<>(); - for (AnnotationInstance annotationInstance : restClientClass.classAnnotations()) { + for (AnnotationInstance annotationInstance : restClientClass.declaredAnnotations()) { if (interceptorBindings.contains(annotationInstance.name())) { classLevelBindings.add(annotationInstance); } diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java index daa4efb6514c33..72c1dbbe841eba 100644 --- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java +++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -675,7 +675,7 @@ private static void generateDefaultConstructors(BuildProducer asyncResponseProvi private void scanAsyncResponseProviders(IndexView index) { for (ClassInfo providerClass : index.getAllKnownImplementors(DOTNAME_ASYNC_RESPONSE_PROVIDER)) { - for (AnnotationInstance annotation : providerClass.classAnnotations()) { + for (AnnotationInstance annotation : providerClass.declaredAnnotations()) { if (annotation.name().equals(DOTNAME_PROVIDER)) { for (Type interf : providerClass.interfaceTypes()) { if (interf.kind() == Type.Kind.PARAMETERIZED_TYPE diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java index 1b20fe94b1a58a..06ab7894183d8a 100644 --- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourcePropertiesProvider.java @@ -82,7 +82,7 @@ private Collection collectAnnotationsToCopy(DotName classNam return annotations; } - for (AnnotationInstance annotation : classInfo.classAnnotations()) { + for (AnnotationInstance annotation : classInfo.declaredAnnotations()) { if (ANNOTATIONS_TO_COPY.stream().anyMatch(annotation.name().toString()::startsWith)) { annotations.add(annotation); } diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index d8f9fc8cb76c27..62fb49fc13245c 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -308,7 +308,7 @@ Set getAnnotationsToAdd( final Set names = new HashSet<>(); final Set clazzAnnotations = classInfo.annotationsMap().keySet(); - for (AnnotationInstance instance : classInfo.classAnnotations()) { + for (AnnotationInstance instance : classInfo.declaredAnnotations()) { // make sure that we don't mix and match Spring and CDI annotations since this can cause a lot of problems if (arcScopes.contains(instance.name())) { return annotationsToAdd; From 6d40f4725cf465a38d0ff871cd85f59d85ebaff2 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 20 Apr 2023 09:31:06 +0200 Subject: [PATCH 147/333] Replace deprecated classAnnotationsWithRepeatable with declaredAnnotationsWithRepeatable --- .../smallrye/reactivemessaging/deployment/WiringHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java index 9da83cf47769a1..f54afe684ac43e 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/WiringHelper.java @@ -136,7 +136,7 @@ static boolean isOutboundConnector(ClassInfo ci) { static List getConnectorAttributes(BeanInfo bi, CombinedIndexBuildItem index, ConnectorAttribute.Direction... directions) { List attributes = bi.getImplClazz() - .classAnnotationsWithRepeatable(ReactiveMessagingDotNames.CONNECTOR_ATTRIBUTES, index.getIndex()) + .declaredAnnotationsWithRepeatable(ReactiveMessagingDotNames.CONNECTOR_ATTRIBUTES, index.getIndex()) .stream().flatMap(ai -> Arrays.stream(ai.value().asNestedArray())).collect(Collectors.toList()); if (attributes.isEmpty()) { AnnotationInstance attribute = bi.getImplClazz().declaredAnnotation(ReactiveMessagingDotNames.CONNECTOR_ATTRIBUTE); From 8eaf427480da703e44f3dbf7389fb297d690b6c4 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 25 Apr 2023 20:33:07 +0200 Subject: [PATCH 148/333] Quartz - update the error message when scheduler was not started - replace the deprecated quarkus.quartz.start-mode with quarkus.scheduler.start-mode --- .../java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java index 14eda93992b2c1..e0207272ec9a11 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java @@ -284,7 +284,7 @@ public org.quartz.Trigger apply(TriggerKey triggerKey) { org.quartz.Scheduler produceQuartzScheduler() { if (scheduler == null) { throw new IllegalStateException( - "Quartz scheduler is either explicitly disabled through quarkus.scheduler.enabled=false or no @Scheduled methods were found. If you only need to schedule a job programmatically you can force the start of the scheduler by setting 'quarkus.quartz.start-mode=forced'."); + "Quartz scheduler is either explicitly disabled through quarkus.scheduler.enabled=false or no @Scheduled methods were found. If you only need to schedule a job programmatically you can force the start of the scheduler by setting 'quarkus.scheduler.start-mode=forced'."); } return scheduler; } From e9d26a781699a2a41a918df8a89e170627aa8ab2 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Tue, 25 Apr 2023 16:32:33 +0300 Subject: [PATCH 149/333] Drop condition for GraalVM < 22.2 The minimum required GraalVM version is 22.2 so there is no need to account for the old native-image logs. This also fixes an issue with GraalVM >= 23.0 where the version output changes and it no longer contains the "Version info:" string. --- .../nativeimage/BasicJavaNativeBuildIT.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java index ee12a9f915c204..373ce3ecae8006 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java @@ -24,13 +24,8 @@ public void shouldBuildNativeImage() throws Exception { assertThat(build.getTasks().get(":quarkusBuild")).isEqualTo(BuildResult.SUCCESS_OUTCOME); final String buildOutput = build.getOutput(); // make sure the output log during the build contains some expected logs from the native-image process - CharSequence[] expectedOutput; - if (buildOutput.contains("Version info:")) { // Starting with 22.0 the native-image output changed - expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...", - "Finished generating '" + NATIVE_IMAGE_NAME + "' in" }; - } else { - expectedOutput = new CharSequence[] { "(clinit):", "(typeflow):", "[total]:" }; - } + CharSequence[] expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...", + "Finished generating '" + NATIVE_IMAGE_NAME + "' in" }; assertThat(buildOutput) .withFailMessage("native-image build log is missing certain expected log messages: \n\n %s", buildOutput) .contains(expectedOutput); @@ -58,13 +53,8 @@ public void shouldBuildNativeImageWithCustomName() throws Exception { assertThat(build.getTasks().get(":quarkusBuild")).isEqualTo(BuildResult.SUCCESS_OUTCOME); final String buildOutput = build.getOutput(); // make sure the output log during the build contains some expected logs from the native-image process - CharSequence[] expectedOutput; - if (buildOutput.contains("Version info:")) { // Starting with 22.0 the native-image output changed - expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...", - "Finished generating 'test-runner' in" }; - } else { - expectedOutput = new CharSequence[] { "(clinit):", "(typeflow):", "[total]:" }; - } + CharSequence[] expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...", + "Finished generating 'test-runner' in" }; assertThat(buildOutput) .withFailMessage("native-image build log is missing certain expected log messages: \n\n %s", buildOutput) .contains(expectedOutput) @@ -93,13 +83,8 @@ public void shouldBuildNativeImageWithCustomNameWithoutSuffix() throws Exception assertThat(build.getTasks().get(":quarkusBuild")).isEqualTo(BuildResult.SUCCESS_OUTCOME); final String buildOutput = build.getOutput(); // make sure the output log during the build contains some expected logs from the native-image process - CharSequence[] expectedOutput; - if (buildOutput.contains("Version info:")) { // Starting with 22.0 the native-image output changed - expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...", - "Finished generating 'test' in" }; - } else { - expectedOutput = new CharSequence[] { "(clinit):", "(typeflow):", "[total]:" }; - } + CharSequence[] expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...", + "Finished generating 'test' in" }; assertThat(buildOutput) .withFailMessage("native-image build log is missing certain expected log messages: \n\n %s", buildOutput) .contains(expectedOutput) From d3ba6a1d499349354a9477faf1b552fdf9b4109d Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 26 Apr 2023 10:53:08 +0200 Subject: [PATCH 150/333] Exclude gradle-wrapper/.gradle dir from the codestarts artifact --- devtools/project-core-extension-codestarts/pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/devtools/project-core-extension-codestarts/pom.xml b/devtools/project-core-extension-codestarts/pom.xml index 321d70df6d863a..61564e2b800dbe 100644 --- a/devtools/project-core-extension-codestarts/pom.xml +++ b/devtools/project-core-extension-codestarts/pom.xml @@ -27,6 +27,19 @@ true + + + + org.apache.maven.plugins + maven-jar-plugin + + + gradle-wrapper/.gradle/** + + + + + org.codehaus.mojo From f766f288800bacddfc1fab6648b0f11f143d6351 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 26 Apr 2023 11:00:07 +0200 Subject: [PATCH 151/333] Remove duplicated test dependencies in infinispan-client-deployment --- extensions/infinispan-client/deployment/pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index e0b7dadb2d40f6..bef2b1168ff346 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -61,18 +61,6 @@ quarkus-kubernetes-service-binding-spi - - io.quarkus - quarkus-junit5-internal - test - - - - org.assertj - assertj-core - test - - org.infinispan infinispan-server-testdriver-core From b4efde61f75d5cd2dd488615560070fd4ea52fcb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 26 Apr 2023 13:35:37 +0200 Subject: [PATCH 152/333] Set minimal Maven version to 3.8.2 --- build-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 89b2d95825de8b..066fa88c9ce503 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -60,7 +60,7 @@ - [3.6.3,) + [3.8.2,) 3.8.8 From a5a24bc4a07a07c94c89971c74691cbe7459fcdb Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 26 Apr 2023 10:33:51 -0300 Subject: [PATCH 153/333] Resolve build warnings - This resolves the following warning introduced in d7842f983f719c19785080da7179ce9c02c9e80a ``` [WARNING] [WARNING] Some problems were encountered while building the effective model for io.quarkus:quarkus-infinispan-client-deployment:jar:999-SNAPSHOT [WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.assertj:assertj-core:jar -> duplicate declaration of version (?) @ line 123, column 21 [WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: io.quarkus:quarkus-junit5-internal:jar -> duplicate declaration of version (?) @ line 128, column 21 [WARNING] [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. [WARNING] [WARNING] For this reason, future Maven versions might no longer support building such malformed projects. [WARNING] ``` --- extensions/infinispan-client/deployment/pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index e0b7dadb2d40f6..bef2b1168ff346 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -61,18 +61,6 @@ quarkus-kubernetes-service-binding-spi - - io.quarkus - quarkus-junit5-internal - test - - - - org.assertj - assertj-core - test - - org.infinispan infinispan-server-testdriver-core From 5663cc466a65677c3dfc9e4052c857114746a883 Mon Sep 17 00:00:00 2001 From: Michael Musgrove Date: Wed, 26 Apr 2023 17:27:17 +0100 Subject: [PATCH 154/333] Narayana upgrade (to 6.0.1.Final) --- 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 dd44deff86c634..a31dd4b2f9e638 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -103,7 +103,7 @@ 2.0.0.CR1 8.0.0.Final 6.1.7.Final - 6.0.0.Final + 6.0.1.Final 2.1 8.0.0.Final 8.7.0 From adf7352c1234e6f5ab9a4d8524adf969c93efe86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 19:56:42 +0000 Subject: [PATCH 155/333] Bump org.junit:junit-bom from 5.9.2 to 5.9.3 in /devtools/gradle Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- devtools/gradle/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index d4435ecd0a09a2..64533633a5e724 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -4,7 +4,7 @@ plugin-publish = "1.2.0" kotlin = "1.8.10" smallrye-config = "3.2.1" -junit5 = "5.9.2" +junit5 = "5.9.3" assertj = "3.24.2" [plugins] From f1a5ec6a6b284ba463e5ef2b25e5321890eff209 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 23:47:19 +0000 Subject: [PATCH 156/333] Bump javaparser-core from 3.25.1 to 3.25.2 Bumps [javaparser-core](https://github.com/javaparser/javaparser) from 3.25.1 to 3.25.2. - [Release notes](https://github.com/javaparser/javaparser/releases) - [Changelog](https://github.com/javaparser/javaparser/blob/master/changelog.md) - [Commits](https://github.com/javaparser/javaparser/compare/javaparser-parent-3.25.1...javaparser-parent-3.25.2) --- updated-dependencies: - dependency-name: com.github.javaparser:javaparser-core dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index dd44deff86c634..96fd888c5b7699 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -172,7 +172,7 @@ 4.9.1 1.7.3 0.34.1 - 3.25.1 + 3.25.2 3.14.9 1.17.2 0.2.1 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 066fa88c9ce503..adf6cd699d7240 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -39,7 +39,7 @@ 2.5.7 2.70.0 - 3.25.1 + 3.25.2 2.0.3.Final 6.0.1 From e8207c683f680b425b72a069876fa5cc1f3484f0 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 27 Apr 2023 10:40:35 +1000 Subject: [PATCH 157/333] Fixes for directory resources in native mode --- .../undertow/runtime/KnownPathResourceManager.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java index fe426c649bf774..ee6069a65fdabe 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java @@ -134,7 +134,8 @@ public List list() { SortedSet dirSet = directories.tailSet(slashPath); for (var s : List.of(fileSet, dirSet)) { - for (String i : s) { + for (String file : s) { + var i = file; if (i.equals(slashPath)) { continue; } @@ -143,6 +144,9 @@ public List list() { if (!i.substring(slashPath.length()).contains("/")) { try { Resource resource = underlying.getResource(i); + if (resource == null && directories.contains(file)) { + resource = new DirectoryResource(file); + } if (resource == null) { throw new RuntimeException("Unable to get listed resource " + i + " from directory " + path + " for path " + slashPath + " from underlying manager " + underlying); @@ -212,4 +216,4 @@ public URL getUrl() { return null; } } -} \ No newline at end of file +} From 985f9daac6be97b9e971c781a77a46ca40066db9 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 27 Apr 2023 05:12:04 +0200 Subject: [PATCH 158/333] Register GZIP interceptor for reflection when http compression is enable Fix https://github.com/quarkusio/quarkus/issues/32927 --- .../deployment/RestClientReactiveProcessor.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 0af9df3ca2dcb9..558dbfe0468b3f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -52,6 +52,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.client.interceptors.ClientGZIPDecodingInterceptor; import org.jboss.resteasy.reactive.client.spi.MissingMessageBodyReaderErrorMessageContextualizer; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore; @@ -108,6 +109,7 @@ class RestClientReactiveProcessor { private static final DotName KOTLIN_METADATA_ANNOTATION = DotName.createSimple("kotlin.Metadata"); private static final String DISABLE_SMART_PRODUCES_QUARKUS = "quarkus.rest-client.disable-smart-produces"; + private static final String ENABLE_COMPRESSION = "quarkus.http.enable-compression"; private static final String KOTLIN_INTERFACE_DEFAULT_IMPL_SUFFIX = "$DefaultImpls"; private static final Set SKIP_COPYING_ANNOTATIONS_TO_GENERATED_CLASS = Set.of( @@ -352,6 +354,19 @@ AdditionalBeanBuildItem registerProviderBeans(CombinedIndexBuildItem combinedInd return builder.build(); } + @BuildStep + void registerCompressionInterceptors(BuildProducer reflectiveClasses) { + Boolean enableCompression = ConfigProvider.getConfig() + .getOptionalValue(ENABLE_COMPRESSION, Boolean.class) + .orElse(false); + if (enableCompression) { + reflectiveClasses.produce(ReflectiveClassBuildItem + .builder(ClientGZIPDecodingInterceptor.class) + .serialization(false) + .build()); + } + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) void addRestClientBeans(Capabilities capabilities, From 2779e9302afa47399fb633cabdca5a5ca29efd3b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 27 Apr 2023 09:50:57 +0300 Subject: [PATCH 159/333] Register class for reflection when returning Multi for JAX-RS Resource This is needed because we now support passing annotations the MessageBodyWriter classes when streaming is in play Fixes: #32886 --- .../deployment/ResteasyReactiveProcessor.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 93eceec1e3b36b..37e8f0c6ca81db 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -5,6 +5,7 @@ import static io.quarkus.resteasy.reactive.common.deployment.QuarkusResteasyReactiveDotNames.ROUTING_CONTEXT; import static java.util.stream.Collectors.toList; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.DATE_FORMAT; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; import java.io.File; import java.io.IOException; @@ -436,9 +437,6 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem, paramConverterProviders.initializeDefaultFactories(factoryFunction); paramConverterProviders.sort(); - boolean filtersAccessResourceMethod = filtersAccessResourceMethod( - resourceInterceptorsBuildItem.getResourceInterceptors()); - GeneratedClassGizmoAdaptor classOutput = new GeneratedClassGizmoAdaptor(generatedClassBuildItemBuildProducer, true); try (ClassCreator c = new ClassCreator(classOutput, QUARKUS_INIT_CLASS, null, Object.class.getName(), ResteasyReactiveInitialiser.class.getName()); @@ -529,6 +527,7 @@ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { .source(source) .build()); + boolean paramsRequireReflection = false; for (short i = 0; i < method.parametersCount(); i++) { Type parameterType = method.parameterType(i); if (!hasAnnotation(method, i, ResteasyReactiveServerDotNames.CONTEXT)) { @@ -545,16 +544,16 @@ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { .build()); } if (parameterType.name().equals(FILE)) { - minimallyRegisterResourceClassForReflection(entry, - reflectiveClassBuildItemBuildProducer); - } - } + paramsRequireReflection = true; + break; - if (filtersAccessResourceMethod) { - minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer); + } } - if (entry.additionalRegisterClassForReflectionCheck()) { + if (paramsRequireReflection || + MULTI.toString().equals(entry.getResourceMethod().getSimpleReturnType()) || + filtersAccessResourceMethod(resourceInterceptorsBuildItem.getResourceInterceptors()) || + entry.additionalRegisterClassForReflectionCheck()) { minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer); } } From 3a0566deecf7b115f11d58cedc5db12d66398bbb Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Thu, 27 Apr 2023 10:46:01 +0200 Subject: [PATCH 160/333] Rename server-list to hosts in the Infinispan Dev Services guide --- docs/src/main/asciidoc/infinispan-dev-services.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/infinispan-dev-services.adoc b/docs/src/main/asciidoc/infinispan-dev-services.adoc index 84a9b9570cc878..c88f079dcc94d3 100644 --- a/docs/src/main/asciidoc/infinispan-dev-services.adoc +++ b/docs/src/main/asciidoc/infinispan-dev-services.adoc @@ -54,11 +54,11 @@ Use `quarkus.infinispan-client.devservices.image-name` property to specify anoth Dev Services for Infinispan is automatically enabled unless: - `quarkus.infinispan-client.devservices.enabled` is set to `false` -- the `quarkus.infinispan-client.server-list` is configured +- the `quarkus.infinispan-client.hosts` is configured Dev Services for Infinispan relies on Docker to start the broker. If your environment does not support Docker, you will need to start the broker manually, or connect to an already running broker. -You can configure the broker address using `quarkus.infinispan-client.server-list`. +You can configure the broker address using `quarkus.infinispan-client.hosts`. == Cross Site Replication If you want run the Infinispan Server container with Cross Site Replication configuration, you need to provide a site name. From bd2b94b723d6dde2c53c698fd50c6f289d5e3c1a Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 27 Apr 2023 11:09:16 +0200 Subject: [PATCH 161/333] Fix setting service account in Kubernetes/Openshift extensions Fix https://github.com/quarkusio/quarkus/issues/32933 --- .../deployment/KubernetesCommonHelper.java | 53 +++++++-------- .../KubernetesWithServiceAccountTest.java | 64 +++++++++++++++++++ 2 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithServiceAccountTest.java diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 81b3aecda5e407..591ae9947c4a38 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -304,17 +304,17 @@ private static Collection createRbacDecorators(String name, } // Add service account from extensions: use the one provided by the user always - String defaultServiceAccount = null; - String defaultServiceAccountNamespace = null; + Optional effectiveServiceAccount = config.getServiceAccount(); + String effectiveServiceAccountNamespace = null; for (KubernetesServiceAccountBuildItem sa : serviceAccountsFromExtensions) { - String saName = defaultIfEmpty(sa.getName(), name); + String saName = Optional.ofNullable(sa.getName()).orElse(name); result.add(new DecoratorBuildItem(target, new AddServiceAccountResourceDecorator(name, saName, sa.getNamespace(), sa.getLabels()))); - if (sa.isUseAsDefault() || defaultServiceAccount == null) { - defaultServiceAccount = saName; - defaultServiceAccountNamespace = sa.getNamespace(); + if (sa.isUseAsDefault() || effectiveServiceAccount.isEmpty()) { + effectiveServiceAccount = Optional.of(saName); + effectiveServiceAccountNamespace = sa.getNamespace(); } } @@ -325,9 +325,9 @@ private static Collection createRbacDecorators(String name, sa.getValue().namespace.orElse(null), sa.getValue().labels))); - if (sa.getValue().isUseAsDefault() || defaultServiceAccount == null) { - defaultServiceAccount = saName; - defaultServiceAccountNamespace = sa.getValue().namespace.orElse(null); + if (sa.getValue().isUseAsDefault() || effectiveServiceAccount.isEmpty()) { + effectiveServiceAccount = Optional.of(saName); + effectiveServiceAccountNamespace = sa.getValue().namespace.orElse(null); } } @@ -364,8 +364,8 @@ private static Collection createRbacDecorators(String name, if (roleBinding.subjects.isEmpty()) { requiresServiceAccount = true; subjects.add(new Subject(null, SERVICE_ACCOUNT, - defaultIfEmpty(defaultServiceAccount, config.getServiceAccount().orElse(name)), - defaultServiceAccountNamespace)); + effectiveServiceAccount.orElse(name), + effectiveServiceAccountNamespace)); } else { for (Map.Entry s : roleBinding.subjects.entrySet()) { String subjectName = s.getValue().name.orElse(s.getKey()); @@ -426,8 +426,8 @@ private static Collection createRbacDecorators(String name, Collections.emptyMap(), new RoleRef(defaultRoleName, defaultClusterWide), new Subject(null, SERVICE_ACCOUNT, - defaultIfEmpty(defaultServiceAccount, config.getServiceAccount().orElse(name)), - defaultServiceAccountNamespace)))); + effectiveServiceAccount.orElse(name), + effectiveServiceAccountNamespace)))); } else if (kubernetesClientRequiresRbacGeneration) { // the property `quarkus.kubernetes-client.generate-rbac` is enabled // and the kubernetes-client extension is present @@ -437,24 +437,25 @@ private static Collection createRbacDecorators(String name, Collections.emptyMap(), new RoleRef(DEFAULT_ROLE_NAME_VIEW, true), new Subject(null, SERVICE_ACCOUNT, - defaultIfEmpty(defaultServiceAccount, config.getServiceAccount().orElse(name)), - defaultServiceAccountNamespace)))); + effectiveServiceAccount.orElse(name), + effectiveServiceAccountNamespace)))); } } // generate service account if none is set, and it's required by other resources - if (defaultServiceAccount == null && requiresServiceAccount) { - // use the application name - defaultServiceAccount = config.getServiceAccount().orElse(name); + if (requiresServiceAccount) { // and generate the resource result.add(new DecoratorBuildItem(target, - new AddServiceAccountResourceDecorator(name, defaultServiceAccount, defaultServiceAccountNamespace, + new AddServiceAccountResourceDecorator(name, effectiveServiceAccount.orElse(name), + effectiveServiceAccountNamespace, Collections.emptyMap()))); } - // set service account in deployment resource - if (defaultServiceAccount != null) { - result.add(new DecoratorBuildItem(target, new ApplyServiceAccountNameDecorator(name, defaultServiceAccount))); + // set service account in deployment resource if the user sets a service account, + // or it's required for a dependant resource. + if (effectiveServiceAccount.isPresent() || requiresServiceAccount) { + result.add(new DecoratorBuildItem(target, + new ApplyServiceAccountNameDecorator(name, effectiveServiceAccount.orElse(name)))); } return result; @@ -1048,12 +1049,4 @@ private static List toPolicyRulesList(Map .build()) .collect(Collectors.toList()); } - - private static String defaultIfEmpty(String str, String defaultStr) { - if (str == null || str.length() == 0) { - return defaultStr; - } - - return str; - } } diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithServiceAccountTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithServiceAccountTest.java new file mode 100644 index 00000000000000..0935145c0c275f --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithServiceAccountTest.java @@ -0,0 +1,64 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.ServiceAccount; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KubernetesWithServiceAccountTest { + + private static final String APP_NAME = "kubernetes-with-service-account"; + private static final String SERVICE_ACCOUNT = "my-service-account"; + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) + .setApplicationName(APP_NAME) + .setApplicationVersion("0.1-SNAPSHOT") + .overrideConfigKey("quarkus.kubernetes.service-account", SERVICE_ACCOUNT); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + + Deployment deployment = getDeploymentByName(kubernetesList, APP_NAME).get(); + assertThat(deployment.getSpec().getTemplate().getSpec().getServiceAccountName()).isEqualTo(SERVICE_ACCOUNT); + + Optional serviceAccount = getServiceAccountByName(kubernetesList, SERVICE_ACCOUNT); + assertThat(serviceAccount.isPresent()).isFalse(); + } + + private Optional getDeploymentByName(List kubernetesList, String name) { + return getResourceByName(kubernetesList, Deployment.class, name); + } + + private Optional getServiceAccountByName(List kubernetesList, String saName) { + return getResourceByName(kubernetesList, ServiceAccount.class, saName); + } + + private Optional getResourceByName(List kubernetesList, Class clazz, + String name) { + return kubernetesList.stream() + .filter(r -> r.getMetadata().getName().equals(name)) + .filter(clazz::isInstance) + .map(clazz::cast) + .findFirst(); + } +} From 2f95160996a08b6a0e05700d710918220959fe96 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 27 Apr 2023 10:25:38 +0200 Subject: [PATCH 162/333] ArC - fix CreationalContext handling when used directly with BeanManager Co-authored-by: Ladicek --- .../io/quarkus/arc/impl/ArcContainerImpl.java | 15 ++- .../io/quarkus/arc/impl/BeanManagerImpl.java | 5 +- .../src/test/resources/testng.xml | 2 + ...DependentPreDestroyOnlyCalledOnceTest.java | 123 ++++++++++++++++++ .../arc/test/beanmanager/BeanManagerTest.java | 3 +- 5 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/DependentPreDestroyOnlyCalledOnceTest.java diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 8d3eefbdf3988c..2f6a85ecd8aa3e 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -471,12 +471,21 @@ private InstanceHandle instanceHandle(Type type, Annotation... qualifiers static InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext, boolean resetCurrentInjectionPoint, Consumer destroyLogic) { + return beanInstanceHandle(bean, parentContext, resetCurrentInjectionPoint, destroyLogic, false); + } + + static InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext, + boolean resetCurrentInjectionPoint, Consumer destroyLogic, boolean useParentCreationalContextDirectly) { if (bean != null) { if (parentContext == null && Dependent.class.equals(bean.getScope())) { parentContext = new CreationalContextImpl<>(null); } - CreationalContextImpl creationalContext = parentContext != null ? parentContext.child(bean) - : new CreationalContextImpl<>(bean); + CreationalContextImpl creationalContext; + if (parentContext != null) { + creationalContext = useParentCreationalContextDirectly ? parentContext : parentContext.child(bean); + } else { + creationalContext = new CreationalContextImpl<>(bean); + } InjectionPoint prev = null; if (resetCurrentInjectionPoint) { prev = InjectionPointProvider.set(CurrentInjectionPointProvider.EMPTY); @@ -494,7 +503,7 @@ static InstanceHandle beanInstanceHandle(InjectableBean bean, Creation } } - InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext) { + static InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext) { return beanInstanceHandle(bean, parentContext, true, null); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java index 141cb5a9753f66..2386de23527306 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java @@ -60,7 +60,8 @@ public Object getReference(Bean bean, Type beanType, CreationalContext ctx + "; its bean types are: " + bean.getTypes()); } if (bean instanceof InjectableBean && ctx instanceof CreationalContextImpl) { - return ArcContainerImpl.instance().beanInstanceHandle((InjectableBean) bean, (CreationalContextImpl) ctx).get(); + return ArcContainerImpl.beanInstanceHandle((InjectableBean) bean, (CreationalContextImpl) ctx, true, null, true) + .get(); } throw new IllegalArgumentException( "Arguments must be instances of " + InjectableBean.class + " and " + CreationalContextImpl.class + ": \nbean: " @@ -80,7 +81,7 @@ public Object getInjectableReference(InjectionPoint ij, CreationalContext ctx InjectableBean bean = (InjectableBean) resolve(beans); InjectionPoint prev = InjectionPointProvider.set(ij); try { - return ArcContainerImpl.beanInstanceHandle(bean, (CreationalContextImpl) ctx, false, null).get(); + return ArcContainerImpl.beanInstanceHandle(bean, (CreationalContextImpl) ctx, false, null, true).get(); } finally { InjectionPointProvider.set(prev); } diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index c27324111b1297..46db3531d29f03 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -383,6 +383,8 @@ + + diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/DependentPreDestroyOnlyCalledOnceTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/DependentPreDestroyOnlyCalledOnceTest.java new file mode 100644 index 00000000000000..718fb6ec67f4c5 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/destroy/DependentPreDestroyOnlyCalledOnceTest.java @@ -0,0 +1,123 @@ +package io.quarkus.arc.test.bean.destroy; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Inject; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DependentPreDestroyOnlyCalledOnceTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(MyDependency.class, MyBean.class, MyInterceptedBean.class, + MyInterceptorBinding.class, MyInterceptor.class); + + @BeforeEach + public void setUp() { + MyDependency.preDestroy = 0; + MyBean.preDestroy = 0; + MyInterceptedBean.preDestroy = 0; + } + + @SuppressWarnings("unchecked") + @Test + public void preDestroyOnBeanOnly() { + BeanManager beanManager = Arc.container().beanManager(); + Bean bean = (Bean) beanManager.resolve(beanManager.getBeans(MyBean.class)); + CreationalContext ctx = beanManager.createCreationalContext(bean); + MyBean instance = (MyBean) beanManager.getReference(bean, MyBean.class, ctx); + bean.destroy(instance, ctx); + + assertEquals(1, MyDependency.preDestroy); + assertEquals(1, MyBean.preDestroy); + } + + @SuppressWarnings("unchecked") + @Test + public void preDestroyOnBeanAndInterceptor() { + BeanManager beanManager = Arc.container().beanManager(); + Bean bean = (Bean) beanManager.resolve( + beanManager.getBeans(MyInterceptedBean.class)); + CreationalContext ctx = beanManager.createCreationalContext(bean); + MyInterceptedBean instance = (MyInterceptedBean) beanManager.getReference(bean, MyInterceptedBean.class, ctx); + bean.destroy(instance, ctx); + + assertEquals(1, MyDependency.preDestroy); + assertEquals(1, MyInterceptedBean.preDestroy); + assertEquals(1, MyInterceptor.preDestroy); + } + + @Dependent + static class MyDependency { + static int preDestroy = 0; + + @PreDestroy + void destroy() { + preDestroy++; + } + } + + @Dependent + static class MyBean { + static int preDestroy = 0; + + @Inject + MyDependency dependency; + + @PreDestroy + void destroy() { + preDestroy++; + } + } + + @Dependent + @MyInterceptorBinding + static class MyInterceptedBean { + static int preDestroy = 0; + + @Inject + MyDependency dependency; + + @PreDestroy + void destroy() { + preDestroy++; + } + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + static int preDestroy = 0; + + @PreDestroy + void preDestroy(InvocationContext ctx) throws Exception { + preDestroy++; + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java index 776191168bdc2f..99536e2a7a0780 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java @@ -125,7 +125,7 @@ public void testGetReference() { }); Legacy legacy = (Legacy) beanManager.getReference(legacyBean, Legacy.class, ctx); assertNotNull(legacy.getBeanManager()); - ctx.release(); + legacyBean.destroy((AlternativeLegacy) legacy, ctx); assertTrue(Legacy.DESTROYED.get()); } @@ -179,7 +179,6 @@ public Annotated getAnnotated() { assertNull(injectableReference.injectionPoint.getBean()); } - @SuppressWarnings("serial") @Test public void testResolveInterceptors() { BeanManager beanManager = Arc.container().beanManager(); From 4ef9a5e7c4f0d2d8205224d998b99c8e5359d405 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 14 Mar 2023 11:10:19 +0100 Subject: [PATCH 163/333] ArC: remove superfluous CDI TCK exclusions Some of these tests were fixed in CDI TCK 4.0.8, some were fixed by the bean discovery in strict mode improvements, some were fixed by the improvements to interceptors. --- .../src/test/resources/testng.xml | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index c27324111b1297..e53ce51eeebdc9 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -61,20 +61,6 @@ - - - - - - - - - - - - - - @@ -103,11 +89,6 @@ - - - - - @@ -173,13 +154,6 @@ - - - - - - - @@ -265,28 +239,11 @@ - - - - - - - - - - - - - - - - - @@ -335,11 +292,6 @@ - - - - - @@ -385,12 +337,6 @@ - - - - - - From 25eb1431c1b29acd816918fda7ef1aff8bf2c25c Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 17 Mar 2023 18:32:24 +0100 Subject: [PATCH 164/333] ArC: fix merging @AroundConstruct interceptor bindings Interceptor bindings have to be merged correctly. That is, a class-level binding of some type is ignored if a method-level binding of the same type is also present. This used to be the case for `@AroundInvoke` interceptors, but not for `@AroundConstruct` interceptors. This commit fixes that. --- .../io/quarkus/arc/processor/BeanInfo.java | 3 +- .../io/quarkus/arc/processor/Methods.java | 27 ++++-- .../src/test/resources/testng.xml | 9 -- ...thClassAndConstructorLevelBindingTest.java | 85 +++++++++++++++++++ 4 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithClassAndConstructorLevelBindingTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index ffa2a7995a2d67..9a4c0fb9e63856 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -754,7 +754,8 @@ private Map initLifecycleInterceptors() { putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.PRE_DESTROY); MethodInfo interceptedConstructor = findInterceptedConstructor(target.get().asClass()); if (beanDeployment.getAnnotation(interceptedConstructor, DotNames.NO_CLASS_INTERCEPTORS) == null) { - constructorLevelBindings.addAll(classLevelBindings); + constructorLevelBindings = Methods.mergeMethodAndClassLevelBindings(constructorLevelBindings, + classLevelBindings); } putLifecycleInterceptors(lifecycleInterceptors, constructorLevelBindings, InterceptionType.AROUND_CONSTRUCT); return lifecycleInterceptors; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index cff90da5a09d2c..cfede7633304b6 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -272,12 +272,7 @@ private static Set mergeBindings(BeanDeployment beanDeployme if (methodLevelBindings.isEmpty()) { merged = classLevelBindings; } else { - merged = new HashSet<>(methodLevelBindings); - for (AnnotationInstance binding : classLevelBindings) { - if (methodLevelBindings.stream().noneMatch(a -> binding.name().equals(a.name()))) { - merged.add(binding); - } - } + merged = mergeMethodAndClassLevelBindings(methodLevelBindings, classLevelBindings); if (Modifier.isPrivate(method.flags()) && !Annotations.containsAny(methodAnnotations, OBSERVER_PRODUCER_ANNOTATIONS)) { @@ -302,6 +297,26 @@ private static Set mergeBindings(BeanDeployment beanDeployme return merged; } + static Set mergeMethodAndClassLevelBindings(Collection methodLevelBindings, + Set classLevelBindings) { + if (methodLevelBindings.isEmpty()) { + return classLevelBindings; + } + + Set methodLevelNames = new HashSet<>(); + for (AnnotationInstance methodLevelBinding : methodLevelBindings) { + methodLevelNames.add(methodLevelBinding.name()); + } + + Set result = new HashSet<>(methodLevelBindings); + for (AnnotationInstance classLevelBinding : classLevelBindings) { + if (!methodLevelNames.contains(classLevelBinding.name())) { + result.add(classLevelBinding); + } + } + return result; + } + static class NameAndDescriptor { private final String name; private final String descriptor; diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index e53ce51eeebdc9..5010bb49ad4bf6 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -184,15 +184,6 @@ - - - - - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithClassAndConstructorLevelBindingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithClassAndConstructorLevelBindingTest.java new file mode 100644 index 00000000000000..b48bbd9dfb2b05 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithClassAndConstructorLevelBindingTest.java @@ -0,0 +1,85 @@ +package io.quarkus.arc.test.interceptors.aroundconstruct; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundConstructWithClassAndConstructorLevelBindingTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBinding.class, MyInterceptor1.class, + MyInterceptor2.class, MyBean.class); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertNotNull(bean); + assertTrue(MyBean.constructed); + assertFalse(MyInterceptor1.intercepted); + assertTrue(MyInterceptor2.intercepted); + } + + @Target({ TYPE, CONSTRUCTOR }) + @Retention(RUNTIME) + @InterceptorBinding + public @interface MyBinding { + int value(); + } + + @MyBinding(1) + @Interceptor + @Priority(1) + public static class MyInterceptor1 { + static boolean intercepted = false; + + @AroundConstruct + void intercept(InvocationContext ctx) throws Exception { + intercepted = true; + ctx.proceed(); + } + } + + @MyBinding(2) + @Interceptor + @Priority(1) + public static class MyInterceptor2 { + static boolean intercepted = false; + + @AroundConstruct + void intercept(InvocationContext ctx) throws Exception { + intercepted = true; + ctx.proceed(); + } + } + + @Dependent + @MyBinding(1) + static class MyBean { + static boolean constructed = false; + + @Inject + @MyBinding(2) + MyBean() { + constructed = true; + } + } +} From 3078ee48c01c83de8456be26ec5fab6807b87a10 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 20 Mar 2023 17:33:48 +0100 Subject: [PATCH 165/333] ArC: validate non-@Repeatable interceptor bindings for member values conflicts --- .../quarkus/arc/processor/BeanDeployment.java | 26 +++++++ .../io/quarkus/arc/processor/BeanInfo.java | 26 +++++-- .../quarkus/arc/processor/Interceptors.java | 55 +++++++++++++ .../src/test/resources/testng.xml | 10 --- ...onflictingStereotypeBindingOnBeanTest.java | 70 +++++++++++++++++ ...onflictingTransitiveBindingOnBeanTest.java | 55 +++++++++++++ ...ingTransitiveBindingOnInterceptorTest.java | 63 +++++++++++++++ ...TransitiveStereotypeBindingOnBeanTest.java | 78 +++++++++++++++++++ 8 files changed, 365 insertions(+), 18 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingStereotypeBindingOnBeanTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnBeanTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnInterceptorTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveStereotypeBindingOnBeanTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 7e9457dd05743d..4d943a25ab3d29 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -1534,10 +1534,36 @@ private void findNamespaces(BeanInfo bean, Set namespaces) { } } + /** + * Returns the set of names of non-binding annotation members of given interceptor + * binding annotation that was registered through {@code InterceptorBindingRegistrar}. + *

    + * Does not return non-binding members of interceptor bindings that were + * discovered based on the {@code @InterceptorBinding} annotation; in such case, + * one has to manually check presence of the {@code @NonBinding} annotation on + * the annotation member declaration. + * + * @param name name of the interceptor binding annotation that was registered through + * {@code InterceptorBindingRegistrar} + * @return set of non-binding annotation members of the interceptor binding annotation + */ public Set getInterceptorNonbindingMembers(DotName name) { return interceptorNonbindingMembers.getOrDefault(name, Collections.emptySet()); } + /** + * Returns the set of names of non-binding annotation members of given qualifier + * annotation that was registered through {@code QualifierRegistrar}. + *

    + * Does not return non-binding members of interceptor bindings that were + * discovered based on the {@code @Qualifier} annotation; in such case, one has to + * manually check presence of the {@code @NonBinding} annotation on the annotation member + * declaration. + * + * @param name name of the qualifier annotation that was registered through + * {@code QualifierRegistrar} + * @return set of non-binding annotation members of the qualifier annotation + */ public Set getQualifierNonbindingMembers(DotName name) { return qualifierNonbindingMembers.getOrDefault(name, Collections.emptySet()); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 9a4c0fb9e63856..0b2364fec65885 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -611,13 +611,21 @@ private Map initInterceptedMethods(List Map interceptedMethods = new HashMap<>(); Map> candidates = new HashMap<>(); + ClassInfo targetClass = target.get().asClass(); List classLevelBindings = new ArrayList<>(); - addClassLevelBindings(target.get().asClass(), classLevelBindings); + addClassLevelBindings(targetClass, classLevelBindings, Set.of()); if (!stereotypes.isEmpty()) { - for (StereotypeInfo stereotype : stereotypes) { - addClassLevelBindings(stereotype.getTarget(), classLevelBindings); + // interceptor binding declared on a bean class replaces an interceptor binding of the same type + // declared by a stereotype that is applied to the bean class + Set skip = classLevelBindings.stream() + .map(AnnotationInstance::name) + .collect(Collectors.toUnmodifiableSet()); + for (StereotypeInfo stereotype : Beans.stereotypesWithTransitive(stereotypes, + beanDeployment.getStereotypesMap())) { + addClassLevelBindings(stereotype.getTarget(), classLevelBindings, skip); } } + Interceptors.checkClassLevelInterceptorBindings(classLevelBindings, targetClass, beanDeployment); Set finalMethods = Methods.addInterceptedMethodCandidates(this, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses); @@ -748,7 +756,7 @@ private Map initLifecycleInterceptors() { Map lifecycleInterceptors = new HashMap<>(); Set classLevelBindings = new HashSet<>(); Set constructorLevelBindings = new HashSet<>(); - addClassLevelBindings(target.get().asClass(), classLevelBindings); + addClassLevelBindings(target.get().asClass(), classLevelBindings, Set.of()); addConstructorLevelBindings(target.get().asClass(), constructorLevelBindings); putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.POST_CONSTRUCT); putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.PRE_DESTROY); @@ -774,15 +782,17 @@ private void putLifecycleInterceptors(Map li } } - private void addClassLevelBindings(ClassInfo classInfo, Collection bindings) { + // bindings whose class name is present in `skip` are ignored (this is used to ignore bindings on stereotypes + // when the original class has a binding of the same type) + private void addClassLevelBindings(ClassInfo classInfo, Collection bindings, Set skip) { beanDeployment.getAnnotations(classInfo).stream() - .filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null - && bindings.stream().noneMatch(e -> e.name().equals(a.name()))) + .filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null) + .filter(a -> !skip.contains(a.name())) .forEach(bindings::add); if (classInfo.superClassType() != null && !classInfo.superClassType().name().equals(DotNames.OBJECT)) { ClassInfo superClass = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName()); if (superClass != null) { - addClassLevelBindings(superClass, bindings); + addClassLevelBindings(superClass, bindings, skip); } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java index 6671766609bcf9..ab40dfbabd7513 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java @@ -2,16 +2,22 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import jakarta.enterprise.inject.spi.DefinitionException; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; @@ -65,6 +71,7 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym priority = 0; } + checkClassLevelInterceptorBindings(bindings, interceptorClass, beanDeployment); checkInterceptorFieldsAndMethods(interceptorClass, beanDeployment); return new InterceptorInfo(interceptorClass, beanDeployment, @@ -73,6 +80,54 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym Injection.forBean(interceptorClass, null, beanDeployment, transformer), priority); } + // similar logic already exists in InterceptorResolver, but it doesn't validate + static void checkClassLevelInterceptorBindings(Collection bindings, ClassInfo targetClass, + BeanDeployment beanDeployment) { + // when called from `createInterceptor` above, `bindings` already include transitive bindings, + // but when called from outside, that isn't guaranteed + Set allBindings = new HashSet<>(bindings); + for (AnnotationInstance binding : bindings) { + Set transitive = beanDeployment.getTransitiveInterceptorBindings(binding.name()); + if (transitive != null) { + allBindings.addAll(transitive); + } + } + + IndexView index = beanDeployment.getBeanArchiveIndex(); + + Map> seenBindings = new HashMap<>(); + for (AnnotationInstance binding : allBindings) { + DotName name = binding.name(); + if (beanDeployment.hasAnnotation(index.getClassByName(name), DotNames.REPEATABLE)) { + // don't validate @Repeatable interceptor bindings, repeatability is their entire point + continue; + } + + List seenValues = seenBindings.get(name); + if (seenValues != null) { + // interceptor binding of the same type already seen + // all annotation members (except nonbinding) must have equal values + ClassInfo declaration = beanDeployment.getInterceptorBinding(name); + Set nonBindingMembers = beanDeployment.getInterceptorNonbindingMembers(name); + + for (AnnotationValue value : seenValues) { + if (declaration.method(value.name()).hasDeclaredAnnotation(DotNames.NONBINDING) + || nonBindingMembers.contains(value.name())) { + continue; + } + + if (!value.equals(binding.valueWithDefault(index, value.name()))) { + throw new DefinitionException("Multiple instances of non-repeatable interceptor binding annotation " + + name + " with different member values on class " + targetClass); + } + } + } else { + // interceptor binding of that type not seen yet, just remember it + seenBindings.put(name, binding.valuesWithDefaults(index)); + } + } + } + private static void checkInterceptorFieldsAndMethods(ClassInfo interceptorClass, BeanDeployment beanDeployment) { ClassInfo aClass = interceptorClass; while (aClass != null) { diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 5010bb49ad4bf6..d8d06a466d69b1 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -101,11 +101,6 @@ - - - - - @@ -144,11 +139,6 @@ - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingStereotypeBindingOnBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingStereotypeBindingOnBeanTest.java new file mode 100644 index 00000000000000..ba4316478240d6 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingStereotypeBindingOnBeanTest.java @@ -0,0 +1,70 @@ +package io.quarkus.arc.test.interceptors.bindings.conflicting; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.interceptor.InterceptorBinding; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ConflictingStereotypeBindingOnBeanTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class, FooBinding.class, BarBinding.class, Stereotype1.class, Stereotype2.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Multiple instances of non-repeatable interceptor binding annotation")); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface FooBinding { + int value(); + } + + @FooBinding(10) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface BarBinding { + } + + @FooBinding(1) + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Stereotype1 { + } + + @BarBinding + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Stereotype2 { + } + + @Stereotype1 + @Stereotype2 + @Dependent + static class MyBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnBeanTest.java new file mode 100644 index 00000000000000..57977a227d0bf1 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnBeanTest.java @@ -0,0 +1,55 @@ +package io.quarkus.arc.test.interceptors.bindings.conflicting; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.interceptor.InterceptorBinding; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ConflictingTransitiveBindingOnBeanTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class, FooBinding.class, BarBinding.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Multiple instances of non-repeatable interceptor binding annotation")); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface FooBinding { + int value(); + } + + @FooBinding(10) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface BarBinding { + } + + @FooBinding(1) + @BarBinding + @Dependent + static class MyBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnInterceptorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnInterceptorTest.java new file mode 100644 index 00000000000000..3756a7faccba61 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveBindingOnInterceptorTest.java @@ -0,0 +1,63 @@ +package io.quarkus.arc.test.interceptors.bindings.conflicting; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ConflictingTransitiveBindingOnInterceptorTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyInterceptor.class, FooBinding.class, BarBinding.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Multiple instances of non-repeatable interceptor binding annotation")); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface FooBinding { + int value(); + } + + @FooBinding(10) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface BarBinding { + } + + @FooBinding(1) + @BarBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveStereotypeBindingOnBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveStereotypeBindingOnBeanTest.java new file mode 100644 index 00000000000000..52214b075f24d3 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/conflicting/ConflictingTransitiveStereotypeBindingOnBeanTest.java @@ -0,0 +1,78 @@ +package io.quarkus.arc.test.interceptors.bindings.conflicting; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Stereotype; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.interceptor.InterceptorBinding; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class ConflictingTransitiveStereotypeBindingOnBeanTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class, FooBinding.class, BarBinding.class, Stereotype1.class, Stereotype2.class, + Stereotype3.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Multiple instances of non-repeatable interceptor binding annotation")); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface FooBinding { + int value(); + } + + @FooBinding(10) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface BarBinding { + } + + @FooBinding(1) + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Stereotype1 { + } + + @BarBinding + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Stereotype2 { + } + + @Stereotype1 + @Stereotype2 + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Stereotype3 { + } + + @Stereotype3 + @Dependent + static class MyBean { + } +} From a0ccdea79f8392dcc469c3b36a363e42fef349df Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 21 Apr 2023 17:07:00 +0200 Subject: [PATCH 166/333] ArC: fix class-level interceptor bindings search to always look at stereotypes Previously, the class-level interceptor bindings search only looked at stereotypes when searching for interceptor bindings of business method interceptors (`@AroundInvoke`). With this commit, the class-level interceptor binding search works identically for business method interceptors and lifecycle callbacks (`@AroundConstruct`, `@PostConstruct`, `@PreDestroy`). --- .../io/quarkus/arc/processor/BeanInfo.java | 37 +++--- .../src/test/resources/testng.xml | 7 - .../InterceptorBindingOnStereotypeTest.java | 121 ++++++++++++++++++ 3 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/stereotype/InterceptorBindingOnStereotypeTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 0b2364fec65885..93803ee8fff08c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -613,18 +613,7 @@ private Map initInterceptedMethods(List ClassInfo targetClass = target.get().asClass(); List classLevelBindings = new ArrayList<>(); - addClassLevelBindings(targetClass, classLevelBindings, Set.of()); - if (!stereotypes.isEmpty()) { - // interceptor binding declared on a bean class replaces an interceptor binding of the same type - // declared by a stereotype that is applied to the bean class - Set skip = classLevelBindings.stream() - .map(AnnotationInstance::name) - .collect(Collectors.toUnmodifiableSet()); - for (StereotypeInfo stereotype : Beans.stereotypesWithTransitive(stereotypes, - beanDeployment.getStereotypesMap())) { - addClassLevelBindings(stereotype.getTarget(), classLevelBindings, skip); - } - } + addClassLevelBindings(targetClass, classLevelBindings); Interceptors.checkClassLevelInterceptorBindings(classLevelBindings, targetClass, beanDeployment); Set finalMethods = Methods.addInterceptedMethodCandidates(this, candidates, classLevelBindings, @@ -756,7 +745,7 @@ private Map initLifecycleInterceptors() { Map lifecycleInterceptors = new HashMap<>(); Set classLevelBindings = new HashSet<>(); Set constructorLevelBindings = new HashSet<>(); - addClassLevelBindings(target.get().asClass(), classLevelBindings, Set.of()); + addClassLevelBindings(target.get().asClass(), classLevelBindings); addConstructorLevelBindings(target.get().asClass(), constructorLevelBindings); putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.POST_CONSTRUCT); putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.PRE_DESTROY); @@ -782,9 +771,27 @@ private void putLifecycleInterceptors(Map li } } + private void addClassLevelBindings(ClassInfo targetClass, Collection bindings) { + List classLevelBindings = new ArrayList<>(); + doAddClassLevelBindings(targetClass, classLevelBindings, Set.of()); + bindings.addAll(classLevelBindings); + if (!stereotypes.isEmpty()) { + // interceptor binding declared on a bean class replaces an interceptor binding of the same type + // declared by a stereotype that is applied to the bean class + Set skip = new HashSet<>(); + for (AnnotationInstance classLevelBinding : classLevelBindings) { + skip.add(classLevelBinding.name()); + } + for (StereotypeInfo stereotype : Beans.stereotypesWithTransitive(stereotypes, + beanDeployment.getStereotypesMap())) { + doAddClassLevelBindings(stereotype.getTarget(), bindings, skip); + } + } + } + // bindings whose class name is present in `skip` are ignored (this is used to ignore bindings on stereotypes // when the original class has a binding of the same type) - private void addClassLevelBindings(ClassInfo classInfo, Collection bindings, Set skip) { + private void doAddClassLevelBindings(ClassInfo classInfo, Collection bindings, Set skip) { beanDeployment.getAnnotations(classInfo).stream() .filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null) .filter(a -> !skip.contains(a.name())) @@ -792,7 +799,7 @@ private void addClassLevelBindings(ClassInfo classInfo, Collection - - - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/stereotype/InterceptorBindingOnStereotypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/stereotype/InterceptorBindingOnStereotypeTest.java new file mode 100644 index 00000000000000..536cd0fb35da38 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/stereotype/InterceptorBindingOnStereotypeTest.java @@ -0,0 +1,121 @@ +package io.quarkus.arc.test.interceptors.bindings.stereotype; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Stereotype; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBindingOnStereotypeTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyStereotype.class, + MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void testInterception() { + assertEquals(0, MyInterceptor.aroundConstruct); + assertEquals(0, MyInterceptor.postConstruct); + assertEquals(0, MyInterceptor.aroundInvoke); + assertEquals(0, MyInterceptor.preDestroy); + + InstanceHandle bean = Arc.container().instance(MyBean.class); + MyBean instance = bean.get(); + + assertEquals(1, MyInterceptor.aroundConstruct); + assertEquals(1, MyInterceptor.postConstruct); + assertEquals(0, MyInterceptor.aroundInvoke); + assertEquals(0, MyInterceptor.preDestroy); + + instance.doSomething(); + + assertEquals(1, MyInterceptor.aroundConstruct); + assertEquals(1, MyInterceptor.postConstruct); + assertEquals(1, MyInterceptor.aroundInvoke); + assertEquals(0, MyInterceptor.preDestroy); + + bean.destroy(); + + assertEquals(1, MyInterceptor.aroundConstruct); + assertEquals(1, MyInterceptor.postConstruct); + assertEquals(1, MyInterceptor.aroundInvoke); + assertEquals(1, MyInterceptor.preDestroy); + } + + @Singleton + @MyStereotype + static class MyBean { + void doSomething() { + } + } + + @MyInterceptorBinding + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @interface MyStereotype { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + static int aroundConstruct = 0; + static int postConstruct = 0; + static int aroundInvoke = 0; + static int preDestroy = 0; + + @AroundConstruct + Object aroundConstruct(InvocationContext ctx) throws Exception { + aroundConstruct++; + return ctx.proceed(); + } + + @PostConstruct + Object postConstruct(InvocationContext ctx) throws Exception { + postConstruct++; + return ctx.proceed(); + } + + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + aroundInvoke++; + return ctx.proceed(); + } + + @PreDestroy + Object preDestroy(InvocationContext ctx) throws Exception { + preDestroy++; + return ctx.proceed(); + } + } +} From ef190cca0bc6cfb4bb73d013419c0670cf539fac Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 21 Apr 2023 18:14:33 +0200 Subject: [PATCH 167/333] ArC: fix searching for bindings on an interceptor declaration to also look for inherited bindings --- .../quarkus/arc/processor/Interceptors.java | 37 +++++-- .../src/test/resources/testng.xml | 6 - .../InheritedBindingOnInterceptorTest.java | 104 ++++++++++++++++++ 3 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnInterceptorTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java index ab40dfbabd7513..9e81eba2ca1aa5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java @@ -36,17 +36,8 @@ private Interceptors() { */ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeployment beanDeployment, InjectionPointModifier transformer) { - Set bindings = new HashSet<>(); Integer priority = null; for (AnnotationInstance annotation : beanDeployment.getAnnotations(interceptorClass)) { - bindings.addAll(beanDeployment.extractInterceptorBindings(annotation)); - // can also be a transitive binding - Set transitiveInterceptorBindings = beanDeployment - .getTransitiveInterceptorBindings(annotation.name()); - if (transitiveInterceptorBindings != null) { - bindings.addAll(transitiveInterceptorBindings); - } - if (annotation.name().equals(DotNames.PRIORITY)) { priority = annotation.value().asInt(); } @@ -61,6 +52,9 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym } } + Set bindings = new HashSet<>(); + addBindings(beanDeployment, interceptorClass, bindings, false); + if (bindings.isEmpty()) { throw new DefinitionException("Interceptor has no bindings: " + interceptorClass); } @@ -80,6 +74,31 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym Injection.forBean(interceptorClass, null, beanDeployment, transformer), priority); } + private static void addBindings(BeanDeployment beanDeployment, ClassInfo classInfo, Collection bindings, + boolean onlyInherited) { + for (AnnotationInstance annotation : beanDeployment.getAnnotations(classInfo)) { + ClassInfo annotationClass = getClassByName(beanDeployment.getBeanArchiveIndex(), annotation.name()); + if (onlyInherited && !beanDeployment.hasAnnotation(annotationClass, DotNames.INHERITED)) { + continue; + } + + bindings.addAll(beanDeployment.extractInterceptorBindings(annotation)); + // can also be a transitive binding + Set transitiveInterceptorBindings = beanDeployment + .getTransitiveInterceptorBindings(annotation.name()); + if (transitiveInterceptorBindings != null) { + bindings.addAll(transitiveInterceptorBindings); + } + } + + if (classInfo.superName() != null && !classInfo.superName().equals(DotNames.OBJECT)) { + ClassInfo superClass = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName()); + if (superClass != null) { + addBindings(beanDeployment, superClass, bindings, true); + } + } + } + // similar logic already exists in InterceptorResolver, but it doesn't validate static void checkClassLevelInterceptorBindings(Collection bindings, ClassInfo targetClass, BeanDeployment beanDeployment) { diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index af118b474ed4da..64c17e1e2d7d64 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -230,12 +230,6 @@ - - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnInterceptorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnInterceptorTest.java new file mode 100644 index 00000000000000..92e57e110e6b56 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnInterceptorTest.java @@ -0,0 +1,104 @@ +package io.quarkus.arc.test.interceptors.bindings.inherited; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class InheritedBindingOnInterceptorTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, FooBinding.class, BarBinding.class, + BarBindingUnused.class, FooInterceptor.class, BarInterceptor.class); + + @Test + public void testInterception() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertNotNull(bean); + bean.doSomething(); + assertTrue(FooInterceptor.intercepted); + assertFalse(BarInterceptor.intercepted); + } + + @Singleton + @FooBinding + @BarBinding + static class MyBean { + void doSomething() { + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @Inherited + @interface FooBinding { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + // not @Inherited + @interface BarBinding { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface BarBindingUnused { + } + + @FooBinding + static class FooInterceptorSuperclass { + } + + @Interceptor + @Priority(1) + static class FooInterceptor extends FooInterceptorSuperclass { + static boolean intercepted = false; + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + intercepted = true; + return ctx.proceed(); + } + } + + @BarBinding + static class BarInterceptorSuperclass { + } + + @BarBindingUnused // just to prevent the "Interceptor has no bindings" error, not used otherwise + @Interceptor + @Priority(1) + static class BarInterceptor extends BarInterceptorSuperclass { + static boolean intercepted = false; + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + intercepted = true; + return ctx.proceed(); + } + } +} From f87cf8c364b5af89c11bfcb4574edddc2fa7d126 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 24 Apr 2023 17:01:11 +0200 Subject: [PATCH 168/333] ArC: fix interceptor method nesting for @PostConstruct/@PreDestroy interceptors Previously, `@PostConstruct`/`@PreDestroy` methods declared on the target class were not invoked from within the `@PostConstruct`/`@PreDestroy` methods declared on an interceptor class. Instead, they were invoked _after_ the interceptor chain finished, effectively creating two chains of lifecycle callback interceptors. This didn't allow an "outer" lifecycle callback method, when declared on an interceptor class, to catch and handle an exception thrown by an "inner" lifecycle callback method, when declared on the target class. This commit fixes the implementation of `@PostConstruct`/`@PreDestroy` interceptors by reifying the "inner" chain of lifecycle callback methods, declared on the target class, into a `Runnable` and passing that to the "outer" chain of lifecycle callback methods, declared on the interceptor class, to be invoked at the very end of the chain. This effectively merges the two chains into one, as prescribed by the specification. Note that the "inner" chain is reified into a `Runnable` only when an "outer" chain actually exists. It often doesn't, in which case, allocating a `Runnable` only to call it is a waste of resources. --- .../quarkus/arc/processor/BeanGenerator.java | 92 ++++++++++--- .../arc/processor/MethodDescriptors.java | 4 +- .../arc/processor/SubclassGenerator.java | 8 +- .../AroundConstructInvocationContext.java | 2 +- .../quarkus/arc/impl/InvocationContexts.java | 10 +- .../LifecycleCallbackInvocationContext.java | 21 +-- ...tConstructPreDestroyInvocationContext.java | 35 +++++ .../src/test/resources/testng.xml | 5 - .../interceptors/LifecycleInterceptor.java | 6 +- .../interceptors/complex/MyInterceptor.java | 6 +- .../AroundInvokeInterceptorNestingTest.java | 76 +++++++++++ .../PostConstructInterceptorNestingTest.java | 71 ++++++++++ ...eAndManySuperclassesWithOverridesTest.java | 121 ++++++++++++++++++ ...getClassAndOutsideAndSuperclassesTest.java | 81 ++++++++++++ ...tsideAndSuperclassesWithOverridesTest.java | 82 ++++++++++++ ...tConstructOnTargetClassAndOutsideTest.java | 66 ++++++++++ .../PreDestroyInterceptorNestingTest.java | 71 ++++++++++ ...eAndManySuperclassesWithOverridesTest.java | 121 ++++++++++++++++++ ...getClassAndOutsideAndSuperclassesTest.java | 81 ++++++++++++ ...tsideAndSuperclassesWithOverridesTest.java | 82 ++++++++++++ ...PreDestroyOnTargetClassAndOutsideTest.java | 66 ++++++++++ 21 files changed, 1056 insertions(+), 51 deletions(-) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/PostConstructPreDestroyInvocationContext.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeInterceptorNestingTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructInterceptorNestingTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyInterceptorNestingTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 598c4394bc1484..5a7b6d3ef9ce3a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -819,16 +819,38 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide if (bean.isClassBean()) { if (!bean.isInterceptor()) { + // if there's no `@PreDestroy` interceptor, we'll generate code to invoke `@PreDestroy` callbacks + // directly into the `destroy` method: + // + // public void destroy(MyBean var1, CreationalContext var2) { + // var1.myPreDestroyCallback(); + // var2.release(); + // } + BytecodeCreator preDestroyBytecode = destroy; + // PreDestroy interceptors if (!bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty()) { + // if there _is_ some `@PreDestroy` interceptor, however, we'll reify the chain of `@PreDestroy` + // callbacks into a `Runnable` that we pass into the interceptor chain to be called + // by the last `proceed()` call: + // + // public void destroy(MyBean var1, CreationalContext var2) { + // // this is a `Runnable` that calls `MyBean.myPreDestroyCallback()` + // MyBean_Bean$$function$$2 var3 = new MyBean_Bean$$function$$2(var1); + // ((MyBean_Subclass)var1).arc$destroy((Runnable)var3); + // var2.release(); + // } + FunctionCreator preDestroyForwarder = destroy.createFunction(Runnable.class); + preDestroyBytecode = preDestroyForwarder.getBytecode(); + destroy.invokeVirtualMethod( MethodDescriptor.ofMethod(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), - SubclassGenerator.DESTROY_METHOD_NAME, - void.class), - destroy.getMethodParam(0)); + SubclassGenerator.DESTROY_METHOD_NAME, void.class, Runnable.class), + destroy.getMethodParam(0), preDestroyForwarder.getInstance()); } // PreDestroy callbacks + // possibly wrapped into Runnable so that PreDestroy interceptors can proceed() correctly List preDestroyCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(), DotNames.PRE_DESTROY, bean.getDeployment().getBeanArchiveIndex()); @@ -839,16 +861,21 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide callback.declaringClass().name(), callback.name())); } reflectionRegistration.registerMethod(callback); - destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - destroy.loadClass(callback.declaringClass().name().toString()), - destroy.load(callback.name()), destroy.newArray(Class.class, destroy.load(0)), + preDestroyBytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + preDestroyBytecode.loadClass(callback.declaringClass().name().toString()), + preDestroyBytecode.load(callback.name()), + preDestroyBytecode.newArray(Class.class, preDestroyBytecode.load(0)), destroy.getMethodParam(0), - destroy.newArray(Object.class, destroy.load(0))); + preDestroyBytecode.newArray(Object.class, preDestroyBytecode.load(0))); } else { // instance.superCoolDestroyCallback() - destroy.invokeVirtualMethod(MethodDescriptor.of(callback), destroy.getMethodParam(0)); + preDestroyBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), destroy.getMethodParam(0)); } } + if (preDestroyBytecode != destroy) { + // only if we're generating a `Runnable`, see above + preDestroyBytecode.returnVoid(); + } } // ctx.release() @@ -1750,9 +1777,35 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat SubclassGenerator.MARK_CONSTRUCTED_METHOD_NAME, void.class), instanceHandle); } + // if there's no `@PostConstruct` interceptor, we'll generate code to invoke `@PostConstruct` callbacks + // directly into the `doCreate` method: + // + // private MyBean doCreate(CreationalContext var1) { + // MyBean var2 = new MyBean(); + // var2.myPostConstructCallback(); + // return var2; + // } + BytecodeCreator postConstructsBytecode = create; + // PostConstruct lifecycle callback interceptors InterceptionInfo postConstructs = bean.getLifecycleInterceptors(InterceptionType.POST_CONSTRUCT); if (!postConstructs.isEmpty()) { + // if there _is_ some `@PostConstruct` interceptor, however, we'll reify the chain of `@PostConstruct` + // callbacks into a `Runnable` that we pass into the interceptor chain to be called + // by the last `proceed()` call: + // + // private MyBean doCreate(CreationalContext var1) { + // ... + // MyBean var7 = new MyBean(); + // // this is a `Runnable` that calls `MyBean.myPostConstructCallback()` + // MyBean_Bean$$function$$1 var11 = new MyBean_Bean$$function$$1(var7); + // ... + // InvocationContext var12 = InvocationContexts.postConstruct(var7, (List)var5, var10, (Runnable)var11); + // var12.proceed(); + // return var7; + // } + FunctionCreator postConstructForwarder = create.createFunction(Runnable.class); + postConstructsBytecode = postConstructForwarder.getBytecode(); // Interceptor bindings ResultHandle bindingsArray = create.newArray(Object.class, postConstructs.bindings.size()); @@ -1768,7 +1821,8 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat // InvocationContextImpl.postConstruct(instance,postConstructs).proceed() ResultHandle invocationContextHandle = create.invokeStaticMethod( MethodDescriptors.INVOCATION_CONTEXTS_POST_CONSTRUCT, instanceHandle, - postConstructsHandle, create.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray)); + postConstructsHandle, create.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray), + postConstructForwarder.getInstance()); TryBlock tryCatch = create.tryBlock(); CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class); @@ -1780,10 +1834,11 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat } // PostConstruct callbacks + // possibly wrapped into Runnable so that PostConstruct interceptors can proceed() correctly if (!bean.isInterceptor()) { List postConstructCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(), - DotNames.POST_CONSTRUCT, - bean.getDeployment().getBeanArchiveIndex()); + DotNames.POST_CONSTRUCT, bean.getDeployment().getBeanArchiveIndex()); + for (MethodInfo callback : postConstructCallbacks) { if (isReflectionFallbackNeeded(callback, targetPackage)) { if (Modifier.isPrivate(callback.flags())) { @@ -1792,15 +1847,20 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat callback.name())); } reflectionRegistration.registerMethod(callback); - create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - create.loadClass(callback.declaringClass().name().toString()), - create.load(callback.name()), create.newArray(Class.class, create.load(0)), instanceHandle, - create.newArray(Object.class, create.load(0))); + postConstructsBytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + postConstructsBytecode.loadClass(callback.declaringClass().name().toString()), + postConstructsBytecode.load(callback.name()), + postConstructsBytecode.newArray(Class.class, postConstructsBytecode.load(0)), instanceHandle, + postConstructsBytecode.newArray(Object.class, postConstructsBytecode.load(0))); } else { - create.invokeVirtualMethod(MethodDescriptor.of(callback), instanceHandle); + postConstructsBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), instanceHandle); } } } + if (postConstructsBytecode != create) { + // only if we're generating a `Runnable`, see above + postConstructsBytecode.returnVoid(); + } create.returnValue(instanceHandle); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 9d14b1dfad9918..a5dc7fad43c2f5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -178,11 +178,11 @@ public final class MethodDescriptors { public static final MethodDescriptor INVOCATION_CONTEXTS_POST_CONSTRUCT = MethodDescriptor.ofMethod( InvocationContexts.class, "postConstruct", - InvocationContext.class, Object.class, List.class, Set.class); + InvocationContext.class, Object.class, List.class, Set.class, Runnable.class); public static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class, "preDestroy", - InvocationContext.class, Object.class, List.class, Set.class); + InvocationContext.class, Object.class, List.class, Set.class, Runnable.class); public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_SUPERCLASS = MethodDescriptor.ofMethod( InvocationContexts.class, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index fcd318c61d779d..284a76641324b7 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -947,9 +947,10 @@ private void createInterceptedMethod(ClassOutput classOutput, BeanInfo bean, Met protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreator subclass, FieldDescriptor preDestroysField) { if (preDestroysField != null) { - MethodCreator destroy = subclass - .getMethodCreator(MethodDescriptor.ofMethod(subclass.getClassName(), DESTROY_METHOD_NAME, void.class)); + MethodCreator destroy = subclass.getMethodCreator(MethodDescriptor.ofMethod(subclass.getClassName(), + DESTROY_METHOD_NAME, void.class, Runnable.class)); ResultHandle predestroysHandle = destroy.readInstanceField(preDestroysField, destroy.getThis()); + ResultHandle forward = destroy.getMethodParam(0); // Interceptor bindings InterceptionInfo preDestroy = bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY); @@ -972,7 +973,8 @@ protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreato // InvocationContextImpl.preDestroy(this,predestroys) ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXTS_PRE_DESTROY, tryCatch.getThis(), predestroysHandle, - tryCatch.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray)); + tryCatch.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray), + forward); // InvocationContext.proceed() tryCatch.invokeInterfaceMethod(MethodDescriptors.INVOCATION_CONTEXT_PROCEED, invocationContext); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java index 532f9a51cb552f..d61b927ac3a18c 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java @@ -7,7 +7,7 @@ import java.util.function.Supplier; /** - * An InvocationContext implementation used for AroundConstruct callbacks. + * An {@code InvocationContext} implementation used for {@code AroundConstruct} callbacks. */ class AroundConstructInvocationContext extends LifecycleCallbackInvocationContext { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java index fa137958f4c7c0..b7e0319ec6f1df 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java @@ -46,11 +46,12 @@ public static Object performTargetAroundInvoke(InvocationContext delegate, * @param target * @param chain * @param interceptorBindings + * @param forward * @return a new invocation context */ public static InvocationContext postConstruct(Object target, List chain, - Set interceptorBindings) { - return new LifecycleCallbackInvocationContext(target, null, interceptorBindings, chain); + Set interceptorBindings, Runnable forward) { + return new PostConstructPreDestroyInvocationContext(target, null, interceptorBindings, chain, forward); } /** @@ -58,11 +59,12 @@ public static InvocationContext postConstruct(Object target, List chain, - Set interceptorBindings) { - return new LifecycleCallbackInvocationContext(target, null, interceptorBindings, chain); + Set interceptorBindings, Runnable forward) { + return new PostConstructPreDestroyInvocationContext(target, null, interceptorBindings, chain, forward); } /** diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java index b84425940266e8..c085df95ff9735 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/LifecycleCallbackInvocationContext.java @@ -7,11 +7,11 @@ /** * A simple stateful {@link jakarta.interceptor.InvocationContext} implementation used for - * {@link jakarta.annotation.PostConstruct} and {@link jakarta.annotation.PreDestroy} callbacks. + * lifecycle callback interceptors. *

    * All lifecycle callback interceptors of a specific chain must be invoked on the same thread. */ -class LifecycleCallbackInvocationContext extends AbstractInvocationContext { +abstract class LifecycleCallbackInvocationContext extends AbstractInvocationContext { protected final Set bindings; protected final List chain; @@ -31,7 +31,8 @@ public Object proceed() throws Exception { // Invoke the next interceptor in the chain invokeNext(); } else { - // The invocation of proceed in the last interceptor method in the chain + // The invocation of proceed in the last interceptor method in the chain, + // need to forward to the target class interceptorChainCompleted(); } // The return value of a lifecycle callback is ignored @@ -53,19 +54,7 @@ public Set getInterceptorBindings() { return bindings; } - @Override - public Object[] getParameters() { - throw new IllegalStateException(); - } - - @Override - public void setParameters(Object[] params) { - throw new IllegalStateException(); - } - - protected void interceptorChainCompleted() throws Exception { - // No-op - } + protected abstract void interceptorChainCompleted() throws Exception; private Object invokeNext() throws Exception { try { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/PostConstructPreDestroyInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/PostConstructPreDestroyInvocationContext.java new file mode 100644 index 00000000000000..3d757ea35c9ce8 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/PostConstructPreDestroyInvocationContext.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.impl; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; + +/** + * An {@code InvocationContext} implementation used for {@code PostConstruct} and {@code PreDestroy} callbacks. + */ +class PostConstructPreDestroyInvocationContext extends LifecycleCallbackInvocationContext { + private final Runnable forward; + + PostConstructPreDestroyInvocationContext(Object target, Object[] parameters, + Set bindings, List chain, Runnable forward) { + super(target, parameters, bindings, chain); + this.forward = forward; + } + + @Override + protected void interceptorChainCompleted() { + if (forward != null) { + forward.run(); + } + } + + @Override + public Object[] getParameters() { + throw new IllegalStateException(); + } + + @Override + public void setParameters(Object[] params) { + throw new IllegalStateException(); + } +} diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 64c17e1e2d7d64..e373449fceee7d 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -61,11 +61,6 @@ - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java index 3cf327c15a577d..19f77456d2d4a4 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java @@ -22,21 +22,23 @@ public class LifecycleInterceptor { static final List PRE_DESTROYS = new CopyOnWriteArrayList<>(); @PostConstruct - void simpleInit(InvocationContext ctx) { + void simpleInit(InvocationContext ctx) throws Exception { Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } POST_CONSTRUCTS.add(ctx.getTarget()); + ctx.proceed(); } @PreDestroy - void simpleDestroy(InvocationContext ctx) { + void simpleDestroy(InvocationContext ctx) throws Exception { Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } PRE_DESTROYS.add(ctx.getTarget()); + ctx.proceed(); } @AroundConstruct diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/complex/MyInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/complex/MyInterceptor.java index ecf34d0aa52dfd..02585cc3f5b041 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/complex/MyInterceptor.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/complex/MyInterceptor.java @@ -21,13 +21,15 @@ public class MyInterceptor { public static AtomicBoolean aroundInvokeInvoked = new AtomicBoolean(false); @PreDestroy - public void preDestroy(InvocationContext ic) { + public void preDestroy(InvocationContext ic) throws Exception { preDestroyInvoked.set(true); + ic.proceed(); } @PostConstruct - public void postConstruct(InvocationContext ic) { + public void postConstruct(InvocationContext ic) throws Exception { postConstructInvoked.set(true); + ic.proceed(); } @AroundConstruct diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeInterceptorNestingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeInterceptorNestingTest.java new file mode 100644 index 00000000000000..92734cdca87d18 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeInterceptorNestingTest.java @@ -0,0 +1,76 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeInterceptorNestingTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("expected-exception", bean.doSomething()); + assertEquals(List.of("MyInterceptor", "MyBean"), MyBean.invocations); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + static final List invocations = new ArrayList<>(); + + String doSomething() { + throw new IllegalStateException("should not be called"); + } + + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) { + invocations.add(MyBean.class.getSimpleName()); + throw new IllegalArgumentException("intentional"); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + try { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } catch (IllegalArgumentException e) { + return "expected-exception"; + } + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructInterceptorNestingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructInterceptorNestingTest.java new file mode 100644 index 00000000000000..9d2901d406e0c4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructInterceptorNestingTest.java @@ -0,0 +1,71 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PostConstructInterceptorNestingTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class).get(); + assertEquals(List.of("MyInterceptor", "MyBean", "expected-exception"), MyBean.invocations); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + static final List invocations = new ArrayList<>(); + + @PostConstruct + void postConstruct() { + invocations.add(MyBean.class.getSimpleName()); + throw new IllegalArgumentException("intentional"); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @PostConstruct + void postConstruct(InvocationContext ctx) throws Exception { + try { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + ctx.proceed(); + } catch (IllegalArgumentException e) { + MyBean.invocations.add("expected-exception"); + } + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..5aa91030a8c269 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java @@ -0,0 +1,121 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PostConstructOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class); + assertEquals(List.of("Foxtrot", "MyInterceptor", "Charlie", "MyBean"), MyBean.invocations); + } + + static class Alpha { + @PostConstruct + void intercept() throws Exception { + MyBean.invocations.add("this should not be called as the method is overridden in MyBean"); + } + } + + static class Bravo extends Alpha { + @PostConstruct + void specialIntercept() { + MyBean.invocations.add("this should not be called as the method is overridden in Charlie"); + } + } + + static class Charlie extends Bravo { + @PostConstruct + void superIntercept() throws Exception { + MyBean.invocations.add(Charlie.class.getSimpleName()); + } + + @Override + void specialIntercept() { + MyBean.invocations.add("this is not an interceptor method"); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends Charlie { + static final List invocations = new ArrayList<>(); + + @Override + @PostConstruct + void intercept() throws Exception { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class Delta { + @PostConstruct + Object intercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add("this should not be called as the method is overridden in MyInterceptor"); + return ctx.proceed(); + } + } + + static class Echo extends Delta { + @PostConstruct + void specialIntercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add("this should not be called as the method is overridden in Foxtrot"); + } + } + + static class Foxtrot extends Echo { + @PostConstruct + Object superIntercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add(Foxtrot.class.getSimpleName()); + return ctx.proceed(); + } + + @Override + void specialIntercept(InvocationContext ctx) { + MyBean.invocations.add("this is not an interceptor method"); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends Foxtrot { + @PostConstruct + Object intercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesTest.java new file mode 100644 index 00000000000000..8b524b195835fc --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesTest.java @@ -0,0 +1,81 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PostConstructOnTargetClassAndOutsideAndSuperclassesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class); + assertEquals(List.of("MyInterceptorSuperclass", "MyInterceptor", "MyBeanSuperclass", "MyBean"), MyBean.invocations); + } + + static class MyBeanSuperclass { + @PostConstruct + void superPostConstruct() { + MyBean.invocations.add(MyBeanSuperclass.class.getSimpleName()); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + static final List invocations = new ArrayList<>(); + + @PostConstruct + void postConstruct() { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @PostConstruct + void superPostConstruct(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptorSuperclass.class.getSimpleName()); + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor extends MyInterceptorSuperclass { + @PostConstruct + Object postConstruct(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..361fb28bda5b61 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java @@ -0,0 +1,82 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PostConstructOnTargetClassAndOutsideAndSuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class); + assertEquals(List.of("MyInterceptorSuperclass", "MyInterceptor", "MyBean"), MyBean.invocations); + } + + static class MyBeanSuperclass { + @PostConstruct + void postConstruct() { + MyBean.invocations.add("this should not be called as the method is overridden in MyBean"); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + static final List invocations = new ArrayList<>(); + + @PostConstruct + @Override + void postConstruct() { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @PostConstruct + void superPostConstruct(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptorSuperclass.class.getSimpleName()); + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor extends MyInterceptorSuperclass { + @PostConstruct + Object postConstruct(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideTest.java new file mode 100644 index 00000000000000..eb5d81d6a38a7f --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PostConstructOnTargetClassAndOutsideTest.java @@ -0,0 +1,66 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PostConstructOnTargetClassAndOutsideTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class); + assertEquals(List.of("MyInterceptor", "MyBean"), MyBean.invocations); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + static final List invocations = new ArrayList<>(); + + @PostConstruct + void postConstruct() { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @PostConstruct + Object postConstruct(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyInterceptorNestingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyInterceptorNestingTest.java new file mode 100644 index 00000000000000..a01def8b023244 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyInterceptorNestingTest.java @@ -0,0 +1,71 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PreDestroyInterceptorNestingTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class).destroy(); + assertEquals(List.of("MyInterceptor", "MyBean", "expected-exception"), MyBean.invocations); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + static final List invocations = new ArrayList<>(); + + @PreDestroy + void preDestroy() { + invocations.add(MyBean.class.getSimpleName()); + throw new IllegalArgumentException("intentional"); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @PreDestroy + void preDestroy(InvocationContext ctx) throws Exception { + try { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + ctx.proceed(); + } catch (IllegalArgumentException e) { + MyBean.invocations.add("expected-exception"); + } + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..bb6f1e1107ad95 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java @@ -0,0 +1,121 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PreDestroyOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class).destroy(); + assertEquals(List.of("Foxtrot", "MyInterceptor", "Charlie", "MyBean"), MyBean.invocations); + } + + static class Alpha { + @PreDestroy + void intercept() throws Exception { + MyBean.invocations.add("this should not be called as the method is overridden in MyBean"); + } + } + + static class Bravo extends Alpha { + @PreDestroy + void specialIntercept() { + MyBean.invocations.add("this should not be called as the method is overridden in Charlie"); + } + } + + static class Charlie extends Bravo { + @PreDestroy + void superIntercept() throws Exception { + MyBean.invocations.add(Charlie.class.getSimpleName()); + } + + @Override + void specialIntercept() { + MyBean.invocations.add("this is not an interceptor method"); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends Charlie { + static final List invocations = new ArrayList<>(); + + @Override + @PreDestroy + void intercept() throws Exception { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class Delta { + @PreDestroy + Object intercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add("this should not be called as the method is overridden in MyInterceptor"); + return ctx.proceed(); + } + } + + static class Echo extends Delta { + @PreDestroy + void specialIntercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add("this should not be called as the method is overridden in Foxtrot"); + } + } + + static class Foxtrot extends Echo { + @PreDestroy + Object superIntercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add(Foxtrot.class.getSimpleName()); + return ctx.proceed(); + } + + @Override + void specialIntercept(InvocationContext ctx) { + MyBean.invocations.add("this is not an interceptor method"); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends Foxtrot { + @PreDestroy + Object intercept(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesTest.java new file mode 100644 index 00000000000000..c95dbc00a645d8 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesTest.java @@ -0,0 +1,81 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PreDestroyOnTargetClassAndOutsideAndSuperclassesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class).destroy(); + assertEquals(List.of("MyInterceptorSuperclass", "MyInterceptor", "MyBeanSuperclass", "MyBean"), MyBean.invocations); + } + + static class MyBeanSuperclass { + @PreDestroy + void superPreDestroy() { + MyBean.invocations.add(MyBeanSuperclass.class.getSimpleName()); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + static final List invocations = new ArrayList<>(); + + @PreDestroy + void preDestroy() { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @PreDestroy + void superPreDestroy(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptorSuperclass.class.getSimpleName()); + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor extends MyInterceptorSuperclass { + @PreDestroy + Object preDestroy(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java new file mode 100644 index 00000000000000..54bf433c895dc0 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java @@ -0,0 +1,82 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PreDestroyOnTargetClassAndOutsideAndSuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class).destroy(); + assertEquals(List.of("MyInterceptorSuperclass", "MyInterceptor", "MyBean"), MyBean.invocations); + } + + static class MyBeanSuperclass { + @PreDestroy + void preDestroy() { + MyBean.invocations.add("this should not be called as the method is overridden in MyBean"); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + static final List invocations = new ArrayList<>(); + + @PreDestroy + @Override + void preDestroy() { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @PreDestroy + void superPreDestroy(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptorSuperclass.class.getSimpleName()); + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor extends MyInterceptorSuperclass { + @PreDestroy + Object preDestroy(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideTest.java new file mode 100644 index 00000000000000..c4fedd6617818a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/PreDestroyOnTargetClassAndOutsideTest.java @@ -0,0 +1,66 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class PreDestroyOnTargetClassAndOutsideTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + arc.instance(MyBean.class).destroy(); + assertEquals(List.of("MyInterceptor", "MyBean"), MyBean.invocations); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + static final List invocations = new ArrayList<>(); + + @PreDestroy + void preDestroy() { + invocations.add(MyBean.class.getSimpleName()); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @PreDestroy + Object preDestroy(InvocationContext ctx) throws Exception { + MyBean.invocations.add(MyInterceptor.class.getSimpleName()); + return ctx.proceed(); + } + } +} From 9ebd15f1c93a2b9dcc1e2c607401ceea3e4d1186 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 25 Apr 2023 12:00:53 +0200 Subject: [PATCH 169/333] ArC: fix bean creation in presence of @AroundConstruct interceptors `@AroundConstruct` interceptors may modify the constructor arguments using `InvocationContext.setParameters()`. ArC used to ignore this modification and call the bean constructor using the original arguments, obtained before calling the interceptors. This commit fixes that and uses the arguments array from `InvocationContext` to call the bean constructor. To do that, the forwarding function passed along the interceptor chain is no longer a `Supplier` (which used to call the bean constructor with original arguments), but a `Function` (which is passed the arguments stored in the `InvocationContext` and uses them to call the bean constructor). --- .../quarkus/arc/processor/BeanGenerator.java | 16 +++- .../arc/processor/MethodDescriptors.java | 3 +- .../AroundConstructInvocationContext.java | 12 +-- .../quarkus/arc/impl/InvocationContexts.java | 7 +- .../src/test/resources/testng.xml | 4 - ...roundConstructWithParameterChangeTest.java | 78 +++++++++++++++++++ 6 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithParameterChangeTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 5a7b6d3ef9ce3a..ac3bac1a187c73 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -1609,10 +1609,20 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat interceptorToWrap, transientReferences, injectableCtorParams, allOtherCtorParams); // Forwarding function - // Supplier forward = () -> new SimpleBean_Subclass(ctx,lifecycleInterceptorProvider1) - FunctionCreator func = create.createFunction(Supplier.class); + // Function forward = (params) -> new SimpleBean_Subclass(params[0], ctx, lifecycleInterceptorProvider1) + FunctionCreator func = create.createFunction(Function.class); BytecodeCreator funcBytecode = func.getBytecode(); - List providerHandles = new ArrayList<>(injectableCtorParams); + List params = new ArrayList<>(); + if (!injectableCtorParams.isEmpty()) { + // `injectableCtorParams` are passed to the first interceptor in the chain + // the `Function` generated here obtains the parameter array from `InvocationContext` + // these 2 arrays have the same shape (size and element types), but not necessarily the same content + ResultHandle paramsArray = funcBytecode.checkCast(funcBytecode.getMethodParam(0), Object[].class); + for (int i = 0; i < injectableCtorParams.size(); i++) { + params.add(funcBytecode.readArrayValue(paramsArray, i)); + } + } + List providerHandles = new ArrayList<>(params); providerHandles.addAll(allOtherCtorParams); ResultHandle retHandle = newInstanceHandle(bean, beanCreator, funcBytecode, create, providerType.className(), baseName, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index a5dc7fad43c2f5..4f5ebd821a8ed1 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; import jakarta.enterprise.context.spi.Context; @@ -173,7 +174,7 @@ public final class MethodDescriptors { public static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( InvocationContexts.class, "aroundConstruct", - InvocationContext.class, Constructor.class, Object[].class, List.class, Supplier.class, Set.class); + InvocationContext.class, Constructor.class, Object[].class, List.class, Function.class, Set.class); public static final MethodDescriptor INVOCATION_CONTEXTS_POST_CONSTRUCT = MethodDescriptor.ofMethod( InvocationContexts.class, diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java index d61b927ac3a18c..2455b8b40eaff4 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundConstructInvocationContext.java @@ -4,7 +4,7 @@ import java.lang.reflect.Constructor; import java.util.List; import java.util.Set; -import java.util.function.Supplier; +import java.util.function.Function; /** * An {@code InvocationContext} implementation used for {@code AroundConstruct} callbacks. @@ -12,17 +12,17 @@ class AroundConstructInvocationContext extends LifecycleCallbackInvocationContext { private final Constructor constructor; - private final Supplier aroundConstructForward; + private final Function forward; AroundConstructInvocationContext(Constructor constructor, Object[] parameters, Set interceptorBindings, - List chain, Supplier aroundConstructForward) { + List chain, Function forward) { super(null, parameters, interceptorBindings, chain); - this.aroundConstructForward = aroundConstructForward; + this.forward = forward; this.constructor = constructor; } - protected void interceptorChainCompleted() throws Exception { - target = aroundConstructForward.get(); + protected void interceptorChainCompleted() { + target = forward.apply(parameters); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java index b7e0319ec6f1df..1650a9014725e8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InvocationContexts.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Supplier; +import java.util.function.Function; import jakarta.interceptor.InvocationContext; @@ -77,10 +77,9 @@ public static InvocationContext preDestroy(Object target, List constructor, Object[] parameters, List chain, - Supplier aroundConstructForward, + Function forward, Set interceptorBindings) { - return new AroundConstructInvocationContext(constructor, parameters, interceptorBindings, chain, - aroundConstructForward); + return new AroundConstructInvocationContext(constructor, parameters, interceptorBindings, chain, forward); } /** diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index e373449fceee7d..ea2fbd43c79970 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -79,8 +79,6 @@ - - @@ -91,8 +89,6 @@ - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithParameterChangeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithParameterChangeTest.java new file mode 100644 index 00000000000000..b036403615f3eb --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructWithParameterChangeTest.java @@ -0,0 +1,78 @@ +package io.quarkus.arc.test.interceptors.aroundconstruct; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundConstructWithParameterChangeTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(SimpleBean.class, MyDependency.class, + MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + SimpleBean simpleBean = Arc.container().instance(SimpleBean.class).get(); + assertNotNull(simpleBean); + assertNotNull(simpleBean.dependency); + assertEquals("from interceptor", simpleBean.dependency.value); + } + + @Singleton + @MyInterceptorBinding + static class SimpleBean { + final MyDependency dependency; + + @Inject + SimpleBean(MyDependency dependency) { + this.dependency = dependency; + } + } + + @Singleton + static class MyDependency { + final String value; + + MyDependency() { + this("default"); + } + + MyDependency(String value) { + this.value = value; + } + } + + @Target({ ElementType.TYPE, ElementType.CONSTRUCTOR }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + @AroundConstruct + void aroundConstruct(InvocationContext ctx) throws Exception { + ctx.setParameters(new Object[] { new MyDependency("from interceptor") }); + ctx.proceed(); + } + } +} From bcf76ea80a5200925675c4d37b8ffe1db0a343e0 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 25 Apr 2023 12:15:10 +0200 Subject: [PATCH 170/333] ArC: fix wrapping exceptions thrown by @AroundConstruct and @PostConstruct interceptors ArC used to always wrap exceptions thrown by `@AroundConstruct` and `@PostConstruct` interceptors into `RuntimeException`, even if these exceptions were themselves `RuntimeException`s. This commit fixes that and only wraps checked exceptions. --- .../quarkus/arc/processor/BeanGenerator.java | 4 + .../src/test/resources/testng.xml | 10 --- .../AroundConstructExceptionHandlingTest.java | 85 +++++++++++++++++++ .../PostConstructExceptionHandlingTest.java | 85 +++++++++++++++++++ 4 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructExceptionHandlingTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/postconstruct/PostConstructExceptionHandlingTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index ac3bac1a187c73..1c6d143763c317 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -1653,6 +1653,8 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat create.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray)); TryBlock tryCatch = create.tryBlock(); CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class); + exceptionCatch.ifFalse(exceptionCatch.instanceOf(exceptionCatch.getCaughtException(), RuntimeException.class)) + .falseBranch().throwException(exceptionCatch.getCaughtException()); // throw new RuntimeException(e) exceptionCatch.throwException(RuntimeException.class, "Error invoking aroundConstructs", exceptionCatch.getCaughtException()); @@ -1836,6 +1838,8 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat TryBlock tryCatch = create.tryBlock(); CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class); + exceptionCatch.ifFalse(exceptionCatch.instanceOf(exceptionCatch.getCaughtException(), RuntimeException.class)) + .falseBranch().throwException(exceptionCatch.getCaughtException()); // throw new RuntimeException(e) exceptionCatch.throwException(RuntimeException.class, "Error invoking postConstructs", exceptionCatch.getCaughtException()); diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index ea2fbd43c79970..9f4ccc7df05bf0 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -77,21 +77,11 @@ - - - - - - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructExceptionHandlingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructExceptionHandlingTest.java new file mode 100644 index 00000000000000..2b1b43af8fbf85 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructExceptionHandlingTest.java @@ -0,0 +1,85 @@ +package io.quarkus.arc.test.interceptors.aroundconstruct; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundConstruct; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundConstructExceptionHandlingTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(SimpleBean.class, + MyInterceptorBinding.class, MyInterceptor1.class, MyInterceptor2.class); + + @Test + public void test() { + IllegalStateException e = assertThrows(IllegalStateException.class, () -> { + Arc.container().instance(SimpleBean.class).get(); + }); + + assertTrue(MyInterceptor1.intercepted); + assertTrue(MyInterceptor2.intercepted); + + assertNotNull(e.getCause()); + assertEquals(IllegalArgumentException.class, e.getCause().getClass()); + assertEquals("intentional", e.getCause().getMessage()); + } + + @Singleton + @MyInterceptorBinding + static class SimpleBean { + } + + @Target({ ElementType.TYPE, ElementType.CONSTRUCTOR }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor1 { + static boolean intercepted = false; + + @AroundConstruct + void aroundConstruct(InvocationContext ctx) throws Exception { + intercepted = true; + try { + ctx.proceed(); + } catch (IllegalArgumentException e) { + throw new IllegalStateException(e); + } + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(2) + static class MyInterceptor2 { + static boolean intercepted = false; + + @AroundConstruct + void aroundConstruct(InvocationContext ctx) { + intercepted = true; + throw new IllegalArgumentException("intentional"); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/postconstruct/PostConstructExceptionHandlingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/postconstruct/PostConstructExceptionHandlingTest.java new file mode 100644 index 00000000000000..7d3c2b706c01df --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/postconstruct/PostConstructExceptionHandlingTest.java @@ -0,0 +1,85 @@ +package io.quarkus.arc.test.interceptors.postconstruct; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class PostConstructExceptionHandlingTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(SimpleBean.class, + MyInterceptorBinding.class, MyInterceptor1.class, MyInterceptor2.class); + + @Test + public void test() { + IllegalStateException e = assertThrows(IllegalStateException.class, () -> { + Arc.container().instance(SimpleBean.class).get(); + }); + + assertTrue(MyInterceptor1.intercepted); + assertTrue(MyInterceptor2.intercepted); + + assertNotNull(e.getCause()); + assertEquals(IllegalArgumentException.class, e.getCause().getClass()); + assertEquals("intentional", e.getCause().getMessage()); + } + + @Singleton + @MyInterceptorBinding + static class SimpleBean { + } + + @Target({ ElementType.TYPE, ElementType.CONSTRUCTOR }) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor1 { + static boolean intercepted = false; + + @PostConstruct + void postConstruct(InvocationContext ctx) throws Exception { + intercepted = true; + try { + ctx.proceed(); + } catch (IllegalArgumentException e) { + throw new IllegalStateException(e); + } + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(2) + static class MyInterceptor2 { + static boolean intercepted = false; + + @PostConstruct + void postConstruct(InvocationContext ctx) { + intercepted = true; + throw new IllegalArgumentException("intentional"); + } + } +} From 6b04b3c5c732e2ac00876a7e674908a3251ddae8 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 25 Apr 2023 15:57:37 +0200 Subject: [PATCH 171/333] ArC: fix generated Bean.destroy() in case a user directly passes in a client proxy ArC callers of `Bean.destroy()` never pass in a client proxy, but there may be direct callers too, even though this API is low-level. This commit unwraps the client proxy if it was passed to `destroy()` before proceeding, which avoids a `ClassCastException` in case the bean has a `@PreDestroy` interceptor (and hence the contextual instance is a generated subclass). --- .../quarkus/arc/processor/BeanGenerator.java | 11 ++- .../arc/processor/MethodDescriptors.java | 3 + .../src/test/resources/testng.xml | 1 + ...eDestroyInterceptorAndClientProxyTest.java | 80 +++++++++++++++++++ 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 1c6d143763c317..4a020beeeb6af5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -819,6 +819,11 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide if (bean.isClassBean()) { if (!bean.isInterceptor()) { + // in case someone calls `Bean.destroy()` directly (i.e., they use the low-level CDI API), + // they may pass us a client proxy + ResultHandle instance = destroy.invokeStaticInterfaceMethod(MethodDescriptors.CLIENT_PROXY_UNWRAP, + destroy.getMethodParam(0)); + // if there's no `@PreDestroy` interceptor, we'll generate code to invoke `@PreDestroy` callbacks // directly into the `destroy` method: // @@ -846,7 +851,7 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide destroy.invokeVirtualMethod( MethodDescriptor.ofMethod(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), SubclassGenerator.DESTROY_METHOD_NAME, void.class, Runnable.class), - destroy.getMethodParam(0), preDestroyForwarder.getInstance()); + instance, preDestroyForwarder.getInstance()); } // PreDestroy callbacks @@ -865,11 +870,11 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide preDestroyBytecode.loadClass(callback.declaringClass().name().toString()), preDestroyBytecode.load(callback.name()), preDestroyBytecode.newArray(Class.class, preDestroyBytecode.load(0)), - destroy.getMethodParam(0), + instance, preDestroyBytecode.newArray(Object.class, preDestroyBytecode.load(0))); } else { // instance.superCoolDestroyCallback() - preDestroyBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), destroy.getMethodParam(0)); + preDestroyBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), instance); } } if (preDestroyBytecode != destroy) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 4f5ebd821a8ed1..b6f3c26ea216bd 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -145,6 +145,9 @@ public final class MethodDescriptors { public static final MethodDescriptor CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class, ClientProxyGenerator.GET_CONTEXTUAL_INSTANCE_METHOD_NAME, Object.class); + public static final MethodDescriptor CLIENT_PROXY_UNWRAP = MethodDescriptor.ofMethod(ClientProxy.class, + "unwrap", Object.class, Object.class); + public static final MethodDescriptor INJECTABLE_BEAN_DESTROY = MethodDescriptor.ofMethod(InjectableBean.class, "destroy", void.class, Object.class, CreationalContext.class); diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 9f4ccc7df05bf0..eaf1958ef4364a 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -43,6 +43,7 @@ + diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java new file mode 100644 index 00000000000000..87c45c0f8bd3f4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java @@ -0,0 +1,80 @@ +package io.quarkus.arc.test.interceptors.predestroy; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class PreDestroyInterceptorAndClientProxyTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void bagr() { + InstanceHandle handle = Arc.container().instance(MyBean.class); + handle.destroy(); + } + + @Test + public void test() { + BeanManager beanManager = Arc.container().beanManager(); + + Bean bean = (Bean) beanManager.resolve(beanManager.getBeans(MyBean.class)); + CreationalContext ctx = beanManager.createCreationalContext(bean); + + MyBean instance = (MyBean) beanManager.getReference(bean, MyBean.class, ctx); + assertNotNull(instance); + assertInstanceOf(ClientProxy.class, instance); + + assertFalse(MyInterceptor.intercepted); + bean.destroy(instance, ctx); + assertTrue(MyInterceptor.intercepted); + } + + @ApplicationScoped + @MyInterceptorBinding + static class MyBean { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + static boolean intercepted = false; + + @PreDestroy + void intercept(InvocationContext ctx) throws Exception { + intercepted = true; + ctx.proceed(); + } + } +} From d05e504f9af2532ded814ee4e3b4a44d81b616ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 22:10:46 +0000 Subject: [PATCH 172/333] Bump liquibase.version from 4.20.0 to 4.21.0 Bumps `liquibase.version` from 4.20.0 to 4.21.0. Updates `liquibase-core` from 4.20.0 to 4.21.0 - [Release notes](https://github.com/liquibase/liquibase/releases) - [Changelog](https://github.com/liquibase/liquibase/blob/master/changelog.txt) - [Commits](https://github.com/liquibase/liquibase/compare/v4.20.0...v4.21.0) Updates `liquibase-mongodb` from 4.20.0 to 4.21.0 - [Release notes](https://github.com/liquibase/liquibase-mongodb/releases) - [Changelog](https://github.com/liquibase/liquibase-mongodb/blob/main/RELEASE.md) - [Commits](https://github.com/liquibase/liquibase-mongodb/compare/liquibase-mongodb-4.20.0...liquibase-mongodb-4.21.0) --- updated-dependencies: - dependency-name: org.liquibase:liquibase-core dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.liquibase.ext:liquibase-mongodb dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 96fd888c5b7699..50f86592c13c1c 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -166,7 +166,7 @@ 1.1.1 9.16.3 3.0.3 - 4.20.0 + 4.21.0 2.0 6.0.0 4.9.1 From 81176b2a4c53591a44e38023497a06a904979064 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 27 Apr 2023 17:28:57 +0100 Subject: [PATCH 173/333] Use configured principal claim when the token is verified with UserInfo --- .../io/quarkus/oidc/runtime/OidcIdentityProvider.java | 10 +++++++--- .../oidc/runtime/providers/KnownOidcProviders.java | 1 + .../java/io/quarkus/oidc/runtime/OidcUtilsTest.java | 3 +++ .../it/keycloak/CustomSecurityIdentityAugmentor.java | 1 - .../src/main/resources/application.properties | 1 + 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 432dff425676db..7595cf1a04a195 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -261,7 +261,7 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { return Uni.createFrom() .failure(new AuthenticationFailedException("JWT token can not be converted to JSON")); } else { - // ID Token or Bearer access token has been introspected + // ID Token or Bearer access token has been introspected or verified via Userinfo acquisition QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(); builder.addCredential(tokenCred); OidcUtils.setSecurityIdentityUserInfo(builder, userInfo); @@ -270,7 +270,11 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { if (result.introspectionResult == null) { if (resolvedContext.oidcConfig.token.allowOpaqueTokenIntrospection && resolvedContext.oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false)) { - userName = ""; + if (resolvedContext.oidcConfig.token.principalClaim.isPresent() && userInfo != null) { + userName = userInfo.getString(resolvedContext.oidcConfig.token.principalClaim.get()); + } else { + userName = ""; + } } else { // we don't expect this to ever happen LOG.debug("Illegal state - token introspection result is not available."); @@ -297,7 +301,7 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { builder.setPrincipal(new Principal() { @Override public String getName() { - return userName; + return userName != null ? userName : ""; } }); if (userInfo != null) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java index e3e84dc1720410..cd1f3357830548 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java @@ -40,6 +40,7 @@ private static OidcTenantConfig github() { ret.getAuthentication().setUserInfoRequired(true); ret.getAuthentication().setIdTokenRequired(false); ret.getToken().setVerifyAccessTokenWithUserInfo(true); + ret.getToken().setPrincipalClaim("name"); return ret; } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java index 8b6ab511cc7337..ec454f5a7fa496 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java @@ -51,6 +51,7 @@ public void testAcceptGitHubProperties() throws Exception { assertTrue(config.authentication.userInfoRequired.get()); assertTrue(config.token.verifyAccessTokenWithUserInfo.get()); assertEquals(List.of("user:email"), config.authentication.scopes.get()); + assertEquals("name", config.getToken().getPrincipalClaim().get()); } @Test @@ -69,6 +70,7 @@ public void testOverrideGitHubProperties() throws Exception { tenant.authentication.setUserInfoRequired(false); tenant.token.setVerifyAccessTokenWithUserInfo(false); tenant.authentication.setScopes(List.of("write")); + tenant.token.setPrincipalClaim("firstname"); OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.GITHUB)); @@ -84,6 +86,7 @@ public void testOverrideGitHubProperties() throws Exception { assertFalse(config.authentication.userInfoRequired.get()); assertFalse(config.token.verifyAccessTokenWithUserInfo.get()); assertEquals(List.of("write"), config.authentication.scopes.get()); + assertEquals("firstname", config.getToken().getPrincipalClaim().get()); } @Test diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomSecurityIdentityAugmentor.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomSecurityIdentityAugmentor.java index eac19b8572c560..a82fe309c37026 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomSecurityIdentityAugmentor.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomSecurityIdentityAugmentor.java @@ -21,7 +21,6 @@ public Uni augment(SecurityIdentity identity, AuthenticationRe if (routingContext != null && (routingContext.normalizedPath().endsWith("code-flow-user-info-only") || routingContext.normalizedPath().endsWith("code-flow-user-info-github") - || routingContext.normalizedPath().endsWith("bearer-user-info-github-service") || routingContext.normalizedPath().endsWith("code-flow-user-info-dynamic-github") || routingContext.normalizedPath().endsWith("code-flow-token-introspection") || routingContext.normalizedPath().endsWith("code-flow-user-info-github-cached-in-idtoken"))) { diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index c7871fe4c7d1c9..e4b46e66fdb65d 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -73,6 +73,7 @@ quarkus.oidc.code-flow-user-info-github.client-id=quarkus-web-app quarkus.oidc.code-flow-user-info-github.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow quarkus.oidc.bearer-user-info-github-service.provider=github +quarkus.oidc.bearer-user-info-github-service.token.principal-claim=preferred_username quarkus.oidc.bearer-user-info-github-service.token.verify-access-token-with-user-info=true quarkus.oidc.bearer-user-info-github-service.token.allow-jwt-introspection=false quarkus.oidc.bearer-user-info-github-service.application-type=service From a96d221f79d8b9d661c9d6abb7a850110334aef8 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 27 Apr 2023 11:14:48 +0200 Subject: [PATCH 174/333] Taking into account the `@Priority` annotation in context providers At the moment, users can register multiple context providers for handing the same class by using: ```java Client client = QuarkusRestClientBuilder.newBuilder() .baseUri(URI.create(uri)) .register(MyLowPriorityContextProvider.class) .register(MyHighPriorityContextProvider.class) .build(Client.class); ``` When doing this, REST Client Reactive will always return the first registered context provider for the matching type: ```java @Priority(2) public static class MyLowPriorityContextProvider implements ContextResolver { @Override public Person getContext(Class aClass) { // ... } } @Priority(1) public static class MyHighPriorityContextProvider implements ContextResolver { @Override public Person getContext(Class aClass) { // ... } } ``` With these changes, the `@Priority` annotation is taking into account to return the correct context provider regardless to when it has been registered. --- .../ContextProvidersPriorityTest.java | 120 ++++++++++++++++++ .../common/jaxrs/ConfigurationImpl.java | 32 +++-- 2 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java new file mode 100644 index 00000000000000..c10b4923d2f1fe --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java @@ -0,0 +1,120 @@ +package io.quarkus.rest.client.reactive.provider; + +import static io.restassured.RestAssured.given; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.ContextResolver; + +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class ContextProvidersPriorityTest { + private static final String HEADER_NAME = "my-header"; + private static final String HEADER_VALUE_FROM_LOW_PRIORITY = "low-priority"; + private static final String HEADER_VALUE_FROM_HIGH_PRIORITY = "high-priority"; + + @TestHTTPResource + URI baseUri; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(Client.class, TestJacksonBasicMessageBodyReader.class)); + + @Test + void shouldUseTheHighestPriorityContextProvider() { + // @formatter:off + var response = + given() + .body(baseUri.toString()) + .when() + .post("/call-client") + .thenReturn(); + // @formatter:on + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.jsonPath().getString(HEADER_NAME)).isEqualTo(format("[%s]", HEADER_VALUE_FROM_HIGH_PRIORITY)); + } + + @Path("/") + @ApplicationScoped + public static class Resource { + + @GET + @Produces("application/json") + public Map> returnHeaderValues(@Context HttpHeaders headers) { + return headers.getRequestHeaders(); + } + + @Path("/call-client") + @POST + public Map> callClient(String uri) { + Client client = QuarkusRestClientBuilder.newBuilder() + .baseUri(URI.create(uri)) + .register(LowPriorityClientHeadersProvider.class) + .register(HighPriorityClientHeadersProvider.class) + .register(new TestJacksonBasicMessageBodyReader()) + .build(Client.class); + return client.get(); + } + } + + public interface Client { + @GET + Map> get(); + } + + @Priority(2) + public static class LowPriorityClientHeadersProvider implements ContextResolver { + + @Override + public ClientHeadersFactory getContext(Class aClass) { + return new CustomClientHeadersFactory(HEADER_VALUE_FROM_LOW_PRIORITY); + } + } + + @Priority(1) + public static class HighPriorityClientHeadersProvider implements ContextResolver { + + @Override + public ClientHeadersFactory getContext(Class aClass) { + return new CustomClientHeadersFactory(HEADER_VALUE_FROM_HIGH_PRIORITY); + } + } + + public static class CustomClientHeadersFactory implements ClientHeadersFactory { + + private final String value; + + public CustomClientHeadersFactory(String value) { + this.value = value; + } + + @Override + public MultivaluedMap update(MultivaluedMap multivaluedMap, + MultivaluedMap multivaluedMap1) { + MultivaluedHashMap newHeaders = new MultivaluedHashMap<>(); + newHeaders.add(HEADER_NAME, value); + return newHeaders; + } + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java index 33f06b164fd69b..588237450831fb 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java @@ -54,7 +54,7 @@ public class ConfigurationImpl implements Configuration { private final MultivaluedMap, ResourceWriter> resourceWriters; private final MultivaluedMap, ResourceReader> resourceReaders; private final MultivaluedMap, RxInvokerProvider> rxInvokerProviders; - private final MultivaluedMap, ContextResolver> contextResolvers; + private final Map, MultivaluedMap>> contextResolvers; public ConfigurationImpl(RuntimeType runtimeType) { this.runtimeType = runtimeType; @@ -69,7 +69,7 @@ public ConfigurationImpl(RuntimeType runtimeType) { this.resourceReaders = new QuarkusMultivaluedHashMap<>(); this.resourceWriters = new QuarkusMultivaluedHashMap<>(); this.rxInvokerProviders = new QuarkusMultivaluedHashMap<>(); - this.contextResolvers = new QuarkusMultivaluedHashMap<>(); + this.contextResolvers = new HashMap<>(); } public ConfigurationImpl(Configuration configuration) { @@ -96,7 +96,7 @@ public ConfigurationImpl(Configuration configuration) { this.resourceWriters.putAll(configurationImpl.resourceWriters); this.rxInvokerProviders = new QuarkusMultivaluedHashMap<>(); this.rxInvokerProviders.putAll(configurationImpl.rxInvokerProviders); - this.contextResolvers = new QuarkusMultivaluedHashMap<>(); + this.contextResolvers = new HashMap<>(); this.contextResolvers.putAll(configurationImpl.contextResolvers); } else { this.allInstances = new HashMap<>(); @@ -111,7 +111,7 @@ public ConfigurationImpl(Configuration configuration) { this.resourceReaders = new QuarkusMultivaluedHashMap<>(); this.resourceWriters = new QuarkusMultivaluedHashMap<>(); this.rxInvokerProviders = new QuarkusMultivaluedHashMap<>(); - this.contextResolvers = new QuarkusMultivaluedHashMap<>(); + this.contextResolvers = new HashMap<>(); // this is the best we can do - we don't have any of the metadata associated with the registration for (Object i : configuration.getInstances()) { register(i); @@ -314,8 +314,10 @@ private void register(Object component, Integer priority) { added = true; Class componentClass = component.getClass(); Type[] args = Types.findParameterizedTypes(componentClass, ContextResolver.class); - contextResolvers.add(args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class, - (ContextResolver) component); + Class key = args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class; + int effectivePriority = priority != null ? priority : determinePriority(component); + contextResolvers.computeIfAbsent(key, k -> new MultivaluedTreeMap<>()) + .add(effectivePriority, (ContextResolver) component); } if (added) { allInstances.put(component.getClass(), component); @@ -419,8 +421,10 @@ public void register(Object component, Map, Integer> componentContracts if (component instanceof ContextResolver) { added = true; Type[] args = Types.findParameterizedTypes(componentClass, ContextResolver.class); - contextResolvers.add(args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class, - (ContextResolver) component); + Class key = args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class; + int effectivePriority = priority != null ? priority : determinePriority(component); + contextResolvers.computeIfAbsent(key, k -> new MultivaluedTreeMap<>()) + .add(effectivePriority, (ContextResolver) component); } if (added) { allInstances.put(componentClass, component); @@ -525,14 +529,16 @@ public RxInvokerProvider getRxInvokerProvider(Class wantedClass) { } public T getFromContext(Class wantedClass) { - List> candidates = contextResolvers.get(wantedClass); + MultivaluedMap> candidates = contextResolvers.get(wantedClass); if (candidates == null) { return null; } - for (ContextResolver contextResolver : candidates) { - Object instance = contextResolver.getContext(wantedClass); - if (instance != null) { - return (T) instance; + for (List> contextResolvers : candidates.values()) { + for (ContextResolver contextResolver : contextResolvers) { + Object instance = contextResolver.getContext(wantedClass); + if (instance != null) { + return (T) instance; + } } } From c5a8c83aa342ae84ea8ece6a33ff62925153a9a5 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 27 Apr 2023 14:18:13 +0200 Subject: [PATCH 175/333] InjectMock should not create a new contextual instance - resolves #32944 - follows up on #32409 --- .../quarkus/arc/processor/BeanGenerator.java | 10 ++++++ .../java/io/quarkus/arc/InjectableBean.java | 17 +++++++++ .../quarkus/it/mockbean/RequestScopedFoo.java | 25 +++++++++++++ .../it/mockbean/RequestScopedFooMockTest.java | 25 +++++++++++++ .../internal/CreateMockitoMocksCallback.java | 35 +++++-------------- .../internal/CreateMockitoSpiesCallback.java | 13 +++---- 6 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/RequestScopedFoo.java create mode 100644 integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/RequestScopedFooMockTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 4a020beeeb6af5..491a5855b0f396 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -315,6 +315,7 @@ Collection generateSyntheticBean(BeanInfo bean) { implementGetStereotypes(bean, beanCreator, stereotypes.getFieldDescriptor()); } implementGetBeanClass(bean, beanCreator); + implementGetImplementationClass(bean, beanCreator); implementGetName(bean, beanCreator); if (bean.isDefaultBean()) { implementIsDefaultBean(bean, beanCreator); @@ -487,6 +488,7 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc implementGetStereotypes(bean, beanCreator, stereotypes.getFieldDescriptor()); } implementGetBeanClass(bean, beanCreator); + implementGetImplementationClass(bean, beanCreator); implementGetName(bean, beanCreator); if (bean.isDefaultBean()) { implementIsDefaultBean(bean, beanCreator); @@ -567,6 +569,7 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer implementGetStereotypes(bean, beanCreator, stereotypes.getFieldDescriptor()); } implementGetBeanClass(bean, beanCreator); + implementGetImplementationClass(bean, beanCreator); implementGetName(bean, beanCreator); if (bean.isDefaultBean()) { implementIsDefaultBean(bean, beanCreator); @@ -2068,6 +2071,13 @@ protected void implementGetBeanClass(BeanInfo bean, ClassCreator beanCreator) { getBeanClass.returnValue(getBeanClass.loadClass(bean.getBeanClass().toString())); } + protected void implementGetImplementationClass(BeanInfo bean, ClassCreator beanCreator) { + MethodCreator getImplementationClass = beanCreator.getMethodCreator("getImplementationClass", Class.class) + .setModifiers(ACC_PUBLIC); + getImplementationClass.returnValue(bean.getImplClazz() != null ? getImplementationClass.loadClass(bean.getImplClazz()) + : getImplementationClass.loadNull()); + } + protected void implementGetName(BeanInfo bean, ClassCreator beanCreator) { if (bean.getName() != null) { MethodCreator getName = beanCreator.getMethodCreator("getName", String.class) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java index 4e058aa5217543..8e53571ca33925 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java @@ -143,6 +143,23 @@ default int getPriority() { return 0; } + /** + * The return value depends on the {@link #getKind()}. + * + *
      + *
    • For managed beans, interceptors, decorators and built-in beans, the bean class is returned.
    • + *
    • For a producer method, the class of the return type is returned.
    • + *
    • For a producer field, the class of the field is returned.
    • + *
    • For a synthetic bean, the implementation class defined by the registrar is returned. + *
    + * + * @return the implementation class, or null in case of a producer of a primitive type or an array + * @see Kind + */ + default Class getImplementationClass() { + return getBeanClass(); + } + enum Kind { CLASS, diff --git a/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/RequestScopedFoo.java b/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/RequestScopedFoo.java new file mode 100644 index 00000000000000..0eb63a012ca541 --- /dev/null +++ b/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/RequestScopedFoo.java @@ -0,0 +1,25 @@ +package io.quarkus.it.mockbean; + +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.RequestScoped; + +import io.quarkus.arc.Unremovable; + +@Unremovable +@RequestScoped +public class RequestScopedFoo { + + static final AtomicBoolean CONSTRUCTED = new AtomicBoolean(); + + public String ping() { + return "bar"; + } + + @PostConstruct + void init() { + CONSTRUCTED.set(true); + } + +} diff --git a/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/RequestScopedFooMockTest.java b/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/RequestScopedFooMockTest.java new file mode 100644 index 00000000000000..c8f369233216a1 --- /dev/null +++ b/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/RequestScopedFooMockTest.java @@ -0,0 +1,25 @@ +package io.quarkus.it.mockbean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; + +@QuarkusTest +class RequestScopedFooMockTest { + + @InjectMock + RequestScopedFoo foo; + + @Test + void testMock() { + when(foo.ping()).thenReturn("pong"); + assertEquals("pong", foo.ping()); + assertFalse(RequestScopedFoo.CONSTRUCTED.get()); + } + +} diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoMocksCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoMocksCallback.java index 551b92d26ab406..c1998bae889707 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoMocksCallback.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoMocksCallback.java @@ -13,9 +13,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; -import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InstanceHandle; -import io.quarkus.arc.Subclass; import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback; import io.quarkus.test.junit.mockito.InjectMock; @@ -29,11 +27,11 @@ public void afterConstruct(Object testInstance) { InjectMock injectMockAnnotation = field.getAnnotation(InjectMock.class); if (injectMockAnnotation != null) { boolean returnsDeepMocks = injectMockAnnotation.returnsDeepMocks(); - Object contextualReference = getContextualReference(testInstance, field, InjectMock.class); - Optional result = createMockAndSetTestField(testInstance, field, contextualReference, + InstanceHandle beanHandle = getBeanHandle(testInstance, field, InjectMock.class); + Optional result = createMockAndSetTestField(testInstance, field, beanHandle, new MockConfiguration(returnsDeepMocks)); if (result.isPresent()) { - MockitoMocksTracker.track(testInstance, result.get(), contextualReference); + MockitoMocksTracker.track(testInstance, result.get(), beanHandle.get()); } } } @@ -41,12 +39,13 @@ public void afterConstruct(Object testInstance) { } } - private Optional createMockAndSetTestField(Object testInstance, Field field, Object contextualReference, + private Optional createMockAndSetTestField(Object testInstance, Field field, InstanceHandle beanHandle, MockConfiguration mockConfiguration) { - Class implementationClass = getImplementationClass(contextualReference); + Class implementationClass = beanHandle.getBean().getImplementationClass(); Object mock; boolean isNew; - Optional currentMock = MockitoMocksTracker.currentMock(testInstance, contextualReference); + // Note that beanHandle.get() returns a client proxy for normal scoped beans; i.e. the contextual instance is not created + Optional currentMock = MockitoMocksTracker.currentMock(testInstance, beanHandle.get()); if (currentMock.isPresent()) { mock = currentMock.get(); isNew = false; @@ -71,15 +70,7 @@ private Optional createMockAndSetTestField(Object testInstance, Field fi } } - /** - * Contextual reference of a normal scoped bean is a client proxy. - * - * @param testInstance - * @param field - * @param annotationType - * @return a contextual reference of a bean - */ - static Object getContextualReference(Object testInstance, Field field, Class annotationType) { + static InstanceHandle getBeanHandle(Object testInstance, Field field, Class annotationType) { Type fieldType = field.getGenericType(); ArcContainer container = Arc.container(); BeanManager beanManager = container.beanManager(); @@ -100,15 +91,7 @@ static Object getContextualReference(Object testInstance, Field field, Class getImplementationClass(Object contextualReference) { - // Unwrap the client proxy if needed - Object contextualInstance = ClientProxy.unwrap(contextualReference); - // If the contextual instance is an intercepted subclass then mock the extended implementation class - return contextualInstance instanceof Subclass ? contextualInstance.getClass().getSuperclass() - : contextualInstance.getClass(); + return handle; } static Annotation[] getQualifiers(Field fieldToMock, BeanManager beanManager) { diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java index 9566411a80f2bb..8db9c5b31ec46a 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java @@ -6,6 +6,7 @@ import org.mockito.Mockito; import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InstanceHandle; import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback; import io.quarkus.test.junit.mockito.InjectSpy; @@ -18,22 +19,22 @@ public void afterConstruct(Object testInstance) { for (Field field : current.getDeclaredFields()) { InjectSpy injectSpyAnnotation = field.getAnnotation(InjectSpy.class); if (injectSpyAnnotation != null) { - Object contextualReference = CreateMockitoMocksCallback.getContextualReference(testInstance, field, + InstanceHandle beanHandle = CreateMockitoMocksCallback.getBeanHandle(testInstance, field, InjectSpy.class); - Object spy = createSpyAndSetTestField(testInstance, field, contextualReference, + Object spy = createSpyAndSetTestField(testInstance, field, beanHandle, injectSpyAnnotation.delegate()); - MockitoMocksTracker.track(testInstance, spy, contextualReference); + MockitoMocksTracker.track(testInstance, spy, beanHandle.get()); } } current = current.getSuperclass(); } } - private Object createSpyAndSetTestField(Object testInstance, Field field, Object contextualReference, boolean delegate) { + private Object createSpyAndSetTestField(Object testInstance, Field field, InstanceHandle beanHandle, boolean delegate) { Object spy; - Object contextualInstance = ClientProxy.unwrap(contextualReference); + Object contextualInstance = ClientProxy.unwrap(beanHandle.get()); if (delegate) { - spy = Mockito.mock(CreateMockitoMocksCallback.getImplementationClass(contextualReference), + spy = Mockito.mock(beanHandle.getBean().getImplementationClass(), AdditionalAnswers.delegatesTo(contextualInstance)); } else { // Unwrap the client proxy if needed From da9425f9b5788c77f24da124bdaadd986aeccf0b Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 27 Apr 2023 18:43:22 +0100 Subject: [PATCH 176/333] Make it simpler to get well-known properties from UserInfo --- .../main/java/io/quarkus/oidc/UserInfo.java | 31 +++++++++++++++ .../io/quarkus/oidc/runtime/UserInfoTest.java | 39 ++++++++++++++++++- .../it/keycloak/CodeFlowUserInfoResource.java | 2 +- 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/UserInfo.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/UserInfo.java index 3b8fc89fd81669..ae2da99e3f5d91 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/UserInfo.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/UserInfo.java @@ -2,10 +2,17 @@ import jakarta.json.JsonObject; +import org.eclipse.microprofile.jwt.Claims; + import io.quarkus.oidc.runtime.AbstractJsonObjectResponse; public class UserInfo extends AbstractJsonObjectResponse { + private static final String EMAIL = "email"; + private static final String NAME = "name"; + private static final String FIRST_NAME = "first_name"; + private static final String FAMILY_NAME = "family_name"; + public UserInfo() { } @@ -20,4 +27,28 @@ public UserInfo(JsonObject json) { public String getUserInfoString() { return getNonNullJsonString(); } + + public String getName() { + return getString(NAME); + } + + public String getFirstName() { + return getString(FIRST_NAME); + } + + public String getFamilyName() { + return getString(FAMILY_NAME); + } + + public String getPreferredUserName() { + return getString(Claims.preferred_username.name()); + } + + public String getSubject() { + return getString(Claims.sub.name()); + } + + public String getEmail() { + return getString(EMAIL); + } } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java index dd28befc9a69f8..099120d147f72c 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java @@ -15,14 +15,49 @@ public class UserInfoTest { UserInfo userInfo = new UserInfo( "{" + + "\"sub\": \"alice123456\"," + "\"name\": \"alice\"," + + "\"first_name\": \"Alice\"," + + "\"family_name\": \"Alice\"," + + "\"preferred_username\": \"Alice Alice\"," + + "\"email\": \"alice@email.com\"," + "\"admin\": true," - + "\"email\": null," + + "\"custom\": null," + "\"id\": 1234," + "\"permissions\": [\"read\", \"write\"]," + "\"scopes\": {\"scope\": \"see\"}" + "}"); + @Test + public void testGetName() { + assertEquals("alice", userInfo.getName()); + } + + @Test + public void testGetFirstName() { + assertEquals("Alice", userInfo.getFirstName()); + } + + @Test + public void testGetFamilyName() { + assertEquals("Alice", userInfo.getFamilyName()); + } + + @Test + public void testPreferredName() { + assertEquals("Alice Alice", userInfo.getPreferredUserName()); + } + + @Test + public void testGetEmail() { + assertEquals("alice@email.com", userInfo.getEmail()); + } + + @Test + public void testGetSubject() { + assertEquals("alice123456", userInfo.getSubject()); + } + @Test public void testGetString() { assertEquals("alice", userInfo.getString("name")); @@ -62,6 +97,6 @@ public void testGetObject() { @Test public void testGetNullProperty() { - assertNull(userInfo.getString("email")); + assertNull(userInfo.getString("custom")); } } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java index ba2f63d9911aad..a653e097e346f9 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java @@ -33,7 +33,7 @@ public class CodeFlowUserInfoResource { public String access() { int cacheSize = tokenCache.getCacheSize(); tokenCache.clearCache(); - return identity.getPrincipal().getName() + ":" + userInfo.getString("preferred_username") + ":" + accessToken.getName() + return identity.getPrincipal().getName() + ":" + userInfo.getPreferredUserName() + ":" + accessToken.getName() + ", cache size: " + cacheSize; } From f8ef380487be9ebc724de732101c10e817f4e9ec Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 27 Apr 2023 22:41:39 +0100 Subject: [PATCH 177/333] Have OIDC DevUI web-app function included with quarkus.oidc.provider --- .../deployment/src/main/resources/dev-templates/provider.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html index 6f17c20060e04d..2e459b2375ce1b 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html +++ b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html @@ -291,9 +291,7 @@ {/if} -{/if} - -{#if info:oidcApplicationType is 'web-app'} +{#else} function signInToService(servicePath) { window.open("http://localhost:" + port + servicePath); } From 0eda9cfc647f6b14565a77005078c4e08355895f Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 28 Apr 2023 10:22:09 +0200 Subject: [PATCH 178/333] Fix #32968 --- extensions/kafka-client/runtime/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/kafka-client/runtime/pom.xml b/extensions/kafka-client/runtime/pom.xml index c7151b6dba3a64..b362ea3e9ee9f9 100644 --- a/extensions/kafka-client/runtime/pom.xml +++ b/extensions/kafka-client/runtime/pom.xml @@ -62,6 +62,8 @@ io.quarkus quarkus-vertx-http-dev-console-runtime-spi + + true From 54b6060c9fd2735f6fffa3f446a7dbae93f7d44b Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Fri, 28 Apr 2023 11:11:41 +0200 Subject: [PATCH 179/333] Gradle Enterprise config Why: * allow to track build scans in gradle enterprise at ge.quarkus.io This change addreses the need by: * enable gradle enterprise via mvn extension * only submits if key configured (no automatic submission by default) * enable build scan for incremental builds in PR's * docs in CONTRIBUTING.md on how to use local build cache if interested --- .github/workflows/ci-actions-incremental.yml | 2 ++ .gitignore | 1 + .mvn/extensions.xml | 12 +++++++ .../gradle-enterprise-custom-user-data.groovy | 31 +++++++++++++++++++ .mvn/gradle-enterprise.xml | 28 +++++++++++++++++ CONTRIBUTING.md | 15 +++++++++ README.md | 1 + integration-tests/pom.xml | 26 ++++++++++++++++ 8 files changed, 116 insertions(+) create mode 100644 .mvn/extensions.xml create mode 100644 .mvn/gradle-enterprise-custom-user-data.groovy create mode 100644 .mvn/gradle-enterprise.xml diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 89765207d8c9bb..5b1e19dbd410da 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -48,6 +48,8 @@ env: DB_USER: hibernate_orm_test DB_PASSWORD: hibernate_orm_test DB_NAME: hibernate_orm_test + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + PULL_REQUEST_NUMBER: ${{ github.event.number }} jobs: # This is a hack to work around a GitHub API limitation: # when the PR is coming from another fork, the pull_requests field of the diff --git a/.gitignore b/.gitignore index 02c9ba47e57299..1c2921f653f41d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ nb-configuration.xml /lsp/ .envrc .jekyll-cache +.mvn/.gradle-enterprise diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 00000000000000..769e62e63579e8 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,12 @@ + + + com.gradle + gradle-enterprise-maven-extension + 1.17 + + + com.gradle + common-custom-user-data-maven-extension + 1.11.1 + + \ No newline at end of file diff --git a/.mvn/gradle-enterprise-custom-user-data.groovy b/.mvn/gradle-enterprise-custom-user-data.groovy new file mode 100644 index 00000000000000..687cc903c54770 --- /dev/null +++ b/.mvn/gradle-enterprise-custom-user-data.groovy @@ -0,0 +1,31 @@ + + +// Add mvn command line +def mvnCommand = '' +if (System.env.MAVEN_CMD_LINE_ARGS) { + mvnCommand = "mvn ${System.env.MAVEN_CMD_LINE_ARGS}".toString() + buildScan.value('mvn command line', mvnCommand) +} + +//Add github action information +if (System.env.GITHUB_ACTIONS) { + buildScan.value('gh-job-name', System.env.GITHUB_JOB) + buildScan.value('gh-event-name', System.env.GITHUB_EVENT_NAME) + buildScan.value('gh-ref-name', System.env.GITHUB_REF_NAME) + buildScan.value('gh-actor', System.env.GITHUB_ACTOR) + buildScan.value('gh-workflow', System.env.GITHUB_WORKFLOW) + + + def prnumber = System.env.PULL_REQUEST_NUMBER + if (prnumber != null) { + buildScan.value('gh-pr', prnumber) + buildScan.tag('pr-' + prnumber) + } + + buildScan.buildScanPublished { publishedBuildScan -> + new File(System.env.GITHUB_STEP_SUMMARY).withWriterAppend { out -> + out.println("\n[Build scan for '${mavenCommand}' in ${jobName}](${publishedBuildScan.buildScanUri})\n") + } + } +} + diff --git a/.mvn/gradle-enterprise.xml b/.mvn/gradle-enterprise.xml new file mode 100644 index 00000000000000..b1caa0a015c22b --- /dev/null +++ b/.mvn/gradle-enterprise.xml @@ -0,0 +1,28 @@ + + + https://ge.quarkus.io + false + + + + ALWAYS + + + #{{'0.0.0.0'}} + + + true + + + #{env['CI'] == null} + true + + + false + false + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a630125f7f292..cdc62de5e2e817 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -503,6 +503,21 @@ CI is using a slightly different GIB config than locally: For more details see the `Get GIB arguments` step in `.github/workflows/ci-actions-incremental.yml`. +##### Gradle Enterprise build cache + +Quarkus has a Gradle Enterprise setup at https://ge.quarkus.io that can be used to analyze the build performance of the Quarkus project. + +Locally you can use `-Dgradle.cache.local.enabled=true` to enable the local Gradle Enterprise cache. This can speed up the build significantly. It is still considered experimental but can be used for local development. + +If you have a need or interest to report build times, you will need to get an API key for the GE instance. It is mainly relevant for those working on optimizing the Quarkus build. Ping on quarkus-dev mailing list or on Zulip if you need one. + +When you have the account setup you run `mvn gradle-enterprise:provision-access-key` and login - from then on build time info will be sent to the GE instance. +You can alternatively also generate an API key from the GE UI and then use an environment variable like this: + +``` +export GRADLE_ENTERPRISE_ACCESS_KEY=ge.quarkus.io=a_secret_key +``` + ## Release your own version You might want to release your own patched version of Quarkus to an internal repository. diff --git a/README.md b/README.md index d5166d384634f7..824db390dbffec 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![Project Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?style=for-the-badge&logo=zulip)](https://quarkusio.zulipchat.com/) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?style=for-the-badge&logo=gitpod&logoColor=white)](https://gitpod.io/#https://github.com/quarkusio/quarkus/-/tree/main/) [![Supported JVM Versions](https://img.shields.io/badge/JVM-11--17--19-brightgreen.svg?style=for-the-badge&logo=openjdk)](https://github.com/quarkusio/quarkus/actions/runs/113853915/) +[![Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?style=for-the-badge&logo=gradle)](https://ge.quarkus.io/scans) # Quarkus - Supersonic Subatomic Java diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 8e9707113a372e..93b5cb20923b20 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -82,6 +82,32 @@ + + + + com.gradle + gradle-enterprise-maven-extension + + + + + maven-compiler-plugin + + + + specs + + ${project.basedir}/disable-unbind-executions + + RELATIVE_PATH + + + + + + + + From 6084bab01f99366d4bade076e9f632db21fdff7e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 28 Apr 2023 16:13:49 +0300 Subject: [PATCH 180/333] Exclude JS files from resource filtering Fixes: #32956 --- build-parent/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index adf6cd699d7240..2ceaf41edc5be6 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -639,6 +639,7 @@ woff2 svg ico + js From c7a323b0d887425986e60e54c36ef60953211d4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 22:01:07 +0000 Subject: [PATCH 181/333] Bump gizmo from 1.6.0.Final to 1.6.1.Final Bumps [gizmo](https://github.com/quarkusio/gizmo) from 1.6.0.Final to 1.6.1.Final. - [Release notes](https://github.com/quarkusio/gizmo/releases) - [Commits](https://github.com/quarkusio/gizmo/compare/1.6.0.Final...1.6.1.Final) --- updated-dependencies: - dependency-name: io.quarkus.gizmo:gizmo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 50f86592c13c1c..40da198e2cf5fd 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -91,7 +91,7 @@ 2.1.0 22.3.0 ${graal-sdk.version} - 1.6.0.Final + 1.6.1.Final 2.14.2 1.0.0.Final 3.12.0 diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index daeaaf48730537..d2c9db7788a23d 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -46,7 +46,7 @@ 3.0.0 2.0.1 - 1.6.0.Final + 1.6.1.Final 3.1.1 3.5.0.Final 2.1.0 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 34a676494853a7..2e61981edcd3b4 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -43,7 +43,7 @@ 5.9.2 3.24.2 3.1.1 - 1.6.0.Final + 1.6.1.Final 3.5.0.Final 3.11.0 3.2.1 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index ccbf35acef87c7..224bcc7f78e66b 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -54,7 +54,7 @@ 3.24.2 3.5.0.Final 2.1.1 - 1.6.0.Final + 1.6.1.Final 3.0.0 3.11.0 From 6cf647c7cbd3739d1309acfac9073c419669a3a5 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 29 Mar 2023 14:02:00 -0300 Subject: [PATCH 182/333] Cleanup Flyway Config --- .../flyway/FlywayCallbacksLocator.java | 2 +- .../io/quarkus/flyway/FlywayProcessor.java | 2 +- extensions/flyway/runtime/pom.xml | 5 + .../flyway/runtime/FlywayBuildTimeConfig.java | 29 +++-- .../runtime/FlywayContainerProducer.java | 4 +- .../quarkus/flyway/runtime/FlywayCreator.java | 84 ++++++------ .../FlywayDataSourceBuildTimeConfig.java | 29 ++--- .../FlywayDataSourceRuntimeConfig.java | 122 +++++++----------- .../flyway/runtime/FlywayRecorder.java | 2 +- .../flyway/runtime/FlywayRuntimeConfig.java | 32 +++-- .../devconsole/FlywayDevConsoleRecorder.java | 4 +- .../flyway/runtime/FlywayCreatorTest.java | 105 +++++++++------ 12 files changed, 206 insertions(+), 214 deletions(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java index 8d90513e8de457..b4e0f4fbc9cd5b 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java @@ -76,7 +76,7 @@ public Map> getCallbacks() */ private Collection callbacksForDataSource(String dataSourceName) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException { - final Optional> callbackConfig = flywayBuildConfig.getConfigForDataSourceName(dataSourceName).callbacks; + final Optional> callbackConfig = flywayBuildConfig.getConfigForDataSourceName(dataSourceName).callbacks(); if (!callbackConfig.isPresent()) { return Collections.emptyList(); } diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index ce3fa9db00e214..b107b878e2b199 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -99,7 +99,7 @@ MigrationStateBuildItem build(BuildProducer featureProducer, Map> applicationMigrationsToDs = new HashMap<>(); for (var i : dataSourceNames) { Collection migrationLocations = discoverApplicationMigrations( - flywayBuildConfig.getConfigForDataSourceName(i).locations); + flywayBuildConfig.getConfigForDataSourceName(i).locations()); applicationMigrationsToDs.put(i, migrationLocations); } Set datasourcesWithMigrations = new HashSet<>(); diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml index c09e75f82456d3..e49a44b3d13a71 100644 --- a/extensions/flyway/runtime/pom.xml +++ b/extensions/flyway/runtime/pom.xml @@ -55,6 +55,11 @@ quarkus-junit5-internal test + + io.quarkus + quarkus-junit5-mockito + test + diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java index 47e7a4878c7f4e..58ae166ac6531d 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java @@ -1,35 +1,40 @@ package io.quarkus.flyway.runtime; -import java.util.Collections; import java.util.Map; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithParentName; -@ConfigRoot(name = "flyway", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) -public final class FlywayBuildTimeConfig { +@ConfigMapping(prefix = "quarkus.flyway") +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public interface FlywayBuildTimeConfig { /** * Gets the {@link FlywayDataSourceBuildTimeConfig} for the given datasource name. */ - public FlywayDataSourceBuildTimeConfig getConfigForDataSourceName(String dataSourceName) { + default FlywayDataSourceBuildTimeConfig getConfigForDataSourceName(String dataSourceName) { if (DataSourceUtil.isDefault(dataSourceName)) { - return defaultDataSource; + return defaultDataSource(); } - return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceBuildTimeConfig.defaultConfig()); + FlywayDataSourceBuildTimeConfig config = namedDataSources().get(dataSourceName); + if (config == null) { + config = defaultDataSource(); + } + return config; } /** * Flyway configuration for the default datasource. */ - @ConfigItem(name = ConfigItem.PARENT) - public FlywayDataSourceBuildTimeConfig defaultDataSource; + @WithParentName + FlywayDataSourceBuildTimeConfig defaultDataSource(); /** * Flyway configurations for named datasources. */ - @ConfigItem(name = ConfigItem.PARENT) - public Map namedDataSources = Collections.emptyMap(); -} \ No newline at end of file + @WithParentName + Map namedDataSources(); +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java index d58bb692839ed9..9149f5f4b22a7d 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java @@ -51,8 +51,8 @@ public FlywayContainer createFlyway(DataSource dataSource, String dataSourceName final Flyway flyway = new FlywayCreator(matchingRuntimeConfig, matchingBuildTimeConfig, matchingConfigCustomizers( configCustomizerInstances, dataSourceName)).withCallbacks(callbacks) .createFlyway(dataSource); - return new FlywayContainer(flyway, matchingRuntimeConfig.cleanAtStart, matchingRuntimeConfig.migrateAtStart, - matchingRuntimeConfig.repairAtStart, matchingRuntimeConfig.validateAtStart, + return new FlywayContainer(flyway, matchingRuntimeConfig.cleanAtStart(), matchingRuntimeConfig.migrateAtStart(), + matchingRuntimeConfig.repairAtStart(), matchingRuntimeConfig.validateAtStart(), dataSourceName, hasMigrations, createPossible); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java index 844be7c8fb053e..863c4e895a4459 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java @@ -49,63 +49,63 @@ public FlywayCreator withCallbacks(Collection callbacks) { public Flyway createFlyway(DataSource dataSource) { FluentConfiguration configure = Flyway.configure(); - if (flywayRuntimeConfig.jdbcUrl.isPresent()) { - if (flywayRuntimeConfig.username.isPresent() && flywayRuntimeConfig.password.isPresent()) { - configure.dataSource(flywayRuntimeConfig.jdbcUrl.get(), flywayRuntimeConfig.username.get(), - flywayRuntimeConfig.password.get()); + if (flywayRuntimeConfig.jdbcUrl().isPresent()) { + if (flywayRuntimeConfig.username().isPresent() && flywayRuntimeConfig.password().isPresent()) { + configure.dataSource(flywayRuntimeConfig.jdbcUrl().get(), flywayRuntimeConfig.username().get(), + flywayRuntimeConfig.password().get()); } else { throw new ConfigurationException( "Username and password must be defined when a JDBC URL is provided in the Flyway configuration"); } } else { - if (flywayRuntimeConfig.username.isPresent() && flywayRuntimeConfig.password.isPresent()) { + if (flywayRuntimeConfig.username().isPresent() && flywayRuntimeConfig.password().isPresent()) { AgroalDataSource agroalDataSource = (AgroalDataSource) dataSource; String jdbcUrl = agroalDataSource.getConfiguration().connectionPoolConfiguration() .connectionFactoryConfiguration().jdbcUrl(); - configure.dataSource(jdbcUrl, flywayRuntimeConfig.username.get(), - flywayRuntimeConfig.password.get()); + configure.dataSource(jdbcUrl, flywayRuntimeConfig.username().get(), + flywayRuntimeConfig.password().get()); } else { configure.dataSource(dataSource); } } - if (flywayRuntimeConfig.initSql.isPresent()) { - configure.initSql(flywayRuntimeConfig.initSql.get()); + if (flywayRuntimeConfig.initSql().isPresent()) { + configure.initSql(flywayRuntimeConfig.initSql().get()); } - if (flywayRuntimeConfig.connectRetries.isPresent()) { - configure.connectRetries(flywayRuntimeConfig.connectRetries.getAsInt()); + if (flywayRuntimeConfig.connectRetries().isPresent()) { + configure.connectRetries(flywayRuntimeConfig.connectRetries().getAsInt()); } - if (flywayRuntimeConfig.defaultSchema.isPresent()) { - configure.defaultSchema(flywayRuntimeConfig.defaultSchema.get()); + if (flywayRuntimeConfig.defaultSchema().isPresent()) { + configure.defaultSchema(flywayRuntimeConfig.defaultSchema().get()); } - if (flywayRuntimeConfig.schemas.isPresent()) { - configure.schemas(flywayRuntimeConfig.schemas.get().toArray(EMPTY_ARRAY)); + if (flywayRuntimeConfig.schemas().isPresent()) { + configure.schemas(flywayRuntimeConfig.schemas().get().toArray(EMPTY_ARRAY)); } - if (flywayRuntimeConfig.table.isPresent()) { - configure.table(flywayRuntimeConfig.table.get()); + if (flywayRuntimeConfig.table().isPresent()) { + configure.table(flywayRuntimeConfig.table().get()); } - configure.locations(flywayBuildTimeConfig.locations.toArray(EMPTY_ARRAY)); - if (flywayRuntimeConfig.sqlMigrationPrefix.isPresent()) { - configure.sqlMigrationPrefix(flywayRuntimeConfig.sqlMigrationPrefix.get()); + configure.locations(flywayBuildTimeConfig.locations().toArray(EMPTY_ARRAY)); + if (flywayRuntimeConfig.sqlMigrationPrefix().isPresent()) { + configure.sqlMigrationPrefix(flywayRuntimeConfig.sqlMigrationPrefix().get()); } - if (flywayRuntimeConfig.repeatableSqlMigrationPrefix.isPresent()) { - configure.repeatableSqlMigrationPrefix(flywayRuntimeConfig.repeatableSqlMigrationPrefix.get()); + if (flywayRuntimeConfig.repeatableSqlMigrationPrefix().isPresent()) { + configure.repeatableSqlMigrationPrefix(flywayRuntimeConfig.repeatableSqlMigrationPrefix().get()); } - configure.cleanDisabled(flywayRuntimeConfig.cleanDisabled); - configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate); - configure.validateOnMigrate(flywayRuntimeConfig.validateOnMigrate); - configure.validateMigrationNaming(flywayRuntimeConfig.validateMigrationNaming); + configure.cleanDisabled(flywayRuntimeConfig.cleanDisabled()); + configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate()); + configure.validateOnMigrate(flywayRuntimeConfig.validateOnMigrate()); + configure.validateMigrationNaming(flywayRuntimeConfig.validateMigrationNaming()); final String[] ignoreMigrationPatterns; - if (flywayRuntimeConfig.ignoreMigrationPatterns.isPresent()) { - ignoreMigrationPatterns = flywayRuntimeConfig.ignoreMigrationPatterns.get(); + if (flywayRuntimeConfig.ignoreMigrationPatterns().isPresent()) { + ignoreMigrationPatterns = flywayRuntimeConfig.ignoreMigrationPatterns().get(); } else { List patterns = new ArrayList<>(2); - if (flywayRuntimeConfig.ignoreMissingMigrations) { + if (flywayRuntimeConfig.ignoreMissingMigrations()) { patterns.add("*:Missing"); } - if (flywayRuntimeConfig.ignoreFutureMigrations) { + if (flywayRuntimeConfig.ignoreFutureMigrations()) { patterns.add("*:Future"); } // Default is *:Future @@ -113,21 +113,21 @@ public Flyway createFlyway(DataSource dataSource) { } configure.ignoreMigrationPatterns(ignoreMigrationPatterns); - configure.cleanOnValidationError(flywayRuntimeConfig.cleanOnValidationError); - configure.outOfOrder(flywayRuntimeConfig.outOfOrder); - if (flywayRuntimeConfig.baselineVersion.isPresent()) { - configure.baselineVersion(flywayRuntimeConfig.baselineVersion.get()); + configure.cleanOnValidationError(flywayRuntimeConfig.cleanOnValidationError()); + configure.outOfOrder(flywayRuntimeConfig.outOfOrder()); + if (flywayRuntimeConfig.baselineVersion().isPresent()) { + configure.baselineVersion(flywayRuntimeConfig.baselineVersion().get()); } - if (flywayRuntimeConfig.baselineDescription.isPresent()) { - configure.baselineDescription(flywayRuntimeConfig.baselineDescription.get()); + if (flywayRuntimeConfig.baselineDescription().isPresent()) { + configure.baselineDescription(flywayRuntimeConfig.baselineDescription().get()); } - configure.placeholders(flywayRuntimeConfig.placeholders); - configure.createSchemas(flywayRuntimeConfig.createSchemas); - if (flywayRuntimeConfig.placeholderPrefix.isPresent()) { - configure.placeholderPrefix(flywayRuntimeConfig.placeholderPrefix.get()); + configure.placeholders(flywayRuntimeConfig.placeholders()); + configure.createSchemas(flywayRuntimeConfig.createSchemas()); + if (flywayRuntimeConfig.placeholderPrefix().isPresent()) { + configure.placeholderPrefix(flywayRuntimeConfig.placeholderPrefix().get()); } - if (flywayRuntimeConfig.placeholderSuffix.isPresent()) { - configure.placeholderSuffix(flywayRuntimeConfig.placeholderSuffix.get()); + if (flywayRuntimeConfig.placeholderSuffix().isPresent()) { + configure.placeholderSuffix(flywayRuntimeConfig.placeholderSuffix().get()); } if (!callbacks.isEmpty()) { configure.callbacks(callbacks.toArray(new Callback[0])); diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java index 7668f4ca191ea3..fa6382cf513aaf 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java @@ -1,18 +1,17 @@ package io.quarkus.flyway.runtime; -import java.util.Collections; import java.util.List; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConvertWith; import io.quarkus.runtime.configuration.TrimmedStringConverter; +import io.smallrye.config.WithConverter; +import io.smallrye.config.WithDefault; @ConfigGroup -public final class FlywayDataSourceBuildTimeConfig { +public interface FlywayDataSourceBuildTimeConfig { - private static final String DEFAULT_LOCATION = "db/migration"; + String DEFAULT_LOCATION = "db/migration"; /** * Comma-separated list of locations to scan recursively for migrations. The location type is determined by its prefix. @@ -23,9 +22,9 @@ public final class FlywayDataSourceBuildTimeConfig { * Locations starting with filesystem: point to a directory on the filesystem, may only contain SQL migrations and are only * scanned recursively down non-hidden directories. */ - @ConfigItem(defaultValue = DEFAULT_LOCATION) - @ConvertWith(TrimmedStringConverter.class) - public List locations; + @WithDefault(DEFAULT_LOCATION) + @WithConverter(TrimmedStringConverter.class) + List locations(); /** * Comma-separated list of fully qualified class names of Callback implementations @@ -33,17 +32,5 @@ public final class FlywayDataSourceBuildTimeConfig { * The {@link org.flywaydb.core.api.callback.Callback} subclass must have a no-args constructor and must not be abstract. * These classes must also not have any fields that hold state (unless that state is initialized in the constructor). */ - @ConfigItem - public Optional> callbacks = Optional.empty(); - - /** - * Creates a {@link FlywayDataSourceBuildTimeConfig} with default settings. - * - * @return {@link FlywayDataSourceBuildTimeConfig} - */ - public static FlywayDataSourceBuildTimeConfig defaultConfig() { - FlywayDataSourceBuildTimeConfig defaultConfig = new FlywayDataSourceBuildTimeConfig(); - defaultConfig.locations = Collections.singletonList(DEFAULT_LOCATION); - return defaultConfig; - } + Optional> callbacks(); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java index 5f63532ea1e2ec..625a13025ca347 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java @@ -1,32 +1,21 @@ package io.quarkus.flyway.runtime; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; +import io.smallrye.config.WithDefault; @ConfigGroup -public final class FlywayDataSourceRuntimeConfig { - - /** - * Creates a {@link FlywayDataSourceRuntimeConfig} with default settings. - * - * @return {@link FlywayDataSourceRuntimeConfig} - */ - public static FlywayDataSourceRuntimeConfig defaultConfig() { - return new FlywayDataSourceRuntimeConfig(); - } +public interface FlywayDataSourceRuntimeConfig { /** * The maximum number of retries when attempting to connect to the database. After each failed attempt, Flyway will wait 1 * second before attempting to connect again, up to the maximum number of times specified by connectRetries. */ - @ConfigItem - public OptionalInt connectRetries = OptionalInt.empty(); + OptionalInt connectRetries(); /** * Sets the default schema managed by Flyway. This schema name is case-sensitive. If not specified, but schemas @@ -40,37 +29,32 @@ public static FlywayDataSourceRuntimeConfig defaultConfig() { *
  • This schema will be the default for the database connection (provided the database supports this concept).
  • * */ - @ConfigItem - public Optional defaultSchema = Optional.empty(); + Optional defaultSchema(); /** * The JDBC URL that Flyway uses to connect to the database. * Falls back to the datasource URL if not specified. */ - @ConfigItem - public Optional jdbcUrl = Optional.empty(); + Optional jdbcUrl(); /** * The username that Flyway uses to connect to the database. * If no specific JDBC URL is configured, falls back to the datasource username if not specified. */ - @ConfigItem - public Optional username = Optional.empty(); + Optional username(); /** * The password that Flyway uses to connect to the database. * If no specific JDBC URL is configured, falls back to the datasource password if not specified. */ - @ConfigItem - public Optional password = Optional.empty(); + Optional password(); /** * Comma-separated case-sensitive list of schemas managed by Flyway. * The first schema in the list will be automatically set as the default one during the migration. * It will also be the one containing the schema history table. */ - @ConfigItem - public Optional> schemas = Optional.empty(); + Optional> schemas(); /** * The name of Flyway's schema history table. @@ -79,149 +63,136 @@ public static FlywayDataSourceRuntimeConfig defaultConfig() { * When the flyway.schemas property is set (multi-schema mode), the schema history table is placed in the first schema of * the list. */ - @ConfigItem - public Optional table = Optional.empty(); + Optional table(); /** * The file name prefix for versioned SQL migrations. - * + *

    * Versioned SQL migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix , which using * the defaults translates to V1.1__My_description.sql */ - @ConfigItem - public Optional sqlMigrationPrefix = Optional.empty(); + Optional sqlMigrationPrefix(); /** * The file name prefix for repeatable SQL migrations. - * + *

    * Repeatable SQL migrations have the following file name structure: prefixSeparatorDESCRIPTIONsuffix , which using the * defaults translates to R__My_description.sql */ - @ConfigItem - public Optional repeatableSqlMigrationPrefix = Optional.empty(); + Optional repeatableSqlMigrationPrefix(); /** * true to execute Flyway clean command automatically when the application starts, false otherwise. - * */ - @ConfigItem - public boolean cleanAtStart; + @WithDefault("false") + boolean cleanAtStart(); /** * true to prevent Flyway clean operations, false otherwise. */ - @ConfigItem - public boolean cleanDisabled; + @WithDefault("false") + boolean cleanDisabled(); /** * true to automatically call clean when a validation error occurs, false otherwise. */ - @ConfigItem - public boolean cleanOnValidationError; + @WithDefault("false") + boolean cleanOnValidationError(); /** * true to execute Flyway automatically when the application starts, false otherwise. - * */ - @ConfigItem - public boolean migrateAtStart; + @WithDefault("false") + boolean migrateAtStart(); /** * true to execute a Flyway repair command when the application starts, false otherwise. - * */ - @ConfigItem - public boolean repairAtStart; + @WithDefault("false") + boolean repairAtStart(); /** * true to execute a Flyway validate command when the application starts, false otherwise. - * */ - @ConfigItem - public boolean validateAtStart; + @WithDefault("false") + boolean validateAtStart(); /** * Enable the creation of the history table if it does not exist already. */ - @ConfigItem - public boolean baselineOnMigrate; + @WithDefault("false") + boolean baselineOnMigrate(); /** * The initial baseline version. */ - @ConfigItem - public Optional baselineVersion = Optional.empty(); + Optional baselineVersion(); /** * The description to tag an existing schema with when executing baseline. */ - @ConfigItem - public Optional baselineDescription = Optional.empty(); + Optional baselineDescription(); /** * Whether to automatically call validate when performing a migration. */ - @ConfigItem(defaultValue = "true") - public boolean validateOnMigrate = true; + @WithDefault("true") + boolean validateOnMigrate(); /** * Allows migrations to be run "out of order". */ - @ConfigItem - public boolean outOfOrder; + @WithDefault("false") + boolean outOfOrder(); /** * Ignore missing migrations when reading the history table. When set to true migrations from older versions present in the * history table but absent in the configured locations will be ignored (and logged as a warning), when false (the default) * the validation step will fail. */ - @ConfigItem - public boolean ignoreMissingMigrations; + @WithDefault("false") + boolean ignoreMissingMigrations(); /** * Ignore future migrations when reading the history table. When set to true migrations from newer versions present in the * history table but absent in the configured locations will be ignored (and logged as a warning), when false (the default) * the validation step will fail. */ - @ConfigItem - public boolean ignoreFutureMigrations; + @WithDefault("false") + boolean ignoreFutureMigrations(); /** * Sets the placeholders to replace in SQL migration scripts. */ - @ConfigItem - public Map placeholders = Collections.emptyMap(); + Map placeholders(); /** * Whether Flyway should attempt to create the schemas specified in the schemas property */ - @ConfigItem(defaultValue = "true") - public boolean createSchemas; + @WithDefault("true") + boolean createSchemas(); /** * Prefix of every placeholder (default: ${ ) */ - @ConfigItem - public Optional placeholderPrefix = Optional.empty(); + Optional placeholderPrefix(); /** * Suffix of every placeholder (default: } ) */ - @ConfigItem - public Optional placeholderSuffix = Optional.empty(); + Optional placeholderSuffix(); /** * The SQL statements to run to initialize a new database connection immediately after opening it. */ - @ConfigItem - public Optional initSql = Optional.empty(); + Optional initSql(); /** * Whether to validate migrations and callbacks whose scripts do not obey the correct naming convention. A failure can be * useful to check that errors such as case sensitivity in migration prefixes have been corrected. */ - @ConfigItem - public boolean validateMigrationNaming; + @WithDefault("false") + boolean validateMigrationNaming(); /** * Ignore migrations during validate and repair according to a given list of patterns (see @@ -229,6 +200,5 @@ public static FlywayDataSourceRuntimeConfig defaultConfig() { * When this configuration is set, the ignoreFutureMigrations and ignoreMissingMigrations settings are ignored. Patterns are * comma separated. */ - @ConfigItem - public Optional ignoreMigrationPatterns = Optional.empty(); + Optional ignoreMigrationPatterns(); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 94c7919fe46159..36ded39d7b507d 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -76,7 +76,7 @@ public Flyway get() { } public void doStartActions() { - if (!config.getValue().enabled) { + if (!config.getValue().enabled()) { return; } for (FlywayContainer flywayContainer : FLYWAY_CONTAINERS) { diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java index 481ee6e8215392..13efcd42e082a3 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -1,42 +1,48 @@ package io.quarkus.flyway.runtime; -import java.util.Collections; import java.util.Map; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithParentName; -@ConfigRoot(name = "flyway", phase = ConfigPhase.RUN_TIME) -public final class FlywayRuntimeConfig { +@ConfigMapping(prefix = "quarkus.flyway") +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public interface FlywayRuntimeConfig { /** * Gets the {@link FlywayDataSourceRuntimeConfig} for the given datasource name. */ - public FlywayDataSourceRuntimeConfig getConfigForDataSourceName(String dataSourceName) { + default FlywayDataSourceRuntimeConfig getConfigForDataSourceName(String dataSourceName) { if (DataSourceUtil.isDefault(dataSourceName)) { - return defaultDataSource; + return defaultDataSource(); } - return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceRuntimeConfig.defaultConfig()); + FlywayDataSourceRuntimeConfig config = namedDataSources().get(dataSourceName); + if (config == null) { + config = defaultDataSource(); + } + return config; } /** * Flag to enable / disable Flyway. * */ - @ConfigItem(defaultValue = "true") - public boolean enabled; + @WithDefault("true") + boolean enabled(); /** * Flyway configuration for the default datasource. */ - @ConfigItem(name = ConfigItem.PARENT) - public FlywayDataSourceRuntimeConfig defaultDataSource = FlywayDataSourceRuntimeConfig.defaultConfig(); + @WithParentName + FlywayDataSourceRuntimeConfig defaultDataSource(); /** * Flyway configurations for named datasources. */ - @ConfigItem(name = ConfigItem.PARENT) - public Map namedDataSources = Collections.emptyMap(); + @WithParentName + Map namedDataSources(); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java index ef51023a14c121..fff138b184cd8b 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java @@ -76,7 +76,7 @@ protected void handlePostAsync(RoutingContext event, MultiMap form) throws Excep return; } FlywayDataSourceBuildTimeConfig config = buildTimeConfig.getConfigForDataSourceName(name); - if (config.locations.isEmpty()) { + if (config.locations().isEmpty()) { flashMessage(event, "Datasource has no locations configured"); return; } @@ -90,7 +90,7 @@ protected void handlePostAsync(RoutingContext event, MultiMap form) throws Excep // In the current project only Path path = resourcesDir.get(0); - Path migrationDir = path.resolve(config.locations.get(0)); + Path migrationDir = path.resolve(config.locations().get(0)); Files.createDirectories(migrationDir); Path file = migrationDir.resolve( "V1.0.0__" + artifactId + ".sql"); diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java index 17764887791ed8..48e818499b4726 100644 --- a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java @@ -18,23 +18,41 @@ import org.flywaydb.core.api.configuration.Configuration; import org.flywaydb.core.api.pattern.ValidatePattern; import org.flywaydb.core.internal.util.ValidatePatternUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; class FlywayCreatorTest { - private FlywayDataSourceRuntimeConfig runtimeConfig = FlywayDataSourceRuntimeConfig.defaultConfig(); - private FlywayDataSourceBuildTimeConfig buildConfig = FlywayDataSourceBuildTimeConfig.defaultConfig(); - private Configuration defaultConfig = Flyway.configure().load().getConfiguration(); + private FlywayDataSourceRuntimeConfig runtimeConfig; + private FlywayDataSourceBuildTimeConfig buildConfig; + private final Configuration defaultConfig = Flyway.configure().load().getConfiguration(); /** * class under test. */ private FlywayCreator creator; + @BeforeEach + void mockConfig() { + SmallRyeConfig config = new SmallRyeConfigBuilder().addDiscoveredSources().addDefaultSources().addDiscoveredConverters() + .withMapping(FlywayRuntimeConfig.class, "quarkus.flyway") + .withMapping(FlywayBuildTimeConfig.class, "quarkus.flyway") + .build(); + + FlywayRuntimeConfig flywayRuntimeConfig = config.getConfigMapping(FlywayRuntimeConfig.class); + FlywayBuildTimeConfig flywayBuildTimeConfig = config.getConfigMapping(FlywayBuildTimeConfig.class); + this.runtimeConfig = Mockito.spy(flywayRuntimeConfig.defaultDataSource()); + this.buildConfig = Mockito.spy(flywayBuildTimeConfig.defaultDataSource()); + } + @Test @DisplayName("locations default matches flyway default") void testLocationsDefault() { @@ -45,9 +63,9 @@ void testLocationsDefault() { @Test @DisplayName("locations carried over from configuration") void testLocationsOverridden() { - buildConfig.locations = Arrays.asList("db/migrations", "db/something"); + Mockito.when(buildConfig.locations()).thenReturn(Arrays.asList("db/migrations", "db/something")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(buildConfig.locations, pathList(createdFlywayConfig().getLocations())); + assertEquals(buildConfig.locations(), pathList(createdFlywayConfig().getLocations())); } @Test @@ -67,9 +85,9 @@ void testBaselineDescriptionDefault() { @Test @DisplayName("baseline description carried over from configuration") void testBaselineDescriptionOverridden() { - runtimeConfig.baselineDescription = Optional.of("baselineDescription"); + Mockito.when(runtimeConfig.baselineDescription()).thenReturn(Optional.of("baselineDescription")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.baselineDescription.get(), createdFlywayConfig().getBaselineDescription()); + assertEquals(runtimeConfig.baselineDescription().get(), createdFlywayConfig().getBaselineDescription()); } @Test @@ -82,9 +100,9 @@ void testBaselineVersionDefault() { @Test @DisplayName("baseline version carried over from configuration") void testBaselineVersionOverridden() { - runtimeConfig.baselineVersion = Optional.of("0.1.2"); + Mockito.when(runtimeConfig.baselineVersion()).thenReturn(Optional.of("0.1.2")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.baselineVersion.get(), createdFlywayConfig().getBaselineVersion().getVersion()); + assertEquals(runtimeConfig.baselineVersion().get(), createdFlywayConfig().getBaselineVersion().getVersion()); } @Test @@ -97,9 +115,9 @@ void testConnectionRetriesDefault() { @Test @DisplayName("connection retries carried over from configuration") void testConnectionRetriesOverridden() { - runtimeConfig.connectRetries = OptionalInt.of(12); + Mockito.when(runtimeConfig.connectRetries()).thenReturn(OptionalInt.of(12)); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.connectRetries.getAsInt(), createdFlywayConfig().getConnectRetries()); + assertEquals(runtimeConfig.connectRetries().getAsInt(), createdFlywayConfig().getConnectRetries()); } @Test @@ -112,9 +130,10 @@ void testRepeatableSqlMigrationPrefixDefault() { @Test @DisplayName("repeatable SQL migration prefix carried over from configuration") void testRepeatableSqlMigrationPrefixOverridden() { - runtimeConfig.repeatableSqlMigrationPrefix = Optional.of("A"); + Mockito.when(runtimeConfig.repeatableSqlMigrationPrefix()).thenReturn(Optional.of("A")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.repeatableSqlMigrationPrefix.get(), createdFlywayConfig().getRepeatableSqlMigrationPrefix()); + assertEquals(runtimeConfig.repeatableSqlMigrationPrefix().get(), + createdFlywayConfig().getRepeatableSqlMigrationPrefix()); } @Test @@ -127,9 +146,9 @@ void testSchemasDefault() { @Test @DisplayName("schemas carried over from configuration") void testSchemasOverridden() { - runtimeConfig.schemas = Optional.of(Arrays.asList("TEST_SCHEMA_1", "TEST_SCHEMA_2")); + Mockito.when(runtimeConfig.schemas()).thenReturn(Optional.of(asList("TEST_SCHEMA_1", "TEST_SCHEMA_2"))); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.schemas.get(), asList(createdFlywayConfig().getSchemas())); + assertEquals(runtimeConfig.schemas().get(), asList(createdFlywayConfig().getSchemas())); } @Test @@ -142,9 +161,9 @@ void testSqlMigrationPrefixDefault() { @Test @DisplayName("SQL migration prefix carried over from configuration") void testSqlMigrationPrefixOverridden() { - runtimeConfig.sqlMigrationPrefix = Optional.of("M"); + Mockito.when(runtimeConfig.sqlMigrationPrefix()).thenReturn(Optional.of("A")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.sqlMigrationPrefix.get(), createdFlywayConfig().getSqlMigrationPrefix()); + assertEquals(runtimeConfig.sqlMigrationPrefix().get(), createdFlywayConfig().getSqlMigrationPrefix()); } @Test @@ -157,31 +176,31 @@ void testTableDefault() { @Test @DisplayName("table carried over from configuration") void testTableOverridden() { - runtimeConfig.table = Optional.of("flyway_history_test_table"); + Mockito.when(runtimeConfig.table()).thenReturn(Optional.of("flyway_history_test_table")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.table.get(), createdFlywayConfig().getTable()); + assertEquals(runtimeConfig.table().get(), createdFlywayConfig().getTable()); } @Test @DisplayName("validate on migrate default matches to true") void testValidateOnMigrate() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.validateOnMigrate, createdFlywayConfig().isValidateOnMigrate()); - assertTrue(runtimeConfig.validateOnMigrate); + assertEquals(runtimeConfig.validateOnMigrate(), createdFlywayConfig().isValidateOnMigrate()); + assertTrue(runtimeConfig.validateOnMigrate()); } @Test @DisplayName("clean disabled default matches to false") void testCleanDisabled() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.cleanDisabled, createdFlywayConfig().isCleanDisabled()); - assertFalse(runtimeConfig.cleanDisabled); + assertEquals(runtimeConfig.cleanDisabled(), createdFlywayConfig().isCleanDisabled()); + assertFalse(runtimeConfig.cleanDisabled()); - runtimeConfig.cleanDisabled = false; + Mockito.when(runtimeConfig.cleanDisabled()).thenReturn(false); creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(createdFlywayConfig().isCleanDisabled()); - runtimeConfig.cleanDisabled = true; + Mockito.when(runtimeConfig.cleanDisabled()).thenReturn(true); creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isCleanDisabled()); } @@ -189,11 +208,11 @@ void testCleanDisabled() { @Test @DisplayName("outOfOrder is correctly set") void testOutOfOrder() { - runtimeConfig.outOfOrder = false; + Mockito.when(runtimeConfig.outOfOrder()).thenReturn(false); creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(createdFlywayConfig().isOutOfOrder()); - runtimeConfig.outOfOrder = true; + Mockito.when(runtimeConfig.outOfOrder()).thenReturn(true); creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isOutOfOrder()); } @@ -201,11 +220,11 @@ void testOutOfOrder() { @Test @DisplayName("ignoreMissingMigrations is correctly set") void testIgnoreMissingMigrations() { - runtimeConfig.ignoreMissingMigrations = false; + Mockito.when(runtimeConfig.ignoreMissingMigrations()).thenReturn(false); creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(ValidatePatternUtils.isMissingIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); - runtimeConfig.ignoreMissingMigrations = true; + Mockito.when(runtimeConfig.ignoreMissingMigrations()).thenReturn(true); creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(ValidatePatternUtils.isMissingIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); } @@ -213,11 +232,11 @@ void testIgnoreMissingMigrations() { @Test @DisplayName("ignoreFutureMigrations is correctly set") void testIgnoreFutureMigrations() { - runtimeConfig.ignoreFutureMigrations = false; + Mockito.when(runtimeConfig.ignoreFutureMigrations()).thenReturn(false); creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(ValidatePatternUtils.isFutureIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); - runtimeConfig.ignoreFutureMigrations = true; + Mockito.when(runtimeConfig.ignoreFutureMigrations()).thenReturn(true); creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(ValidatePatternUtils.isFutureIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); } @@ -226,14 +245,14 @@ void testIgnoreFutureMigrations() { @DisplayName("cleanOnValidationError defaults to false and is correctly set") void testCleanOnValidationError() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.cleanOnValidationError, createdFlywayConfig().isCleanOnValidationError()); - assertFalse(runtimeConfig.cleanOnValidationError); + assertEquals(runtimeConfig.cleanOnValidationError(), createdFlywayConfig().isCleanOnValidationError()); + assertFalse(runtimeConfig.cleanOnValidationError()); - runtimeConfig.cleanOnValidationError = false; + Mockito.when(runtimeConfig.cleanOnValidationError()).thenReturn(false); creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(createdFlywayConfig().isCleanOnValidationError()); - runtimeConfig.cleanOnValidationError = true; + Mockito.when(runtimeConfig.cleanOnValidationError()).thenReturn(true); creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isCleanOnValidationError()); } @@ -242,20 +261,20 @@ void testCleanOnValidationError() { @MethodSource("validateOnMigrateOverwritten") @DisplayName("validate on migrate overwritten in configuration") void testValidateOnMigrateOverwritten(final boolean input, final boolean expected) { - runtimeConfig.validateOnMigrate = input; + Mockito.when(runtimeConfig.validateOnMigrate()).thenReturn(input); creator = new FlywayCreator(runtimeConfig, buildConfig); assertEquals(createdFlywayConfig().isValidateOnMigrate(), expected); - assertEquals(runtimeConfig.validateOnMigrate, expected); + assertEquals(runtimeConfig.validateOnMigrate(), expected); } @Test @DisplayName("validateMigrationNaming defaults to false and it is correctly set") void testValidateMigrationNaming() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.validateMigrationNaming, createdFlywayConfig().isValidateMigrationNaming()); - assertFalse(runtimeConfig.validateMigrationNaming); + assertEquals(runtimeConfig.validateMigrationNaming(), createdFlywayConfig().isValidateMigrationNaming()); + assertFalse(runtimeConfig.validateMigrationNaming()); - runtimeConfig.validateMigrationNaming = true; + Mockito.when(runtimeConfig.validateMigrationNaming()).thenReturn(true); creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isValidateMigrationNaming()); } @@ -265,13 +284,13 @@ void testValidateMigrationNaming() { void testIgnoreMigrationPatterns() { creator = new FlywayCreator(runtimeConfig, buildConfig); assertEquals(0, createdFlywayConfig().getIgnoreMigrationPatterns().length); - assertFalse(runtimeConfig.ignoreMigrationPatterns.isPresent()); + assertFalse(runtimeConfig.ignoreMigrationPatterns().isPresent()); - runtimeConfig.ignoreMigrationPatterns = Optional.of(new String[] { "*:missing" }); + Mockito.when(runtimeConfig.ignoreMigrationPatterns()).thenReturn(Optional.of(new String[] { "*:missing" })); creator = new FlywayCreator(runtimeConfig, buildConfig); final ValidatePattern[] existingIgnoreMigrationPatterns = createdFlywayConfig().getIgnoreMigrationPatterns(); assertEquals(1, existingIgnoreMigrationPatterns.length); - final String[] ignoreMigrationPatterns = runtimeConfig.ignoreMigrationPatterns.get(); + final String[] ignoreMigrationPatterns = runtimeConfig.ignoreMigrationPatterns().get(); final ValidatePattern[] validatePatterns = Arrays.stream(ignoreMigrationPatterns) .map(ValidatePattern::fromPattern).toArray(ValidatePattern[]::new); assertArrayEquals(validatePatterns, existingIgnoreMigrationPatterns); From 8538335145d11ea3c315f4e2da276fd186000ae7 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 28 Apr 2023 22:11:38 -0300 Subject: [PATCH 183/333] Bump surefire plugin in independent-projects to 3.0.0 --- independent-projects/enforcer-rules/pom.xml | 1 + independent-projects/revapi/pom.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/independent-projects/enforcer-rules/pom.xml b/independent-projects/enforcer-rules/pom.xml index dec7b9521e6105..10f7c57218e77b 100644 --- a/independent-projects/enforcer-rules/pom.xml +++ b/independent-projects/enforcer-rules/pom.xml @@ -36,6 +36,7 @@ 11 3.2.1 + 3.0.0 3.0.0-M3 3.5.1 diff --git a/independent-projects/revapi/pom.xml b/independent-projects/revapi/pom.xml index 20e6e24ade203f..8d37ec70c0168c 100644 --- a/independent-projects/revapi/pom.xml +++ b/independent-projects/revapi/pom.xml @@ -29,6 +29,7 @@ 3.2.1 + 3.0.0 11 11 11 From bc15a5ef5fbe1b6d172f70dbcf6742be1b1b8928 Mon Sep 17 00:00:00 2001 From: Andres Almiray Date: Sat, 29 Apr 2023 15:15:36 +0200 Subject: [PATCH 184/333] Update JReleaser guide for native executables --- docs/src/main/asciidoc/jreleaser.adoc | 91 ++++++++++++--------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/docs/src/main/asciidoc/jreleaser.adoc b/docs/src/main/asciidoc/jreleaser.adoc index b94c1e74a665b0..7831cfaf2f7927 100644 --- a/docs/src/main/asciidoc/jreleaser.adoc +++ b/docs/src/main/asciidoc/jreleaser.adoc @@ -6,7 +6,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc = Packaging And Releasing With JReleaser include::_attributes.adoc[] -:jreleaser-version: 0.9.1 +:jreleaser-version: 1.6.0 :numbered: :sectnums: @@ -121,7 +121,7 @@ to the `` section of the file: kr.motd.maven os-maven-plugin - 1.7.0 + 1.7.1 @@ -337,7 +337,7 @@ configuration section, like so: - NATIVE_IMAGE + BINARY ${distribution.directory}/{{distributionName}}-{{projectVersion}}-linux-x86_64.tar.gz @@ -367,8 +367,8 @@ that there's a configured distribution the plugin expects more metadata to be pr [source] ---- -[WARNING] [validation] project.copyright must not be blank since 0.4.0. This warning will become an error in a future release. [ERROR] == JReleaser == +[ERROR] project.copyright must not be blank [ERROR] project.description must not be blank [ERROR] project.website must not be blank [ERROR] project.docsUrl must not be blank @@ -384,9 +384,11 @@ POM elements. If you choose the former option then the plugin's configuration ma - app -- Sample Quarkus CLI application - pass:[https://github.com/aalmiray/app] - pass:[https://github.com/aalmiray/app] + app - Sample Quarkus CLI application + + https://github.com/aalmiray/app + https://github.com/aalmiray/app + APACHE-2.0 Andres Almiray 2021 Kordamp @@ -451,7 +453,7 @@ For this to work we simply have to enable Homebrew in the JReleaser plugin confi ---- - NATIVE_IMAGE + BINARY ALWAYS @@ -489,28 +491,37 @@ remote resources such as Git repositories. This is how it would look like: ---- # because we changed the project's version ./mvnw -Pnative,dist package -./mvnw -Prelease jreleaser:full-release -Djreleaser.select.current.platform -Djreleaser.dryrun +./mvnw -Prelease jreleaser:full-release -Djreleaser.select.current.platform -Djreleaser.dry.run=true [INFO] --- jreleaser-maven-plugin:{jreleaser-version}:full-release (default-cli) @ app --- [INFO] JReleaser {jreleaser-version} [INFO] - basedir set to /tmp/app +[INFO] - outputdir set to /tmp/app/target/jreleaser [WARNING] Platform selection is in effect [WARNING] Artifacts will be filtered by platform matching: [osx-x86_64] +[INFO] git-root-search set to false [INFO] Loading variables from /Users/aalmiray/.jreleaser/config.toml [INFO] Validating configuration +[INFO] Strict mode set to false [INFO] Project version set to 1.0.0.Alpha1 [INFO] Release is not snapshot -[INFO] Timestamp is 2021-12-16T13:31:12.163687+01:00 -[INFO] HEAD is at a21f3f2 +[INFO] Timestamp is 2023-04-27T15:06:34.289907+02:00 +[INFO] HEAD is at 73603ac [INFO] Platform is osx-x86_64 -[INFO] dryrun set to true -[INFO] Generating changelog: target/jreleaser/release/CHANGELOG.md -[INFO] Calculating checksums +[INFO] dry-run set to true +[INFO] Generating changelog +[INFO] Storing changelog: target/jreleaser/release/CHANGELOG.md +[INFO] Cataloging artifacts +[INFO] [sbom] Cataloging is not enabled. Skipping +[INFO] Calculating checksums for distributions and files [INFO] [checksum] target/distributions/app-1.0.0.Alpha1-osx-x86_64.zip.sha256 -[INFO] Signing files -[INFO] Signing is not enabled. Skipping -[INFO] Uploading is not enabled. Skipping -[INFO] Releasing to https://github.com/aalmiray/app +[INFO] Signing distributions and files +[INFO] [sign] Signing is not enabled. Skipping +[INFO] Deploying Maven artifacts +[INFO] [maven] Deploying is not enabled. Skipping +[INFO] Uploading distributions and files +[INFO] [upload] Uploading is not enabled. Skipping +[INFO] Releasing to https://github.com/aalmiray/app@main [INFO] - uploading app-1.0.0.Alpha1-osx-x86_64.zip [INFO] - uploading checksums_sha256.txt [INFO] Preparing distributions @@ -524,9 +535,10 @@ remote resources such as Git repositories. This is how it would look like: [INFO] [brew] publishing app distribution [INFO] [brew] setting up repository aalmiray/homebrew-tap [INFO] Announcing release -[INFO] Announcing is not enabled. Skipping +[INFO] [announce] Announcing is not enabled. Skipping [INFO] Writing output properties to target/jreleaser/output.properties -[INFO] JReleaser succeeded after 1.335 s +[INFO] JReleaser succeeded after 0.620 s + ---- JReleaser will perform the following tasks for us: @@ -539,25 +551,26 @@ JReleaser will perform the following tasks for us: * Upload all assets, including checksums. * Create a Homebrew formula, publishing to pass:[https://github.com/aalmiray/homebrew-tap]. -Of course no remote repository was affected as we can appreciate the `-Djreleaser.dryrun` property was in effect. If you're +Of course no remote repository was affected as we can appreciate the `-Djreleaser.dry.run=true` property was in effect. If you're so inclined inspect the contents of `target/jreleaser/package/app/brew/Formula/app.rb` which defines the Homebrew formula to be published. It should look something like this: -[source,ruby,subs=macros+] +[source,ruby,subs=attributes,macros+] .app.rb ---- +# Generated with JReleaser {jreleaser-version} at 2023-04-27T15:06:34.289907+02:00 class App < Formula desc "app -- Sample Quarkus CLI application" homepage "pass:[https://github.com/aalmiray/app]" url "pass:[https://github.com/aalmiray/app/releases/download/v1.0.0.Alpha1/app-1.0.0.Alpha1-osx-x86_64.zip]" version "1.0.0.Alpha1" - sha256 "a7e8df6eef3c4c5df7357e678b3c4bc6945b926cec4178a0239660de5dba0fc4" + sha256 "85c9918b23e3ac4ef64d5dd02714e241231d3f1358afdba09d3fd0b9a889e131" license "Apache-2.0" def install libexec.install Dir["*"] - bin.install_symlink "#{libexec}/bin/app" + bin.install_symlink "#{libexec}/bin/app" => "app" end test do @@ -567,7 +580,7 @@ class App < Formula end ---- -When ready, create a release for real this time by simply removing the `-Djreleaser.dryrun` flag from the command line, then +When ready, create a release for real this time by simply removing the `-Djreleaser.dry.run` flag from the command line, then browse to your repository and look at the freshly created release. == Further reading @@ -649,7 +662,7 @@ As a reference, these are the full contents of the `pom.xml`: kr.motd.maven os-maven-plugin - 1.7.0 + 1.7.1 @@ -695,30 +708,8 @@ As a reference, these are the full contents of the `pom.xml`: native - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - - + false native @@ -792,7 +783,7 @@ As a reference, these are the full contents of the `pom.xml`: - NATIVE_IMAGE + BINARY ALWAYS From c1ebd0844c342a627a3ae9555bcac1e53d65d5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crocquesel?= <88554524+scrocquesel@users.noreply.github.com> Date: Sat, 29 Apr 2023 15:43:03 +0200 Subject: [PATCH 185/333] Lowercase enum values to avoid hypen in k8s flavor accronym --- .../client/deployment/DevServicesKubernetesProcessor.java | 6 +++--- .../runtime/KubernetesDevServicesBuildTimeConfig.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java index 8deaab773cc082..54ca2c89d411aa 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java @@ -197,17 +197,17 @@ private RunningDevService startKubernetes(DockerStatusBuildItem dockerStatusBuil final Supplier defaultKubernetesClusterSupplier = () -> { KubernetesContainer container; switch (config.flavor) { - case API_ONLY: + case api_only: container = new ApiServerContainer( config.apiVersion.map(version -> findOrElseThrow(version, ApiServerContainerVersion.class)) .orElseGet(() -> latest(ApiServerContainerVersion.class))); break; - case K3S: + case k3s: container = new K3sContainer( config.apiVersion.map(version -> findOrElseThrow(version, K3sContainerVersion.class)) .orElseGet(() -> latest(K3sContainerVersion.class))); break; - case KIND: + case kind: container = new KindContainer( config.apiVersion.map(version -> findOrElseThrow(version, KindContainerVersion.class)) .orElseGet(() -> latest(KindContainerVersion.class))); diff --git a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java index 6c50f4ce8154e4..3c194f1d269d9c 100644 --- a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java +++ b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java @@ -29,7 +29,7 @@ public class KubernetesDevServicesBuildTimeConfig { /** * The flavor to use (kind, k3s or api-only). Default to api-only. */ - @ConfigItem(defaultValue = "API_ONLY") + @ConfigItem(defaultValue = "api-only") public Flavor flavor; /** @@ -70,14 +70,14 @@ public static enum Flavor { /** * kind (needs priviledge docker) */ - KIND, + kind, /** * k3s (needs priviledge docker) */ - K3S, + k3s, /** * api only */ - API_ONLY; + api_only; } } From 157aac4c1c7ab878e54e5eb5ca62cbe80736c59b Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 28 Apr 2023 17:38:07 +0200 Subject: [PATCH 186/333] Qute - fix a regression introduced in #32653 (3.0.1) - value resolvers are not registered for non-application classes after hot reload - fixes #32959 --- .../qute/deployment/QuteProcessor.java | 60 +++++++++---------- .../ExistingValueResolversDevModeTest.java | 40 +++++++++++++ .../qute/deployment/devmode/TestRoute.java | 19 ++++++ .../generator/ExtensionMethodGenerator.java | 16 ++++- 4 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/ExistingValueResolversDevModeTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/TestRoute.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index e0653607520062..e362d555376ddf 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -1742,11 +1742,14 @@ public String apply(String name) { } }); + // NOTE: We can't use this optimization for classes generated by ValueResolverGenerator because we cannot easily + // map a target class to a specific set of generated classes ExistingValueResolvers existingValueResolvers = liveReloadBuildItem.getContextObject(ExistingValueResolvers.class); if (existingValueResolvers == null) { existingValueResolvers = new ExistingValueResolvers(); liveReloadBuildItem.setContextObject(ExistingValueResolvers.class, existingValueResolvers); } + Set generatedValueResolvers = new HashSet<>(); ValueResolverGenerator.Builder builder = ValueResolverGenerator.builder() .setIndex(index).setClassOutput(classOutput); @@ -1770,15 +1773,11 @@ public Function apply(ClassInfo clazz) { Set controlled = new HashSet<>(); Map uncontrolled = new HashMap<>(); for (TemplateDataBuildItem data : templateData) { - processTemplateData(data, controlled, uncontrolled, builder, existingValueResolvers, applicationClassPredicate); + processTemplateData(data, controlled, uncontrolled, builder); } for (ImplicitValueResolverBuildItem implicit : implicitClasses) { DotName implicitClassName = implicit.getClazz().name(); - if (existingValueResolvers.contains(implicitClassName)) { - // A non-application value resolver already generated - continue; - } if (controlled.contains(implicitClassName)) { LOGGER.debugf("Implicit value resolver for %s ignored: class is annotated with @TemplateData", implicitClassName); @@ -1790,13 +1789,10 @@ public Function apply(ClassInfo clazz) { continue; } builder.addClass(implicit.getClazz(), implicit.getTemplateData()); - existingValueResolvers.add(implicitClassName, applicationClassPredicate); } ValueResolverGenerator generator = builder.build(); generator.generate(); - - Set generatedValueResolvers = new HashSet<>(); generatedValueResolvers.addAll(generator.getGeneratedTypes()); ExtensionMethodGenerator extensionMethodGenerator = new ExtensionMethodGenerator(index, classOutput); @@ -1804,10 +1800,12 @@ public Function apply(ClassInfo clazz) { Map namespaceToClass = new HashMap<>(); for (TemplateExtensionMethodBuildItem templateExtension : templateExtensionMethods) { - if (existingValueResolvers.contains(templateExtension.getMethod())) { + String generatedValueResolverClass = existingValueResolvers.getGeneratedClass(templateExtension.getMethod()); + if (generatedValueResolverClass != null) { + // A ValueResolver of a non-application class was already generated + generatedValueResolvers.add(generatedValueResolverClass); continue; } - existingValueResolvers.add(templateExtension.getMethod(), applicationClassPredicate); if (templateExtension.hasNamespace()) { // Group extension methods declared on the same class by namespace @@ -1835,8 +1833,10 @@ public Function apply(ClassInfo clazz) { namespaceMethods.add(templateExtension); } else { // Generate ValueResolver per extension method - extensionMethodGenerator.generate(templateExtension.getMethod(), templateExtension.getMatchName(), + String generatedClass = extensionMethodGenerator.generate(templateExtension.getMethod(), + templateExtension.getMatchName(), templateExtension.getMatchNames(), templateExtension.getMatchRegex(), templateExtension.getPriority()); + existingValueResolvers.add(templateExtension.getMethod(), generatedClass, applicationClassPredicate); } } @@ -1853,6 +1853,10 @@ public Function apply(ClassInfo clazz) { try (NamespaceResolverCreator namespaceResolverCreator = extensionMethodGenerator .createNamespaceResolver(priorityEntry.getValue().get(0).getMethod().declaringClass(), nsEntry.getKey(), priorityEntry.getKey())) { + for (TemplateExtensionMethodBuildItem extensionMethod : priorityEntry.getValue()) { + existingValueResolvers.add(extensionMethod.getMethod(), namespaceResolverCreator.getClassName(), + applicationClassPredicate); + } try (ResolveCreator resolveCreator = namespaceResolverCreator.implementResolve()) { for (TemplateExtensionMethodBuildItem method : priorityEntry.getValue()) { resolveCreator.addMethod(method.getMethod(), method.getMatchName(), method.getMatchNames(), @@ -1901,28 +1905,25 @@ public Function apply(ClassInfo clazz) { */ static class ExistingValueResolvers { - final Set identifiers = new HashSet<>(); - - boolean contains(DotName className) { - return identifiers.contains(className.toString()); - } + final Map identifiersToGeneratedClass = new HashMap<>(); boolean contains(MethodInfo extensionMethod) { - return identifiers.contains(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString()); + return identifiersToGeneratedClass + .containsKey(toKey(extensionMethod)); } - boolean add(DotName className, Predicate applicationClassPredicate) { - if (!applicationClassPredicate.test(className)) { - return identifiers.add(className.toString()); - } - return false; + String getGeneratedClass(MethodInfo extensionMethod) { + return identifiersToGeneratedClass.get(toKey(extensionMethod)); } - boolean add(MethodInfo extensionMethod, Predicate applicationClassPredicate) { + void add(MethodInfo extensionMethod, String className, Predicate applicationClassPredicate) { if (!applicationClassPredicate.test(extensionMethod.declaringClass().name())) { - return identifiers.add(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString()); + identifiersToGeneratedClass.put(toKey(extensionMethod), className); } - return false; + } + + private String toKey(MethodInfo extensionMethod) { + return extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString(); } } @@ -2942,22 +2943,15 @@ private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtua } private void processTemplateData(TemplateDataBuildItem templateData, - Set controlled, Map uncontrolled, ValueResolverGenerator.Builder builder, - ExistingValueResolvers existingValueResolvers, - CompletedApplicationClassPredicateBuildItem applicationClassPredicate) { + Set controlled, Map uncontrolled, ValueResolverGenerator.Builder builder) { DotName targetClassName = templateData.getTargetClass().name(); - if (existingValueResolvers.contains(targetClassName)) { - return; - } if (templateData.isTargetAnnotatedType()) { controlled.add(targetClassName); builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance()); - existingValueResolvers.add(targetClassName, applicationClassPredicate); } else { // At this point we can be sure that multiple unequal @TemplateData do not exist for a specific target uncontrolled.computeIfAbsent(targetClassName, name -> { builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance()); - existingValueResolvers.add(targetClassName, applicationClassPredicate); return templateData.getAnnotationInstance(); }); } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/ExistingValueResolversDevModeTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/ExistingValueResolversDevModeTest.java new file mode 100644 index 00000000000000..d8069523c9d12e --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/ExistingValueResolversDevModeTest.java @@ -0,0 +1,40 @@ +package io.quarkus.qute.deployment.devmode; + +import static io.restassured.RestAssured.given; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +/** + * Test that built-in extension value resolvers are correctly registered after live reload. + */ +public class ExistingValueResolversDevModeTest { + + @RegisterExtension + static final QuarkusDevModeTest config = new QuarkusDevModeTest() + .withApplicationRoot(root -> root + .addClass(TestRoute.class) + .addAsResource(new StringAsset( + "{#let a = 3}{#let b = a.minus(2)}b={b}{/}{/}"), + "templates/let.html")); + + @Test + public void testExistingValueResolvers() { + given().get("test") + .then() + .statusCode(200) + .body(Matchers.equalTo("b=1")); + + config.modifyResourceFile("templates/let.html", t -> t.concat("::MODIFIED")); + + given().get("test") + .then() + .statusCode(200) + .body(Matchers.equalTo("b=1::MODIFIED")); + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/TestRoute.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/TestRoute.java new file mode 100644 index 00000000000000..15fbd2054ded68 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/TestRoute.java @@ -0,0 +1,19 @@ +package io.quarkus.qute.deployment.devmode; + +import jakarta.inject.Inject; + +import io.quarkus.qute.Template; +import io.quarkus.vertx.web.Route; +import io.vertx.ext.web.RoutingContext; + +public class TestRoute { + + @Inject + Template let; + + @Route(path = "test") + public void test(RoutingContext ctx) { + ctx.end(let.render()); + } + +} diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java index bbc9b8f3cb965b..bb1a50f4b6bfe3 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java @@ -103,7 +103,16 @@ public static void validate(MethodInfo method, String namespace) { } } - public void generate(MethodInfo method, String matchName, List matchNames, String matchRegex, Integer priority) { + /** + * + * @param method + * @param matchName + * @param matchNames + * @param matchRegex + * @param priority + * @return the fully qualified name of the generated class + */ + public String generate(MethodInfo method, String matchName, List matchNames, String matchRegex, Integer priority) { AnnotationInstance extensionAnnotation = method.annotation(TEMPLATE_EXTENSION); List parameters = method.parameterTypes(); @@ -199,6 +208,7 @@ public void generate(MethodInfo method, String matchName, List matchName implementResolve(valueResolver, declaringClass, method, matchName, matchNames, patternField, params); valueResolver.close(); + return generatedName.replace('/', '.'); } public NamespaceResolverCreator createNamespaceResolver(ClassInfo declaringClass, String namespace, int priority) { @@ -449,6 +459,10 @@ public NamespaceResolverCreator(ClassInfo declaringClass, String namespace, int implementGetPriority(namespaceResolver, priority); } + public String getClassName() { + return namespaceResolver.getClassName().replace('/', '.'); + } + public ResolveCreator implementResolve() { return new ResolveCreator(); } From 8bfb436b312272e1cc8708374b8d1cdd27999372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Zangh=C3=AC?= Date: Sun, 30 Apr 2023 17:32:18 +0200 Subject: [PATCH 187/333] fix command fixed example in command-mode-reference.adoc --- docs/src/main/asciidoc/command-mode-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/command-mode-reference.adoc b/docs/src/main/asciidoc/command-mode-reference.adoc index 7633b1164438ae..5105520ea870cb 100644 --- a/docs/src/main/asciidoc/command-mode-reference.adoc +++ b/docs/src/main/asciidoc/command-mode-reference.adoc @@ -54,7 +54,7 @@ import io.quarkus.runtime.annotations.QuarkusMain; public class HelloWorldMain implements QuarkusApplication { @Override public int run(String... args) throws Exception { // <.> - System.out.println("Hello " + args[1]); + System.out.println("Hello " + args[0]); return 0; } } From 4c43c7ad416f6a0b3b162ab68641939e8eb7cf49 Mon Sep 17 00:00:00 2001 From: Hitesh C Date: Sun, 30 Apr 2023 19:36:49 +0000 Subject: [PATCH 188/333] Fix for 33021 changed string comparison from == to .equals(). --- .../src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index 2b23b7609c4b92..0d5e48cc58f93e 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -334,7 +334,7 @@ private static Key readTokenDecryptionKey(OidcTenantConfig oidcConfig) { List keys = KeyUtils.loadJsonWebKeys(keyContent); if (keys != null && keys.size() == 1 && (keys.get(0).getAlgorithm() == null - || keys.get(0).getAlgorithm() == KeyEncryptionAlgorithm.RSA_OAEP.getAlgorithm()) + || keys.get(0).getAlgorithm().equals(KeyEncryptionAlgorithm.RSA_OAEP.getAlgorithm())) && ("enc".equals(keys.get(0).getUse()) || keys.get(0).getUse() == null)) { key = PublicJsonWebKey.class.cast(keys.get(0)).getPrivateKey(); } From 9c7c84e96ef83e867bff56cafb0e8bae81e80626 Mon Sep 17 00:00:00 2001 From: Hitesh C Date: Sun, 30 Apr 2023 20:35:45 +0000 Subject: [PATCH 189/333] [FIXED] Wiremock integrated tests after changing the privateKey for quarkus.oidc.code-flow-encrypted-id-token-jwk.token.decryption-key-location, the integration Tests fail for the older code and pass for the updated code, which is the expected behaviour --- .../src/main/resources/application.properties | 2 +- .../main/resources/privateKeyEncryptedIdToken.jwk | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 integration-tests/oidc-wiremock/src/main/resources/privateKeyEncryptedIdToken.jwk diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index e4b46e66fdb65d..a549bdae45d0d9 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -25,7 +25,7 @@ quarkus.oidc.code-flow-encrypted-id-token-jwk.client-id=quarkus-web-app quarkus.oidc.code-flow-encrypted-id-token-jwk.credentials.secret=secret quarkus.oidc.code-flow-encrypted-id-token-jwk.application-type=web-app quarkus.oidc.code-flow-encrypted-id-token-jwk.token-path=${keycloak.url}/realms/quarkus/encrypted-id-token -quarkus.oidc.code-flow-encrypted-id-token-jwk.token.decryption-key-location=privateKey.jwk +quarkus.oidc.code-flow-encrypted-id-token-jwk.token.decryption-key-location=privateKeyEncryptedIdToken.jwk quarkus.oidc.code-flow-encrypted-id-token-pem.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.code-flow-encrypted-id-token-pem.client-id=quarkus-web-app diff --git a/integration-tests/oidc-wiremock/src/main/resources/privateKeyEncryptedIdToken.jwk b/integration-tests/oidc-wiremock/src/main/resources/privateKeyEncryptedIdToken.jwk new file mode 100644 index 00000000000000..5f0a577eff87b0 --- /dev/null +++ b/integration-tests/oidc-wiremock/src/main/resources/privateKeyEncryptedIdToken.jwk @@ -0,0 +1,14 @@ +{ + "kty":"RSA", +"alg":"RSA-OAEP", +"use":"enc", + "kid":"1", + "n":"iJw33l1eVAsGoRlSyo-FCimeOc-AaZbzQ2iESA3Nkuo3TFb1zIkmt0kzlnWVGt48dkaIl13Vdefh9hqw_r9yNF8xZqX1fp0PnCWc5M_TX_ht5fm9y0TpbiVmsjeRMWZn4jr3DsFouxQ9aBXUJiu26V0vd2vrECeeAreFT4mtoHY13D2WVeJvboc5mEJcp50JNhxRCJ5UkY8jR_wfUk2Tzz4-fAj5xQaBccXnqJMu_1C6MjoCEiB7G1d13bVPReIeAGRKVJIF6ogoCN8JbrOhc_48lT4uyjbgnd24beatuKWodmWYhactFobRGYo5551cgMe8BoxpVQ4to30cGA0qjQ", + "e":"AQAB", + "d":"AvIDTlsK_priQLTwEQf5IVf2Xl638Q7dHdXyDC-oAAPmv1GcqRVH7Wm5oAPW_CZQfWhV55WRVaJzP8AhksyD5NcslH79hQZT4NT6xgApGYecrvmseuZ4dfR-e1cxXTRNBxaoXvwSiv4LuOPHmC8XGX712AhOoCGKiZp1WFqqkKwTpkgJEApJFVb-XRIKQa0YaRKpJsJ534pLMwTh7LoPLM4BCaBVbRfHzH2H5L3TSJP718kyCuxg3z2p9Y7zIOLTmgFdeR0_kd_xKUFZ2ByN3SKlC0IWlLUSiMPsGYExRpZTMZHKyD939gv-2_Z-bOYfKlYNIvAmQH_8CcX2I039LQ", + "p":"104AjPaxZoi_BiMBODlChnZOvRJT071PdkeZ283uyrdW8qqKD9q8FTMgUXzKoboHtUiHbJbLOobPmPDh93839rq7dTdCNzNVOuLmE-V3_bmaShdzvxEIazwPf6AvjbEZAc-zu2RS4SNkp1LbzgSl9nINSlF7t6Lkl6T28PYULys", + "q":"om5ooyzxa4ZJ-dU0ODsEb-Bmz6xwb27xF9aEhBYJprHeoNs2QM1D64_A39weD9MYwBux4-ivshCJ0dVKEbDujJRLnzf-ssrasA6CFyaaCT4DKtq1oWb9rcG-2LQd5Bm9PttrUrSUNqitr085IYikaLEz7UU6gtXPoC8UOcJ4cSc", + "dp":"DeWE95Q8oweUfMrpmz1m49LjBiUWsAX6CQJaFevWy9LFk-gZ_Sf7F8sy_M93LLUbJkJGK2YYO_DTmWWC0Dyv2gb3bntglLuFdsWKYCJhekjugnW9DMoGpxU7Utt99kFGAe3sBd5V0x47sukQMt3t8FgwL2nO-G1VH8yP-8GGT_0", + "dq":"TGBeE1wuqMCcSD1YMJiPnYuGzF_o_nzMIMldxj4Wi6tXY4uwFwhtx3Xw21JFUGuSV8KuAtyGwNPF-kSwb2Eiyjdw140c1jVMXzxzLy-XfoEKPDxa62niHrHba0pGQ9tWgRfrfxgqGQl3odc-peX6aL_qCsdim-KtnkSE3iPzPkE", + "qi":"Jzp5KnT24y0wOoPUn_11S3ZcYl0i03dkaH4c5zR02G1MJG9K017juurx2aXVTctOzrj7O226EUiL1Qbq3QtnWFDDGY6vNZuqzJM7AMXsvp1djq_6fEVhxCIOgfJbmhb3mkG82rxn4et9o_TNr6mvEmHzG15sHbvZbAnn4GeqToY" +} From 143a61b6cee6f331c96c17c251f6f6e75634cb15 Mon Sep 17 00:00:00 2001 From: humberto Date: Mon, 24 Apr 2023 13:12:41 +0200 Subject: [PATCH 190/333] Support for multi tenancy column discriminator #28644 --- docs/src/main/asciidoc/hibernate-orm.adoc | 24 +- .../orm/deployment/HibernateOrmProcessor.java | 5 - .../FastBootHibernatePersistenceProvider.java | 2 +- .../FastBootEntityManagerFactoryBuilder.java | 13 +- .../runtime/boot/FastBootMetadataBuilder.java | 12 +- .../orm/runtime/recording/RecordedState.java | 10 +- ...tHibernateReactivePersistenceProvider.java | 2 +- ...otReactiveEntityManagerFactoryBuilder.java | 6 +- .../discriminator/pom.xml | 181 +++++++++++++++ .../fruit/CustomTenantResolver.java | 39 ++++ .../hibernate/multitenancy/fruit/Fruit.java | 98 +++++++++ .../multitenancy/fruit/FruitResource.java | 207 ++++++++++++++++++ .../inventory/InventoryResource.java | 39 ++++ .../inventory/InventoryTenantResolver.java | 39 ++++ .../multitenancy/inventory/Plane.java | 74 +++++++ .../src/main/resources/application.properties | 14 ++ .../src/main/resources/import-inventory.sql | 5 + .../src/main/resources/import.sql | 7 + ...nateTenancyFunctionalityInGraalITCase.java | 11 + .../HibernateTenancyFunctionalityTest.java | 167 ++++++++++++++ .../HibernateNamedPersistenceUnitTest.java | 36 +++ ...NamedPersistenceUnitTestInGraalITCase.java | 8 + .../hibernate-orm-tenancy/pom.xml | 1 + 23 files changed, 980 insertions(+), 20 deletions(-) create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/pom.xml create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/CustomTenantResolver.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/Fruit.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/FruitResource.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryResource.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryTenantResolver.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/Plane.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/application.properties create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import-inventory.sql create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import.sql create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityInGraalITCase.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityTest.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTest.java create mode 100644 integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTestInGraalITCase.java diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index 8a4685b3ce566d..421523ec1f7ebc 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -8,7 +8,7 @@ include::_attributes.adoc[] :categories: data :summary: Hibernate ORM is the de facto Jakarta Persistence implementation and offers you the full breath of an Object Relational Mapper. It works beautifully in Quarkus. :config-file: application.properties -:orm-doc-url-prefix: https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html +:orm-doc-url-prefix: https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html Hibernate ORM is the de facto standard Jakarta Persistence (formerly known as JPA) implementation and offers you the full breadth of an Object Relational Mapper. It works beautifully in Quarkus. @@ -916,7 +916,7 @@ Jump over to xref:datasource.adoc[Quarkus - Datasources] for all details. "The term multitenancy, in general, is applied to software development to indicate an architecture in which a single running instance of an application simultaneously serves multiple clients (tenants). This is highly common in SaaS solutions. Isolating information (data, customizations, etc.) pertaining to the various tenants is a particular challenge in these systems. This includes the data owned by each tenant stored in the database" (link:{orm-doc-url-prefix}#multitenacy[Hibernate User Guide]). -Quarkus currently supports the link:{orm-doc-url-prefix}#multitenacy-separate-database[separate database] and the link:{orm-doc-url-prefix}#multitenacy-separate-schema[separate schema] approach. +Quarkus currently supports the link:{orm-doc-url-prefix}#multitenacy-separate-database[separate database] approach, the link:{orm-doc-url-prefix}#multitenacy-separate-schema[separate schema] approach and the link:{orm-doc-url-prefix}#multitenacy-discriminator[discriminator] approach. To see multitenancy in action, you can check out the {quickstarts-tree-url}/hibernate-orm-multi-tenancy-quickstart[hibernate-orm-multi-tenancy-quickstart] quickstart. @@ -1147,6 +1147,26 @@ INSERT INTO known_fruits(id, name) VALUES (2, 'Apricots'); INSERT INTO known_fruits(id, name) VALUES (3, 'Blackberries'); ---- + + +==== DISCRIMINATOR approach + +The default data source will be used for all tenants. All entities defining a field annotated with `@TenantId` will have that field populated automatically, and will get filtered automatically in queries. + + +[source,properties] +---- +# Enable DISCRIMINATOR approach +quarkus.hibernate-orm.multitenant=DISCRIMINATOR + +# The default data source used for all tenant schemas +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=quarkus_test +quarkus.datasource.password=quarkus_test +quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test +---- + + === Programmatically Resolving Tenants Connections If you need a more dynamic configuration for the different tenants you want to support and don't want to end up with multiple entries in your configuration file, diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 2c994a2cf0f53f..cc59601653cd71 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -1489,11 +1489,6 @@ private static MultiTenancyStrategy getMultiTenancyStrategy(Optional mul final MultiTenancyStrategy multiTenancyStrategy = MultiTenancyStrategy .valueOf(multitenancyStrategy.orElse(MultiTenancyStrategy.NONE.name()) .toUpperCase(Locale.ROOT)); - if (multiTenancyStrategy == MultiTenancyStrategy.DISCRIMINATOR) { - // See https://hibernate.atlassian.net/browse/HHH-6054 - throw new ConfigurationException("The Hibernate ORM multitenancy strategy " - + MultiTenancyStrategy.DISCRIMINATOR + " is currently not supported"); - } return multiTenancyStrategy; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index 68aa72627142bc..5db72232952c54 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -195,7 +195,7 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, standardServiceRegistry /* Mostly ignored! (yet needs to match) */, runtimeSettings, - validatorFactory, cdiBeanManager); + validatorFactory, cdiBeanManager, recordedState.getMultiTenancyStrategy()); } log.debug("Found no matching persistence units"); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java index a9ca93cb09317a..52db112ee6e5f5 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java @@ -34,6 +34,7 @@ import io.quarkus.arc.InjectableInstance; import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings; +import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; import io.quarkus.hibernate.orm.runtime.observers.QuarkusSessionFactoryObserverForDbVersionCheck; import io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForNamedQueryValidation; import io.quarkus.hibernate.orm.runtime.observers.SessionFactoryObserverForSchemaExport; @@ -49,16 +50,19 @@ public class FastBootEntityManagerFactoryBuilder implements EntityManagerFactory private final Object validatorFactory; private final Object cdiBeanManager; + protected final MultiTenancyStrategy multiTenancyStrategy; + public FastBootEntityManagerFactoryBuilder( PrevalidatedQuarkusMetadata metadata, String persistenceUnitName, StandardServiceRegistry standardServiceRegistry, RuntimeSettings runtimeSettings, Object validatorFactory, - Object cdiBeanManager) { + Object cdiBeanManager, MultiTenancyStrategy multiTenancyStrategy) { this.metadata = metadata; this.persistenceUnitName = persistenceUnitName; this.standardServiceRegistry = standardServiceRegistry; this.runtimeSettings = runtimeSettings; this.validatorFactory = validatorFactory; this.cdiBeanManager = cdiBeanManager; + this.multiTenancyStrategy = multiTenancyStrategy; } @Override @@ -76,7 +80,8 @@ public EntityManagerFactory build() { try { final SessionFactoryOptionsBuilder optionsBuilder = metadata.buildSessionFactoryOptionsBuilder(); populate(persistenceUnitName, optionsBuilder, standardServiceRegistry); - return new SessionFactoryImpl(metadata, optionsBuilder.buildOptions()); + return new SessionFactoryImpl(metadata, optionsBuilder.buildOptions(), + metadata.getTypeConfiguration().getMetadataBuildingContext().getBootstrapContext()); } catch (Exception e) { throw persistenceException("Unable to build Hibernate SessionFactory", e); } @@ -189,7 +194,9 @@ protected void populate(String persistenceUnitName, SessionFactoryOptionsBuilder BytecodeProvider bytecodeProvider = ssr.getService(BytecodeProvider.class); options.addSessionFactoryObservers(new SessionFactoryObserverForBytecodeEnhancer(bytecodeProvider)); - if (options.isMultiTenancyEnabled()) { + // Should be added in case of discriminator strategy too, that is not handled by options.isMultiTenancyEnabled() + if (options.isMultiTenancyEnabled() + || (multiTenancyStrategy != null && multiTenancyStrategy != MultiTenancyStrategy.NONE)) { options.applyCurrentTenantIdentifierResolver(new HibernateCurrentTenantIdentifierResolver(persistenceUnitName)); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index 37441b4b287da1..85ffce844c7f5b 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -112,7 +112,7 @@ public class FastBootMetadataBuilder { private final Collection> additionalIntegrators; private final Collection> providedServices; private final PreGeneratedProxies preGeneratedProxies; - private final Optional dataSource; + private final MultiTenancyStrategy multiTenancyStrategy; private final boolean isReactive; private final boolean fromPersistenceXml; private final List integrationStaticDescriptors; @@ -121,7 +121,6 @@ public class FastBootMetadataBuilder { public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefinition, Scanner scanner, Collection> additionalIntegrators, PreGeneratedProxies preGeneratedProxies) { this.persistenceUnit = puDefinition.getActualHibernateDescriptor(); - this.dataSource = puDefinition.getConfig().getDataSource(); this.isReactive = puDefinition.isReactive(); this.fromPersistenceXml = puDefinition.isFromPersistenceXml(); this.additionalIntegrators = additionalIntegrators; @@ -136,6 +135,8 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti final RecordableBootstrap ssrBuilder = RecordableBootstrapFactory.createRecordableBootstrapBuilder(puDefinition); + // Should be set before calling mergeSettings() + this.multiTenancyStrategy = puDefinition.getConfig().getMultiTenancyStrategy(); final MergedSettings mergedSettings = mergeSettings(puDefinition); this.buildTimeSettings = createBuildTimeSettings(puDefinition, mergedSettings.getConfigurationValues()); @@ -198,6 +199,7 @@ public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefiniti // for the time being we want to revoke access to the temp ClassLoader if one // was passed metamodelBuilder.applyTempClassLoader(null); + } private BuildTimeSettings createBuildTimeSettings(QuarkusPersistenceUnitDefinition puDefinition, @@ -248,8 +250,8 @@ private MergedSettings mergeSettings(QuarkusPersistenceUnitDefinition puDefiniti cfg.put(PERSISTENCE_UNIT_NAME, persistenceUnit.getName()); - MultiTenancyStrategy multiTenancyStrategy = puDefinition.getConfig().getMultiTenancyStrategy(); - if (multiTenancyStrategy != null && multiTenancyStrategy != MultiTenancyStrategy.NONE) { + if (multiTenancyStrategy != null && multiTenancyStrategy != MultiTenancyStrategy.NONE + && multiTenancyStrategy != MultiTenancyStrategy.DISCRIMINATOR) { // We need to initialize the multi tenant connection provider // on static init as it is used in MetadataBuildingOptionsImpl // to determine if multi-tenancy is enabled. @@ -423,7 +425,7 @@ public RecordedState build() { destroyServiceRegistry(); ProxyDefinitions proxyClassDefinitions = ProxyDefinitions.createFromMetadata(storeableMetadata, preGeneratedProxies); return new RecordedState(dialect, storeableMetadata, buildTimeSettings, getIntegrators(), - providedServices, integrationSettingsBuilder.build(), proxyClassDefinitions, + providedServices, integrationSettingsBuilder.build(), proxyClassDefinitions, multiTenancyStrategy, isReactive, fromPersistenceXml); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java index c7d9c889df755f..d9b728cbea644a 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java @@ -8,6 +8,7 @@ import io.quarkus.hibernate.orm.runtime.BuildTimeSettings; import io.quarkus.hibernate.orm.runtime.IntegrationSettings; +import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; import io.quarkus.hibernate.orm.runtime.proxies.ProxyDefinitions; public final class RecordedState { @@ -19,13 +20,15 @@ public final class RecordedState { private final Collection> providedServices; private final IntegrationSettings integrationSettings; private final ProxyDefinitions proxyClassDefinitions; + private final MultiTenancyStrategy multiTenancyStrategy; + private final boolean isReactive; private final boolean fromPersistenceXml; public RecordedState(Dialect dialect, PrevalidatedQuarkusMetadata metadata, BuildTimeSettings settings, Collection integrators, Collection> providedServices, IntegrationSettings integrationSettings, - ProxyDefinitions classDefinitions, + ProxyDefinitions classDefinitions, MultiTenancyStrategy strategy, boolean isReactive, boolean fromPersistenceXml) { this.dialect = dialect; this.metadata = metadata; @@ -34,6 +37,7 @@ public RecordedState(Dialect dialect, PrevalidatedQuarkusMetadata metadata, this.providedServices = providedServices; this.integrationSettings = integrationSettings; this.proxyClassDefinitions = classDefinitions; + this.multiTenancyStrategy = strategy; this.isReactive = isReactive; this.fromPersistenceXml = fromPersistenceXml; } @@ -66,6 +70,10 @@ public ProxyDefinitions getProxyClassDefinitions() { return proxyClassDefinitions; } + public MultiTenancyStrategy getMultiTenancyStrategy() { + return multiTenancyStrategy; + } + public boolean isReactive() { return isReactive; } diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java index 98a1147dc3f664..3e94c94fd13673 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java @@ -207,7 +207,7 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, standardServiceRegistry /* Mostly ignored! (yet needs to match) */, runtimeSettings, - validatorFactory, cdiBeanManager); + validatorFactory, cdiBeanManager, recordedState.getMultiTenancyStrategy()); } log.debug("Found no matching persistence units"); diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java index 49944951a6a0b0..630742c7adc6c4 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastBootReactiveEntityManagerFactoryBuilder.java @@ -10,14 +10,16 @@ import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings; import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder; +import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy; import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata; public final class FastBootReactiveEntityManagerFactoryBuilder extends FastBootEntityManagerFactoryBuilder { public FastBootReactiveEntityManagerFactoryBuilder(PrevalidatedQuarkusMetadata metadata, String persistenceUnitName, StandardServiceRegistry standardServiceRegistry, RuntimeSettings runtimeSettings, Object validatorFactory, - Object cdiBeanManager) { - super(metadata, persistenceUnitName, standardServiceRegistry, runtimeSettings, validatorFactory, cdiBeanManager); + Object cdiBeanManager, MultiTenancyStrategy strategy) { + super(metadata, persistenceUnitName, standardServiceRegistry, runtimeSettings, validatorFactory, + cdiBeanManager, strategy); } @Override diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/pom.xml b/integration-tests/hibernate-orm-tenancy/discriminator/pom.xml new file mode 100644 index 00000000000000..717941c2d5e1ae --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/pom.xml @@ -0,0 +1,181 @@ + + + + + quarkus-integration-test-hibernate-orm-tenancy + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-hibernate-orm-tenancy-discriminator + Quarkus - Integration Tests - Hibernate - Tenancy - Discriminator + Tests for Hibernate ORM Multitenancy using column discriminator, running with the PostgreSQL database + + + + io.quarkus + quarkus-undertow + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-jackson + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-postgresql-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-undertow-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-postgresql + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + false + + + + + + + + diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/CustomTenantResolver.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/CustomTenantResolver.java new file mode 100644 index 00000000000000..424bb8087c0c36 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/CustomTenantResolver.java @@ -0,0 +1,39 @@ +package io.quarkus.it.hibernate.multitenancy.fruit; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; + +import org.jboss.logging.Logger; + +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import io.quarkus.hibernate.orm.runtime.tenant.TenantResolver; +import io.vertx.ext.web.RoutingContext; + +@PersistenceUnitExtension +@RequestScoped +public class CustomTenantResolver implements TenantResolver { + + private static final Logger LOG = Logger.getLogger(CustomTenantResolver.class); + + @Inject + RoutingContext context; + + @Override + public String getDefaultTenantId() { + return "base"; + } + + @Override + public String resolveTenantId() { + String path = context.request().path(); + final String tenantId; + if (path.startsWith("/mycompany")) { + tenantId = "mycompany"; + } else { + tenantId = getDefaultTenantId(); + } + LOG.debugv("TenantId = {0}", tenantId); + return tenantId; + } + +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/Fruit.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/Fruit.java new file mode 100644 index 00000000000000..5486526247bace --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/Fruit.java @@ -0,0 +1,98 @@ +package io.quarkus.it.hibernate.multitenancy.fruit; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.NamedQuery; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.xml.bind.annotation.XmlRootElement; + +import org.hibernate.annotations.TenantId; + +@Entity +@Table(name = "known_fruits") +@NamedQuery(name = "Fruits.findAll", query = "SELECT f FROM Fruit f ORDER BY f.name") +@NamedQuery(name = "Fruits.findByName", query = "SELECT f FROM Fruit f WHERE f.name=:name") +@XmlRootElement(name = "fruit") +public class Fruit { + + @TenantId + @Column(length = 40) + private String tenantId; + + @Id + @SequenceGenerator(name = "fruitsSequence", sequenceName = "known_fruits_id_seq", allocationSize = 1, initialValue = 10) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "fruitsSequence") + private Integer id; + + @Column(length = 40, unique = true) + private String name; + + public Fruit() { + } + + public Fruit(String name) { + this.name = name; + } + + public Fruit(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Fruit other = (Fruit) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + @Override + public String toString() { + return "Fruit [id=" + id + ", name=" + name + "]"; + } + +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/FruitResource.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/FruitResource.java new file mode 100644 index 00000000000000..44fb2aba771b06 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/fruit/FruitResource.java @@ -0,0 +1,207 @@ +package io.quarkus.it.hibernate.multitenancy.fruit; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.jaxrs.PathParam; + +@ApplicationScoped +@Produces("application/json") +@Consumes("application/json") +@Path("/") +public class FruitResource { + + private static final Logger LOG = Logger.getLogger(FruitResource.class.getName()); + + @Inject + EntityManager entityManager; + + @GET + @Path("fruits") + public Fruit[] getDefault() { + return get(); + } + + @GET + @Path("{tenant}/fruits") + public Fruit[] getTenant() { + return get(); + } + + private Fruit[] get() { + return entityManager.createNamedQuery("Fruits.findAll", Fruit.class) + .getResultList().toArray(new Fruit[0]); + } + + @GET + @Path("fruits/{id}") + public Fruit getSingleDefault(@PathParam("id") int id) { + return findById(id); + } + + @GET + @Path("{tenant}/fruits/{id}") + public Fruit getSingleTenant(@PathParam("id") int id) { + return findById(id); + } + + private Fruit findById(int id) { + Fruit entity = entityManager.find(Fruit.class, id); + if (entity == null) { + throw new WebApplicationException("Fruit with id of " + id + " does not exist.", 404); + } + return entity; + } + + @POST + @Transactional + @Path("fruits") + public Response createDefault(@NotNull Fruit fruit) { + return create(fruit); + } + + @POST + @Transactional + @Path("{tenant}/fruits") + public Response createTenant(@NotNull Fruit fruit) { + return create(fruit); + } + + private Response create(@NotNull Fruit fruit) { + if (fruit.getId() != null) { + throw new WebApplicationException("Id was invalidly set on request.", 422); + } + LOG.debugv("Create {0}", fruit.getName()); + entityManager.persist(fruit); + return Response.ok(fruit).status(201).build(); + } + + @PUT + @Path("fruits/{id}") + @Transactional + public Fruit updateDefault(@PathParam("id") int id, @NotNull Fruit fruit) { + return update(id, fruit); + } + + @PUT + @Path("{tenant}/fruits/{id}") + @Transactional + public Fruit updateTenant(@PathParam("id") int id, @NotNull Fruit fruit) { + return update(id, fruit); + } + + private Fruit update(@NotNull @PathParam("id") int id, @NotNull Fruit fruit) { + if (fruit.getName() == null) { + throw new WebApplicationException("Fruit Name was not set on request.", 422); + } + + Fruit entity = entityManager.find(Fruit.class, id); + if (entity == null) { + throw new WebApplicationException("Fruit with id of " + id + " does not exist.", 404); + } + entity.setName(fruit.getName()); + + LOG.debugv("Update #{0} {1}", fruit.getId(), fruit.getName()); + + return entity; + } + + @DELETE + @Path("fruits/{id}") + @Transactional + public Response deleteDefault(@PathParam("id") int id) { + return delete(id); + } + + @DELETE + @Path("{tenant}/fruits/{id}") + @Transactional + public Response deleteTenant(@PathParam("id") int id) { + return delete(id); + } + + private Response delete(int id) { + Fruit fruit = entityManager.getReference(Fruit.class, id); + if (fruit == null) { + throw new WebApplicationException("Fruit with id of " + id + " does not exist.", 404); + } + LOG.debugv("Delete #{0} {1}", fruit.getId(), fruit.getName()); + entityManager.remove(fruit); + return Response.status(204).build(); + } + + @GET + @Path("fruitsFindBy") + public Response findByDefault(@NotNull @QueryParam("type") String type, @NotNull @QueryParam("value") String value) { + return findBy(type, value); + } + + @GET + @Path("{tenant}/fruitsFindBy") + public Response findByTenant(@NotNull @QueryParam("type") String type, @NotNull @QueryParam("value") String value) { + return findBy(type, value); + } + + private Response findBy(@NotNull String type, @NotNull String value) { + if (!"name".equalsIgnoreCase(type)) { + throw new IllegalArgumentException("Currently only 'fruitsFindBy?type=name' is supported"); + } + List list = entityManager.createNamedQuery("Fruits.findByName", Fruit.class).setParameter("name", value) + .getResultList(); + if (list.size() == 0) { + return Response.status(404).build(); + } + Fruit fruit = list.get(0); + return Response.status(200).entity(fruit).build(); + } + + @Provider + public static class ErrorMapper implements ExceptionMapper { + + @Override + public Response toResponse(Exception exception) { + LOG.error("Failed to handle request", exception); + + int code = 500; + if (exception instanceof WebApplicationException) { + code = ((WebApplicationException) exception).getResponse().getStatus(); + } + + Error error = new Error(); + error.exceptionType = exception.getClass().getName(); + error.code = code; + error.error = exception.getMessage(); + + return Response.status(code) + .type(MediaType.APPLICATION_JSON) + .entity(error) + .build(); + } + } + + public static class Error { + + public String exceptionType; + public int code; + public String error; + } +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryResource.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryResource.java new file mode 100644 index 00000000000000..cc87aa657e18bb --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryResource.java @@ -0,0 +1,39 @@ +package io.quarkus.it.hibernate.multitenancy.inventory; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import io.quarkus.hibernate.orm.PersistenceUnit; + +@ApplicationScoped +@Produces("application/json") +@Consumes("application/json") +@Path("/") +public class InventoryResource { + + @Inject + @PersistenceUnit("inventory") + EntityManager entityManager; + + @GET + @Path("inventory") + public Plane[] getDefault() { + return get(); + } + + @GET + @Path("{tenant}/inventory") + public Plane[] getTenant() { + return get(); + } + + private Plane[] get() { + return entityManager.createNamedQuery("Planes.findAll", Plane.class) + .getResultList().toArray(new Plane[0]); + } +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryTenantResolver.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryTenantResolver.java new file mode 100644 index 00000000000000..3dc77844c31023 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/InventoryTenantResolver.java @@ -0,0 +1,39 @@ +package io.quarkus.it.hibernate.multitenancy.inventory; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; + +import org.jboss.logging.Logger; + +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import io.quarkus.hibernate.orm.runtime.tenant.TenantResolver; +import io.vertx.ext.web.RoutingContext; + +@PersistenceUnitExtension("inventory") +@RequestScoped +public class InventoryTenantResolver implements TenantResolver { + + private static final Logger LOG = Logger.getLogger(InventoryTenantResolver.class); + + @Inject + RoutingContext context; + + @Override + public String getDefaultTenantId() { + return "inventory"; + } + + @Override + public String resolveTenantId() { + String path = context.request().path(); + final String tenantId; + if (path.startsWith("/mycompany")) { + tenantId = "inventorymycompany"; + } else { + tenantId = getDefaultTenantId(); + } + LOG.debugv("TenantId = {0}", tenantId); + return tenantId; + } + +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/Plane.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/Plane.java new file mode 100644 index 00000000000000..ddf384c73f024a --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/java/io/quarkus/it/hibernate/multitenancy/inventory/Plane.java @@ -0,0 +1,74 @@ +package io.quarkus.it.hibernate.multitenancy.inventory; + +import java.util.Objects; + +import jakarta.persistence.*; + +import org.hibernate.annotations.TenantId; + +@Entity +@Table(name = "plane") +@NamedQuery(name = "Planes.findAll", query = "SELECT p FROM Plane p ORDER BY p.name") +public class Plane { + + @TenantId + @Column(length = 40) + private String tenantId; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "plane_iq_seq") + private Long id; + + @Column(length = 40, unique = true) + private String name; + + public Plane() { + } + + public Plane(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Plane other = (Plane) obj; + return Objects.equals(this.name, other.name) + && Objects.equals(this.id, other.id); + } + + @Override + public String toString() { + return "Plane [id=" + id + ", name=" + name + "]"; + } +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/application.properties b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/application.properties new file mode 100644 index 00000000000000..b01c631e80f96d --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/application.properties @@ -0,0 +1,14 @@ +# Database configuration +quarkus.datasource.db-kind=postgresql + +# Default persistence unit +quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.hibernate-orm.multitenant=discriminator +quarkus.hibernate-orm.packages=io.quarkus.it.hibernate.multitenancy.fruit + +# Inventory persistence unit +quarkus.hibernate-orm."inventory".database.generation=drop-and-create +quarkus.hibernate-orm."inventory".multitenant=discriminator +quarkus.hibernate-orm."inventory".datasource= +quarkus.hibernate-orm."inventory".packages=io.quarkus.it.hibernate.multitenancy.inventory +quarkus.hibernate-orm."inventory".sql-load-script=import-inventory.sql diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import-inventory.sql b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import-inventory.sql new file mode 100644 index 00000000000000..e3fe72a3ba6ea0 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import-inventory.sql @@ -0,0 +1,5 @@ +INSERT INTO plane(tenantId, id, name) VALUES ('inventory', 1, 'Airbus A320'); +INSERT INTO plane(tenantId, id, name) VALUES ('inventory', 2, 'Airbus A350'); + +INSERT INTO plane(tenantId, id, name) VALUES ('inventorymycompany', 3, 'Boeing 737'); +INSERT INTO plane(tenantId, id, name) VALUES ('inventorymycompany', 4, 'Boeing 747'); \ No newline at end of file diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import.sql b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import.sql new file mode 100644 index 00000000000000..ea0527f2bdbdaa --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/main/resources/import.sql @@ -0,0 +1,7 @@ +INSERT INTO known_fruits(tenantId, id, name) VALUES ('base', 1, 'Cherry'); +INSERT INTO known_fruits(tenantId, id, name) VALUES ('base', 2, 'Apple'); +INSERT INTO known_fruits(tenantId, id, name) VALUES ('base', 3, 'Banana'); + +INSERT INTO known_fruits(tenantId, id, name) VALUES ('mycompany', 4, 'Avocado'); +INSERT INTO known_fruits(tenantId, id, name) VALUES ('mycompany', 5, 'Apricots'); +INSERT INTO known_fruits(tenantId, id, name) VALUES ('mycompany', 6, 'Blackberries'); diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityInGraalITCase.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityInGraalITCase.java new file mode 100644 index 00000000000000..d9ae0c86a83ea9 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityInGraalITCase.java @@ -0,0 +1,11 @@ +package io.quarkus.it.hibernate.multitenancy.fruit; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +/** + * Test various JPA operations running in native mode + */ +@QuarkusIntegrationTest +public class HibernateTenancyFunctionalityInGraalITCase extends HibernateTenancyFunctionalityTest { + +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityTest.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityTest.java new file mode 100644 index 00000000000000..6d3090f0d02084 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/fruit/HibernateTenancyFunctionalityTest.java @@ -0,0 +1,167 @@ +package io.quarkus.it.hibernate.multitenancy.fruit; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.ws.rs.core.Response.Status; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import io.restassured.response.Response; + +/** + * Test various Hibernate Multitenancy operations running in Quarkus + */ +@QuarkusTest +public class HibernateTenancyFunctionalityTest { + + @BeforeEach + public void cleanup() { + + deleteIfExists("", "Dragonfruit"); + deleteIfExists("", "Gooseberry"); + deleteIfExists("/mycompany", "Damson"); + deleteIfExists("/mycompany", "Grapefruit"); + + } + + @Test + public void testAddDeleteDefaultTenant() throws Exception { + + // Create fruit for default 'base' tenant + given().with().body(new Fruit("Delete")).contentType(ContentType.JSON).when().post("/fruits").then() + .assertThat().statusCode(is(Status.CREATED.getStatusCode())); + + // Get it + Fruit newFruit = findByName("", "Delete"); + + // Delete it + given().pathParam("id", newFruit.getId()).contentType("application/json").accept("application/json") + .when().delete("/fruits/{id}").then().assertThat().statusCode(is(Status.NO_CONTENT.getStatusCode())); + + } + + @Test + public void testGetFruitsDefaultTenant() throws Exception { + + Fruit[] fruits = given().when().get("/fruits").then().assertThat() + .statusCode(is(Status.OK.getStatusCode())).extract() + .as(Fruit[].class); + assertThat(fruits, arrayContaining(new Fruit(2, "Apple"), new Fruit(3, "Banana"), new Fruit(1, "Cherry"))); + + } + + @Test + public void testGetFruitsTenantMycompany() throws Exception { + + Fruit[] fruits = given().when().get("/mycompany/fruits").then().assertThat() + .statusCode(is(Status.OK.getStatusCode())).extract() + .as(Fruit[].class); + assertThat(fruits, arrayWithSize(3)); + assertThat(fruits, arrayContaining(new Fruit(5, "Apricots"), new Fruit(4, "Avocado"), new Fruit(6, "Blackberries"))); + + } + + @Test + public void testPostFruitDefaultTenant() throws Exception { + + // Create fruit for default 'base' tenant + Fruit newFruit = new Fruit("Dragonfruit"); + given().with().body(newFruit).contentType(ContentType.JSON).when().post("/fruits").then() + .assertThat() + .statusCode(is(Status.CREATED.getStatusCode())); + + // Getting it directly should return the new fruit + Fruit dragonFruit = findByName("", newFruit.getName()); + assertThat(dragonFruit, not(is(nullValue()))); + + // Getting fruit list should also contain the new fruit + Fruit[] baseFruits = given().when().get("/fruits").then().assertThat() + .statusCode(is(Status.OK.getStatusCode())).extract() + .as(Fruit[].class); + assertThat(baseFruits, arrayWithSize(4)); + assertThat(baseFruits, + arrayContaining(new Fruit(2, "Apple"), new Fruit(3, "Banana"), new Fruit(1, "Cherry"), dragonFruit)); + + // The other tenant should NOT have the new fruit + Fruit[] mycompanyFruits = given().when().get("/mycompany/fruits").then().assertThat() + .statusCode(is(Status.OK.getStatusCode())) + .extract().as(Fruit[].class); + assertThat(mycompanyFruits, arrayWithSize(3)); + assertThat(mycompanyFruits, + arrayContaining(new Fruit(5, "Apricots"), new Fruit(4, "Avocado"), new Fruit(6, "Blackberries"))); + + // Getting it directly should also NOT return the new fruit + assertThat(findByName("/mycompany", newFruit.getName()), is(nullValue())); + + } + + @Test + public void testUpdateFruitDefaultTenant() throws Exception { + + // Create fruits for both tenants + + Fruit newFruitBase = new Fruit("Dragonfruit"); + given().with().body(newFruitBase).contentType(ContentType.JSON).when().post("/fruits").then() + .assertThat() + .statusCode(is(Status.CREATED.getStatusCode())); + Fruit baseFruit = findByName("", newFruitBase.getName()); + assertThat(baseFruit, not(is(nullValue()))); + + Fruit newFruitMycompany = new Fruit("Damson"); + given().with().body(newFruitMycompany).contentType(ContentType.JSON).when().post("/mycompany/fruits") + .then().assertThat() + .statusCode(is(Status.CREATED.getStatusCode())); + Fruit mycompanyFruit = findByName("/mycompany", newFruitMycompany.getName()); + assertThat(mycompanyFruit, not(is(nullValue()))); + + // Update both + + String baseFruitName = "Gooseberry"; + baseFruit.setName(baseFruitName); + given().with().body(baseFruit).contentType(ContentType.JSON).when() + .put("/fruits/{id}", baseFruit.getId()).then().assertThat() + .statusCode(is(Status.OK.getStatusCode())); + + String mycompanyFruitName = "Grapefruit"; + mycompanyFruit.setName(mycompanyFruitName); + given().with().body(mycompanyFruit).contentType(ContentType.JSON).when() + .put("/mycompany/fruits/{id}", mycompanyFruit.getId()) + .then().assertThat().statusCode(is(Status.OK.getStatusCode())); + + // Check if we can get them back and they only exist for one tenant + + assertThat(findByName("", baseFruitName), is(not(nullValue()))); + assertThat(findByName("/mycompany", baseFruitName), is(nullValue())); + + assertThat(findByName("/mycompany", mycompanyFruitName), is(not(nullValue()))); + assertThat(findByName("", mycompanyFruitName), is(nullValue())); + + } + + private Fruit findByName(String tenantPath, String name) { + Response response = given().when().get(tenantPath + "/fruitsFindBy?type=name&value={name}", name); + if (response.getStatusCode() == Status.OK.getStatusCode()) { + return response.as(Fruit.class); + } + return null; + } + + private void deleteIfExists(String tenantPath, String name) { + Fruit dragonFruit = findByName(tenantPath, name); + if (dragonFruit != null) { + given().pathParam("id", dragonFruit.getId()).when().delete(tenantPath + "/fruits/{id}").then() + .assertThat() + .statusCode(is(Status.NO_CONTENT.getStatusCode())); + } + } + +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTest.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTest.java new file mode 100644 index 00000000000000..0bc439eadd99c1 --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTest.java @@ -0,0 +1,36 @@ +package io.quarkus.it.hibernate.multitenancy.inventory; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.is; + +import jakarta.ws.rs.core.Response.Status; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class HibernateNamedPersistenceUnitTest { + + @Test + public void testGetPlanesDefaultTenant() throws Exception { + + Plane[] planes = given().when().get("/inventory").then().assertThat() + .statusCode(is(Status.OK.getStatusCode())).extract() + .as(Plane[].class); + assertThat(planes, arrayContaining(new Plane(1L, "Airbus A320"), new Plane(2L, "Airbus A350"))); + + } + + @Test + public void testGetPlanesTenantMycompany() throws Exception { + + Plane[] planes = given().when().get("/mycompany/inventory").then().assertThat() + .statusCode(is(Status.OK.getStatusCode())).extract() + .as(Plane[].class); + assertThat(planes, arrayContaining(new Plane(3L, "Boeing 737"), new Plane(4L, "Boeing 747"))); + + } +} diff --git a/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTestInGraalITCase.java b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTestInGraalITCase.java new file mode 100644 index 00000000000000..79ba8001baed8a --- /dev/null +++ b/integration-tests/hibernate-orm-tenancy/discriminator/src/test/java/io/quarkus/it/hibernate/multitenancy/inventory/HibernateNamedPersistenceUnitTestInGraalITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.hibernate.multitenancy.inventory; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class HibernateNamedPersistenceUnitTestInGraalITCase extends HibernateNamedPersistenceUnitTest { + +} diff --git a/integration-tests/hibernate-orm-tenancy/pom.xml b/integration-tests/hibernate-orm-tenancy/pom.xml index 08cfae88cf0b90..c88c3bd3628d9a 100644 --- a/integration-tests/hibernate-orm-tenancy/pom.xml +++ b/integration-tests/hibernate-orm-tenancy/pom.xml @@ -17,6 +17,7 @@ datasource connection-resolver schema + discriminator connection-resolver-legacy-qualifiers From 032e04100a7c42f20831ea7df8005b9e32c1dacf Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Mon, 1 May 2023 17:14:22 +0300 Subject: [PATCH 191/333] disable gradle enterprise build scan as breaks tcks --- .mvn/{extensions.xml => extensions.xml.disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .mvn/{extensions.xml => extensions.xml.disabled} (100%) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml.disabled similarity index 100% rename from .mvn/extensions.xml rename to .mvn/extensions.xml.disabled From b9c703496c74a927a6dd3acbf9d69519e8b4ff61 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Thu, 27 Apr 2023 11:35:28 +0200 Subject: [PATCH 192/333] Arc: Implement validation for various erroneous cases of bean metadata injection (@Intercepted Bean, Bean, Interceptor); enable relevant TCK tests --- .../reactivemessaging/DefaultScopeTest.java | 4 +- .../quarkus/arc/processor/BeanDeployment.java | 2 +- .../java/io/quarkus/arc/processor/Beans.java | 6 +- .../io/quarkus/arc/processor/Decorators.java | 3 +- .../io/quarkus/arc/processor/DotNames.java | 1 + .../io/quarkus/arc/processor/Injection.java | 130 +++++++++++++++++- .../quarkus/arc/processor/Interceptors.java | 3 +- .../src/test/resources/testng.xml | 75 ---------- .../BeanMetadataWrongTypeTest.java | 35 +++++ ...terceptedBeanConstructorInjectionTest.java | 36 +++++ .../InterceptedBeanFieldInjectionTest.java | 35 +++++ ...terceptedBeanInitializerInjectionTest.java | 36 +++++ ...InterceptedBeanNotUnboundWildcardTest.java | 61 ++++++++ ...InterceptorBeanInjectionBeanClassTest.java | 34 +++++ ...terceptorBeanInjectionConstructorTest.java | 36 +++++ .../InterceptorBeanInjectionDisposerTest.java | 41 ++++++ ...terceptorBeanInjectionInitializerTest.java | 36 +++++ .../InterceptorBeanInjectionProducerTest.java | 36 +++++ .../InterceptorBeanWrongTypeTest.java | 58 ++++++++ .../InterceptedBeanInjectionTest.java | 2 +- 20 files changed, 580 insertions(+), 90 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/beanMetadata/BeanMetadataWrongTypeTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanConstructorInjectionTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanFieldInjectionTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanInitializerInjectionTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanNotUnboundWildcardTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionBeanClassTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionConstructorTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionDisposerTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionInitializerTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionProducerTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanWrongTypeTest.java diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/DefaultScopeTest.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/DefaultScopeTest.java index 90dc2222671cad..66947b85bfab11 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/DefaultScopeTest.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/DefaultScopeTest.java @@ -55,9 +55,9 @@ public String toUpperCase(String payload) { public static class NoScopeButResource { @Inject - Bean bean; + Bean bean; - public Bean getBean() { + public Bean getBean() { return bean; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 4d943a25ab3d29..83ccfac1497a45 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -1154,7 +1154,7 @@ private List findBeans(Collection beanDefiningAnnotations, Li BeanInfo declaringBean = beanClassToBean.get(disposerMethod.declaringClass()); if (declaringBean != null) { Injection injection = Injection.forDisposer(disposerMethod, declaringBean.getImplClazz(), this, - injectionPointTransformer, declaringBean); + injectionPointTransformer); injection.init(declaringBean); disposers.add(new DisposerInfo(declaringBean, disposerMethod, injection)); injectionPoints.addAll(injection.injectionPoints); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 035ffb1ffd0910..621be618c9787d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -189,7 +189,8 @@ static BeanInfo createProducerMethod(Set beanTypes, MethodInfo producerMet + "its scope must be @Dependent: " + producerMethod); } - List injections = Injection.forBean(producerMethod, declaringBean, beanDeployment, transformer); + List injections = Injection.forBean(producerMethod, declaringBean, beanDeployment, transformer, + Injection.BeanType.PRODUCER_METHOD); BeanInfo bean = new BeanInfo(producerMethod, beanDeployment, scope, beanTypes, qualifiers, injections, declaringBean, disposer, isAlternative, stereotypes, name, isDefaultBean, null, priority); for (Injection injection : injections) { @@ -1293,7 +1294,8 @@ BeanInfo create() { } } - List injections = Injection.forBean(beanClass, null, beanDeployment, transformer); + List injections = Injection.forBean(beanClass, null, beanDeployment, transformer, + Injection.BeanType.MANAGED_BEAN); BeanInfo bean = new BeanInfo(beanClass, beanDeployment, scope, types, qualifiers, injections, null, null, isAlternative, stereotypes, name, isDefaultBean, null, priority); for (Injection injection : injections) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java index 467a74486a25dc..ae8f38cbc3f73b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java @@ -31,7 +31,8 @@ static DecoratorInfo createDecorator(ClassInfo decoratorClass, BeanDeployment be // Find the delegate injection point List delegateInjectionPoints = new LinkedList<>(); - List injections = Injection.forBean(decoratorClass, null, beanDeployment, transformer); + List injections = Injection.forBean(decoratorClass, null, beanDeployment, transformer, + Injection.BeanType.DECORATOR); for (Injection injection : injections) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { if (injectionPoint.isDelegate()) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java index 3100592f6ce19c..f70e5577ff3ade 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java @@ -87,6 +87,7 @@ public final class DotNames { public static final DotName PROVIDER = create(Provider.class); public static final DotName INJECTION_POINT = create(InjectionPoint.class); public static final DotName INTERCEPTOR = create(Interceptor.class); + public static final DotName INTERCEPTOR_BEAN = create(jakarta.enterprise.inject.spi.Interceptor.class); public static final DotName INTERCEPTOR_BINDING = create(InterceptorBinding.class); public static final DotName INTERCEPTED = create(Intercepted.class); public static final DotName AROUND_INVOKE = create(AroundInvoke.class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java index 1f14b611428e7f..47f5e780daa52b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java @@ -20,6 +20,7 @@ import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; import org.jboss.logging.Logger; @@ -45,13 +46,115 @@ public class Injection { static Injection forSyntheticBean(Iterable injectionPoints) { List ips = new ArrayList<>(); for (TypeAndQualifiers injectionPoint : injectionPoints) { - ips.add(InjectionPointInfo.fromSyntheticInjectionPoint(injectionPoint)); + InjectionPointInfo injectionPointInfo = InjectionPointInfo.fromSyntheticInjectionPoint(injectionPoint); + validateInjections(injectionPointInfo, BeanType.SYNTHETIC_BEAN); + ips.add(injectionPointInfo); } return new Injection(null, ips); } + private static void validateInjections(InjectionPointInfo injectionPointInfo, BeanType beanType) { + // Mostly validation related to Bean metadata injection restrictions + // see https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0.html#bean_metadata + if (beanType == BeanType.MANAGED_BEAN || beanType == BeanType.SYNTHETIC_BEAN || beanType == BeanType.PRODUCER_METHOD) { + // If an Interceptor instance is injected into a bean instance other than an interceptor instance, + // the container automatically detects the problem and treats it as a definition error. + if (injectionPointInfo.getType().name().equals(DotNames.INTERCEPTOR_BEAN)) { + throw new DefinitionException("Invalid injection of Interceptor bean, can only be used in interceptors " + + "but was detected in: " + injectionPointInfo.getTargetInfo()); + } + + // If a Bean instance with qualifier @Intercepted is injected into a bean instance other than an interceptor + // instance, the container automatically detects the problem and treats it as a definition error. + if (injectionPointInfo.getType().name().equals(DotNames.BEAN) + && injectionPointInfo.getRequiredQualifier(DotNames.INTERCEPTED) != null) { + throw new DefinitionException( + "Invalid injection of @Intercepted Bean, can only be injected into interceptors " + + "but was detected in: " + injectionPointInfo.getTargetInfo()); + } + + // the injection point is a field, an initializer method parameter or a bean constructor, with qualifier + // @Default, then the type parameter of the injected Bean, or Interceptor must be the same as the type + // declaring the injection point + if (injectionPointInfo.getRequiredType().name().equals(DotNames.BEAN) + && injectionPointInfo.getRequiredType().kind() == Type.Kind.PARAMETERIZED_TYPE + && injectionPointInfo.getRequiredType().asParameterizedType().arguments().size() == 1) { + Type actualType = injectionPointInfo.getRequiredType().asParameterizedType().arguments().get(0); + AnnotationTarget ipTarget = injectionPointInfo.getTarget(); + DotName expectedType = null; + if (ipTarget.kind() == Kind.FIELD) { + // field injection derives this from the class + expectedType = ipTarget.asField().declaringClass().name(); + } else if (ipTarget.kind() == Kind.METHOD) { + // the injection point is a producer method parameter then the type parameter of the injected Bean + // must be the same as the producer method return type + if (beanType == BeanType.PRODUCER_METHOD) { + expectedType = ipTarget.asMethod().returnType().name(); + } else { + expectedType = ipTarget.asMethod().declaringClass().name(); + } + } + if (expectedType != null + // This is very rudimentary check, might need to be expanded? + && !expectedType.equals(actualType.name())) { + throw new DefinitionException( + "Type of injected Bean does not match the type of the bean declaring the " + + "injection point. Problematic injection point: " + injectionPointInfo.getTargetInfo()); + } + } + } + if (beanType == BeanType.INTERCEPTOR) { + // the injection point is a field, an initializer method parameter or a bean constructor of an interceptor, + // with qualifier @Intercepted, then the type parameter of the injected Bean must be an unbounded wildcard + if (injectionPointInfo.getRequiredType().name().equals(DotNames.BEAN) + && injectionPointInfo.getRequiredQualifier(DotNames.INTERCEPTED) != null + && injectionPointInfo.getRequiredType().kind() == Type.Kind.PARAMETERIZED_TYPE) { + ParameterizedType parameterizedType = injectionPointInfo.getRequiredType().asParameterizedType(); + // there should be exactly one param - wildcard - and it has to be unbound; all else is DefinitionException + if (parameterizedType.arguments().size() != 1 + || !(parameterizedType.arguments().get(0).kind() == Type.Kind.WILDCARD_TYPE) + || !(parameterizedType.arguments().get(0).asWildcardType().extendsBound().name().equals(DotNames.OBJECT) + && parameterizedType.arguments().get(0).asWildcardType().superBound() == null)) { + throw new DefinitionException( + "Injected @Intercepted Bean has to use unbound wildcard as its type parameter. " + + "Problematic injection point: " + injectionPointInfo.getTargetInfo()); + } + } + // the injection point is a field, an initializer method parameter or a bean constructor, with qualifier + // @Default, then the type parameter of the injected Bean, or Interceptor must be the same as the type + // declaring the injection point + if (injectionPointInfo.getRequiredType().name().equals(DotNames.INTERCEPTOR_BEAN) + && injectionPointInfo.getRequiredType().kind() == Type.Kind.PARAMETERIZED_TYPE + && injectionPointInfo.getRequiredType().asParameterizedType().arguments().size() == 1) { + Type actualType = injectionPointInfo.getRequiredType().asParameterizedType().arguments().get(0); + AnnotationTarget ipTarget = injectionPointInfo.getTarget(); + DotName expectedType = null; + if (ipTarget.kind() == Kind.FIELD) { + expectedType = ipTarget.asField().declaringClass().name(); + } else if (ipTarget.kind() == Kind.METHOD) { + expectedType = ipTarget.asMethod().declaringClass().name(); + } + if (expectedType != null + // This is very rudimentary check, might need to be expanded? + && !expectedType.equals(actualType.name())) { + throw new DefinitionException( + "Type of injected Interceptor does not match the type of the bean declaring the " + + "injection point. Problematic injection point: " + injectionPointInfo.getTargetInfo()); + } + } + } + } + + private static void validateInjections(List injections, BeanType beanType) { + for (Injection injection : injections) { + for (InjectionPointInfo ipi : injection.injectionPoints) { + validateInjections(ipi, beanType); + } + } + } + static List forBean(AnnotationTarget beanTarget, BeanInfo declaringBean, BeanDeployment beanDeployment, - InjectionPointModifier transformer) { + InjectionPointModifier transformer, BeanType beanType) { if (Kind.CLASS.equals(beanTarget.kind())) { List injections = forClassBean(beanTarget.asClass(), beanTarget.asClass(), beanDeployment, transformer, false, new HashSet<>()); @@ -109,7 +212,7 @@ static List forBean(AnnotationTarget beanTarget, BeanInfo declaringBe + beanTarget.asClass() + "." + initializerMethod.name()); } } - + validateInjections(injections, beanType); return injections; } else if (Kind.METHOD.equals(beanTarget.kind())) { MethodInfo producerMethod = beanTarget.asMethod(); @@ -139,12 +242,22 @@ static List forBean(AnnotationTarget beanTarget, BeanInfo declaringBe return Collections.emptyList(); } // All parameters are injection points - return Collections.singletonList(new Injection(producerMethod, + List injections = Collections.singletonList(new Injection(producerMethod, InjectionPointInfo.fromMethod(producerMethod, declaringBean.getImplClazz(), beanDeployment, transformer))); + validateInjections(injections, beanType); + return injections; } throw new IllegalArgumentException("Unsupported annotation target"); } + static enum BeanType { + MANAGED_BEAN, + PRODUCER_METHOD, + SYNTHETIC_BEAN, + INTERCEPTOR, + DECORATOR + } + // returns injections in the order they should be performed private static List forClassBean(ClassInfo beanClass, ClassInfo classInfo, BeanDeployment beanDeployment, InjectionPointModifier transformer, boolean skipConstructors, Set seenMethods) { @@ -229,7 +342,7 @@ private static List forClassBean(ClassInfo beanClass, ClassInfo class } static Injection forDisposer(MethodInfo disposerMethod, ClassInfo beanClass, BeanDeployment beanDeployment, - InjectionPointModifier transformer, BeanInfo declaringBean) { + InjectionPointModifier transformer) { if (beanDeployment.hasAnnotation(disposerMethod, DotNames.INJECT)) { throw new DefinitionException("Disposer method must not be annotated @Inject " + "(alternatively, initializer method must not have a @Disposes parameter): " @@ -258,8 +371,11 @@ static Injection forDisposer(MethodInfo disposerMethod, ClassInfo beanClass, Bea + disposerMethod); } - return new Injection(disposerMethod, InjectionPointInfo.fromMethod(disposerMethod, beanClass, beanDeployment, - annotations -> annotations.stream().anyMatch(a -> a.name().equals(DotNames.DISPOSES)), transformer)); + Injection injection = new Injection(disposerMethod, + InjectionPointInfo.fromMethod(disposerMethod, beanClass, beanDeployment, + annotations -> annotations.stream().anyMatch(a -> a.name().equals(DotNames.DISPOSES)), transformer)); + injection.injectionPoints.forEach(ipi -> validateInjections(ipi, BeanType.MANAGED_BEAN)); + return injection; } static Injection forObserver(MethodInfo observerMethod, ClassInfo beanClass, BeanDeployment beanDeployment, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java index 9e81eba2ca1aa5..f8d0c7f6f84867 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java @@ -71,7 +71,8 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym return new InterceptorInfo(interceptorClass, beanDeployment, bindings.size() == 1 ? Collections.singleton(bindings.iterator().next()) : Collections.unmodifiableSet(bindings), - Injection.forBean(interceptorClass, null, beanDeployment, transformer), priority); + Injection.forBean(interceptorClass, null, beanDeployment, transformer, Injection.BeanType.INTERCEPTOR), + priority); } private static void addBindings(BeanDeployment beanDeployment, ClassInfo classInfo, Collection bindings, diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 170c0318adf489..d1ce5d1d664fd6 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -62,11 +62,6 @@ - - - - - @@ -78,21 +73,6 @@ - - - - - - - - - - - - - - - @@ -103,11 +83,6 @@ - - - - - @@ -116,31 +91,11 @@ - - - - - - - - - - - - - - - - - - - - @@ -188,11 +143,6 @@ - - - - - @@ -202,16 +152,6 @@ - - - - - - - - - - @@ -244,27 +184,12 @@ - - - - - - - - - - - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/beanMetadata/BeanMetadataWrongTypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/beanMetadata/BeanMetadataWrongTypeTest.java new file mode 100644 index 00000000000000..d88a023f6f6005 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/beanMetadata/BeanMetadataWrongTypeTest.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.test.injection.erroneous.beanMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class BeanMetadataWrongTypeTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(BeanMetadataWrongTypeTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + MyBean(Bean beanMetadata) { + + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanConstructorInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanConstructorInjectionTest.java new file mode 100644 index 00000000000000..78b472ee3bea8f --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanConstructorInjectionTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.injection.erroneous.interceptedBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Intercepted; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedBeanConstructorInjectionTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptedBeanConstructorInjectionTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + MyBean(@Intercepted Bean bean) { + + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanFieldInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanFieldInjectionTest.java new file mode 100644 index 00000000000000..072b81cc8661f5 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanFieldInjectionTest.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.test.injection.erroneous.interceptedBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Intercepted; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedBeanFieldInjectionTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptedBeanFieldInjectionTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + @Intercepted + Bean bean; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanInitializerInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanInitializerInjectionTest.java new file mode 100644 index 00000000000000..4fb868802c80ed --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanInitializerInjectionTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.injection.erroneous.interceptedBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Intercepted; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedBeanInitializerInjectionTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptedBeanInitializerInjectionTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + void init(@Intercepted Bean bean) { + + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanNotUnboundWildcardTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanNotUnboundWildcardTest.java new file mode 100644 index 00000000000000..a4fc28d5c8539b --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptedBean/InterceptedBeanNotUnboundWildcardTest.java @@ -0,0 +1,61 @@ +package io.quarkus.arc.test.injection.erroneous.interceptedBean; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Intercepted; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedBeanNotUnboundWildcardTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptedBeanNotUnboundWildcardTest.class, + MyInterceptor.class, Binding.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @Interceptor + @Priority(1) + @Binding + static class MyInterceptor { + + @Inject + @Intercepted + Bean interceptedBean; + + @AroundInvoke + public Object doNothing(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + @interface Binding { + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionBeanClassTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionBeanClassTest.java new file mode 100644 index 00000000000000..1154d2c0455618 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionBeanClassTest.java @@ -0,0 +1,34 @@ +package io.quarkus.arc.test.injection.erroneous.interceptorBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.inject.spi.Interceptor; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBeanInjectionBeanClassTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptorBeanInjectionBeanClassTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + Interceptor interceptor; + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionConstructorTest.java new file mode 100644 index 00000000000000..114b8c6e65cb96 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionConstructorTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.injection.erroneous.interceptorBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.inject.spi.Interceptor; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBeanInjectionConstructorTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptorBeanInjectionDisposerTest.class, + InterceptorBeanInjectionDisposerTest.MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + public MyBean(Interceptor interceptor) { + + } + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionDisposerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionDisposerTest.java new file mode 100644 index 00000000000000..fc4da8c6ff93ef --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionDisposerTest.java @@ -0,0 +1,41 @@ +package io.quarkus.arc.test.injection.erroneous.interceptorBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.inject.spi.Interceptor; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBeanInjectionDisposerTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptorBeanInjectionDisposerTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Produces + String produceSth() { + return "foo"; + } + + void disposer(@Disposes String s, Interceptor interceptor) { + + } + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionInitializerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionInitializerTest.java new file mode 100644 index 00000000000000..f1bc41d479123e --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionInitializerTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.injection.erroneous.interceptorBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.inject.spi.Interceptor; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBeanInjectionInitializerTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptorBeanInjectionInitializerTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Inject + void initMethod(Interceptor interceptor) { + + } + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionProducerTest.java new file mode 100644 index 00000000000000..274b56fa24d615 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanInjectionProducerTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.injection.erroneous.interceptorBean; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.inject.spi.Interceptor; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBeanInjectionProducerTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptorBeanInjectionProducerTest.class, + MyBean.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @ApplicationScoped + static class MyBean { + + @Produces + String produceSth(Interceptor interceptor) { + return "foo"; + } + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanWrongTypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanWrongTypeTest.java new file mode 100644 index 00000000000000..fbaaef2d786f7f --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/erroneous/interceptorBean/InterceptorBeanWrongTypeTest.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.test.injection.erroneous.interceptorBean; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptorBeanWrongTypeTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder().beanClasses(InterceptorBeanWrongTypeTest.class, + MyInterceptor.class, Binding.class).shouldFail().build(); + + @Test + public void testExceptionThrown() { + Throwable error = container.getFailure(); + assertThat(error).isInstanceOf(DefinitionException.class); + } + + @Interceptor + @Priority(1) + @Binding + static class MyInterceptor { + + @Inject + jakarta.enterprise.inject.spi.Interceptor interceptor; + + @AroundInvoke + public Object doNothing(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + @interface Binding { + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/intercepted/InterceptedBeanInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/intercepted/InterceptedBeanInjectionTest.java index c205e68816d5f4..9f5ec83e245b58 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/intercepted/InterceptedBeanInjectionTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/intercepted/InterceptedBeanInjectionTest.java @@ -38,7 +38,7 @@ public void testInterception() { static class InterceptedBean { @Inject - Bean bean; + Bean bean; @Inject InterceptedDependent dependent; From 033dc605b7257c21b14e8883401044caee42e27d Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Mon, 1 May 2023 13:22:51 -0400 Subject: [PATCH 193/333] Create update guide --- docs/src/main/asciidoc/update-to-quarkus-3.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/update-to-quarkus-3.adoc b/docs/src/main/asciidoc/update-to-quarkus-3.adoc index 5fa1b716967a16..ddc5b8d29e2166 100644 --- a/docs/src/main/asciidoc/update-to-quarkus-3.adoc +++ b/docs/src/main/asciidoc/update-to-quarkus-3.adoc @@ -16,6 +16,7 @@ You can update your projects from Quarkus 2.x to Quarkus 3.x by running an updat The update command primarily uses OpenRewrite recipes to automate updating most of your project's dependencies, source code, and documentation. These recipes cover many but not all of the items described in the link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0[Migration Guide 3.0]. After updating the project, if you do not find all the updates you expect, there are two possible reasons: + - The recipe might not cover an item in your project. - Your project might use an extension that does not support Quarkus 3 yet. From 8168cff7f20869a506a01a6c7d066574c0477cb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 08:19:18 +0000 Subject: [PATCH 194/333] Bump picocli.version from 4.7.1 to 4.7.3 Bumps `picocli.version` from 4.7.1 to 4.7.3. Updates `picocli` from 4.7.1 to 4.7.3 - [Release notes](https://github.com/remkop/picocli/releases) - [Changelog](https://github.com/remkop/picocli/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/remkop/picocli/compare/v4.7.1...v4.7.3) Updates `picocli-codegen` from 4.7.1 to 4.7.3 - [Release notes](https://github.com/remkop/picocli/releases) - [Changelog](https://github.com/remkop/picocli/blob/main/RELEASE-NOTES.md) - [Commits](https://github.com/remkop/picocli/compare/v4.7.1...v4.7.3) --- updated-dependencies: - dependency-name: info.picocli:picocli dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: info.picocli:picocli-codegen dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 40da198e2cf5fd..2dfa531d433ca4 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -192,7 +192,7 @@ 0.23.0 1.43.1 2.1 - 4.7.1 + 4.7.3 1.0.4 1.23.0 1.10.0 From 53a0c0b68b784426ddc732eb46585c7ca637375d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 08:19:19 +0000 Subject: [PATCH 195/333] Bump micrometer-bom from 1.10.5 to 1.10.6 Bumps [micrometer-bom](https://github.com/micrometer-metrics/micrometer) from 1.10.5 to 1.10.6. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.10.5...v1.10.6) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 40da198e2cf5fd..6db72f89788953 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -40,7 +40,7 @@ 1.25.0-alpha 1.8.1 5.0.2.Final - 1.10.5 + 1.10.6 2.1.12 0.22.0 20.1 From bf7fdcb183b7c579232c6bf2fee9cfa50a439c3f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 26 Apr 2023 13:37:01 +0200 Subject: [PATCH 196/333] Use Maven 3.9.1 in generated projects Now that we support it, let's make it the default for 3.1. --- build-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index adf6cd699d7240..159106a25d8c0c 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -63,7 +63,7 @@ [3.8.2,) - 3.8.8 + 3.9.1 3.2.0 8.1 ${project.version} From a258da8c8430a08ede0cfdfc9ca74befaa60b806 Mon Sep 17 00:00:00 2001 From: Michelle Purcell Date: Tue, 2 May 2023 09:50:38 +0100 Subject: [PATCH 197/333] Fix config for Vale linter GitHub Action on PR --- .github/workflows/vale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml index bca99e25cba0a7..a72f5421995f77 100644 --- a/.github/workflows/vale.yml +++ b/.github/workflows/vale.yml @@ -27,7 +27,7 @@ jobs: uses: errata-ai/vale-action@reviewdog with: fail_on_error: false - vale_flags: "--no-exit --config=docs/.vale/vale.ini" + vale_flags: "--no-exit --config=docs/.vale.ini" filter_mode: diff_context files: docs/src/main/asciidoc/ env: From 49c5da17885c131cc9db024aa1ce5e6ffe6a6e51 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Tue, 2 May 2023 01:03:20 +0200 Subject: [PATCH 198/333] Update Mockito to 5.3.1 --- bom/application/pom.xml | 2 +- extensions/amazon-lambda-http/runtime/pom.xml | 5 ----- extensions/azure-functions-http/runtime/pom.xml | 5 ----- extensions/google-cloud-functions-http/runtime/pom.xml | 5 ----- extensions/jaeger/deployment/pom.xml | 4 ++-- independent-projects/arc/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 8 files changed, 6 insertions(+), 21 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 40da198e2cf5fd..c3e7a8a8e9e43e 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -181,7 +181,7 @@ 2.1.SP2 5.4.Final 2.1.SP1 - 5.2.0 + 5.3.1 5.8.0 4.10.1 2.0.2.Final diff --git a/extensions/amazon-lambda-http/runtime/pom.xml b/extensions/amazon-lambda-http/runtime/pom.xml index c0e4e7edd81ea4..b9634486b6ea17 100644 --- a/extensions/amazon-lambda-http/runtime/pom.xml +++ b/extensions/amazon-lambda-http/runtime/pom.xml @@ -60,11 +60,6 @@ mockito-core test - - org.mockito - mockito-inline - test - diff --git a/extensions/azure-functions-http/runtime/pom.xml b/extensions/azure-functions-http/runtime/pom.xml index 7fd96d8e28293f..aac16f0ab69d83 100644 --- a/extensions/azure-functions-http/runtime/pom.xml +++ b/extensions/azure-functions-http/runtime/pom.xml @@ -42,11 +42,6 @@ mockito-core test - - org.mockito - mockito-inline - test - diff --git a/extensions/google-cloud-functions-http/runtime/pom.xml b/extensions/google-cloud-functions-http/runtime/pom.xml index ca04a865e950d0..19e4c6cf3461ea 100644 --- a/extensions/google-cloud-functions-http/runtime/pom.xml +++ b/extensions/google-cloud-functions-http/runtime/pom.xml @@ -47,11 +47,6 @@ mockito-core test - - org.mockito - mockito-inline - test - diff --git a/extensions/jaeger/deployment/pom.xml b/extensions/jaeger/deployment/pom.xml index 4b901e141b3515..5b8ad53ce4b328 100644 --- a/extensions/jaeger/deployment/pom.xml +++ b/extensions/jaeger/deployment/pom.xml @@ -42,10 +42,10 @@ org.mockito - mockito-inline + mockito-core test - + io.jaegertracing jaeger-zipkin test diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index d2c9db7788a23d..a0432406050f3d 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -55,7 +55,7 @@ 5.9.2 1.8.10 1.6.4 - 5.3.0 + 5.3.1 1.7.0.Alpha14 2.0.1 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index ae3f0bf8c8e35b..0318bb2a0bff80 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -353,7 +353,7 @@ org.mockito mockito-core - 5.3.0 + 5.3.1 test diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 003456dfc2dec4..d43acc50fec1e4 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -59,7 +59,7 @@ 1.23.0 3.5.0.Final 3.9.1 - 5.2.0 + 5.3.1 3.2.1 3.0.0 1.6.8 From 4e32f4a617cb50168a27fe0ac4c07d249266cef6 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 2 May 2023 12:12:21 +0200 Subject: [PATCH 199/333] Print valid api versions when specifying one in K8s Client Dev Services Plus, accept api-versions that use either the major version `v1` only or both the major and the minor versions `v1.22`. Fix https://github.com/quarkusio/quarkus/issues/33007 --- .../DevServicesKubernetesProcessor.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java index 8deaab773cc082..48e507f612678c 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java @@ -15,7 +15,7 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; -import java.util.stream.Stream; +import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.testcontainers.DockerClientFactory; @@ -199,17 +199,19 @@ private RunningDevService startKubernetes(DockerStatusBuildItem dockerStatusBuil switch (config.flavor) { case API_ONLY: container = new ApiServerContainer( - config.apiVersion.map(version -> findOrElseThrow(version, ApiServerContainerVersion.class)) + config.apiVersion + .map(version -> findOrElseThrow(config.flavor, version, ApiServerContainerVersion.class)) .orElseGet(() -> latest(ApiServerContainerVersion.class))); break; case K3S: container = new K3sContainer( - config.apiVersion.map(version -> findOrElseThrow(version, K3sContainerVersion.class)) + config.apiVersion.map(version -> findOrElseThrow(config.flavor, version, K3sContainerVersion.class)) .orElseGet(() -> latest(K3sContainerVersion.class))); break; case KIND: container = new KindContainer( - config.apiVersion.map(version -> findOrElseThrow(version, KindContainerVersion.class)) + config.apiVersion + .map(version -> findOrElseThrow(config.flavor, version, KindContainerVersion.class)) .orElseGet(() -> latest(KindContainerVersion.class))); break; default: @@ -241,12 +243,17 @@ private RunningDevService startKubernetes(DockerStatusBuildItem dockerStatusBuil .orElseGet(defaultKubernetesClusterSupplier); } - > T findOrElseThrow(final String version, final Class versions) { + > T findOrElseThrow(final Flavor flavor, final String version, final Class versions) { final String versionWithPrefix = !version.startsWith("v") ? "v" + version : version; - return Stream.of(versions.getEnumConstants()) - .filter(v -> v.descriptor().getKubernetesVersion().equalsIgnoreCase(versionWithPrefix)) + return KubernetesVersionEnum.ascending(versions) + .stream() + .filter(v -> v.descriptor().getKubernetesVersion().startsWith(versionWithPrefix)) .findFirst() - .orElseThrow(); + .orElseThrow(() -> new IllegalArgumentException( + String.format("Invalid API version '%s' for flavor '%s'. Options are: [%s]", versionWithPrefix, flavor, + KubernetesVersionEnum.ascending(versions).stream() + .map(v -> v.descriptor().getKubernetesVersion()) + .collect(Collectors.joining(", "))))); } private Map getKubernetesClientConfigFromKubeConfig(KubeConfig kubeConfig) { From 329ceba386951ab4cda9b61019b573c4d3a42420 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 2 May 2023 15:05:04 +0200 Subject: [PATCH 200/333] Update to Gradle Enterprise Maven Extension 1.17.1 This fixes an issue with the MP TCK runner. --- .mvn/{extensions.xml.disabled => extensions.xml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .mvn/{extensions.xml.disabled => extensions.xml} (91%) diff --git a/.mvn/extensions.xml.disabled b/.mvn/extensions.xml similarity index 91% rename from .mvn/extensions.xml.disabled rename to .mvn/extensions.xml index 769e62e63579e8..b6cffd97bcd2b5 100644 --- a/.mvn/extensions.xml.disabled +++ b/.mvn/extensions.xml @@ -2,7 +2,7 @@ com.gradle gradle-enterprise-maven-extension - 1.17 + 1.17.1 com.gradle From e63bd5632a0c7bf4bd52f43428ffded3a923cbee Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 2 May 2023 16:30:23 +0200 Subject: [PATCH 201/333] Bump Gradle to 8.1.1 --- build-parent/pom.xml | 2 +- devtools/gradle/gradle/wrapper/gradle-wrapper.properties | 4 ++-- independent-projects/bootstrap/pom.xml | 2 +- .../codestarts/quarkus/tooling/gradle-wrapper/codestart.yml | 2 +- .../devtools-testing/src/main/resources/fake-catalog.json | 2 +- independent-projects/tools/pom.xml | 2 +- .../gradle/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 2ceaf41edc5be6..7cd4777bfbebd1 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -65,7 +65,7 @@ 3.8.8 3.2.0 - 8.1 + 8.1.1 ${project.version} ${project.version} 3.8.1 diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties index d5e7b2ac5e4170..733c0c7955fdb6 100644 --- a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists # https://gradle.org/release-checksums/ -distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip +distributionSha256Sum=5625a0ae20fe000d9225d000b36909c7a0e0e8dda61c19b12da769add847c975 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index e1f3a0173fe29f..7203a74940ddae 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -77,7 +77,7 @@ 3.5.1 2.1.0 1.1.0 - 8.1 + 8.1.1 0.0.9 0.1.3 2.22.0 diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml index e67e0bfd333e54..b7d647cc2e521d 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml @@ -6,7 +6,7 @@ language: base: data: gradle: - version: 8.1 + version: 8.1.1 shared-data: buildtool: cli: ./gradlew diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index 97e6b6045cc25a..db1623d48a73cd 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -388,7 +388,7 @@ "supported-maven-versions": "[3.6.2,)", "proposed-maven-version": "3.8.8", "maven-wrapper-version": "3.2.0", - "gradle-wrapper-version": "8.1" + "gradle-wrapper-version": "8.1.1" } }, "codestarts-artifacts": [ diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 003456dfc2dec4..67a226a68d4fb5 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -42,7 +42,7 @@ 3.8.8 3.2.0 - 8.1 + 8.1.1 diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties index d5e7b2ac5e4170..733c0c7955fdb6 100644 --- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists # https://gradle.org/release-checksums/ -distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip +distributionSha256Sum=5625a0ae20fe000d9225d000b36909c7a0e0e8dda61c19b12da769add847c975 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 6ce558fcea38869662c8ba18743b96940549e502 Mon Sep 17 00:00:00 2001 From: polarctos Date: Thu, 27 Apr 2023 00:29:01 +0200 Subject: [PATCH 202/333] Fail on attempt to set custom hostname verifier Both custom HostnameVerifier and SSLContext are currently not supported by resteasy-reactive-client. This is documented as known limitations. This change aligns the implementation to the already present state for SSLContext. It makes the builder fail when HostnameVerifier is tried to be configured. The benefit is that it fails as early as currently possible and not only when the custom hostname verification would be needed later on. Switch both to UnsupportedOperationException. On the rest-client-reactive settings a custom hostname verifier is currently not supported. Adapted the tests accordingly as the builder fails early now throwing UnsupportedOperationException. --- .../client/reactive/ConfigurationTest.java | 3 --- .../reactive/GlobalConfigurationTest.java | 2 -- .../reactive/HelloClientWithBaseUri.java | 10 -------- .../configuration-test-application.properties | 4 ---- ...-configuration-test-application.properties | 1 - .../RestClientCDIDelegateBuilderTest.java | 23 ------------------- .../client/impl/ClientBuilderImpl.java | 6 ++--- 7 files changed, 3 insertions(+), 46 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java index ffc73d2756307f..91a59964f0a99a 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java @@ -78,9 +78,6 @@ private void verifyClientConfig(RestClientConfig clientConfig, boolean checkExtr assertThat(clientConfig.followRedirects.get()).isEqualTo(true); assertThat(clientConfig.queryParamStyle).isPresent(); assertThat(clientConfig.queryParamStyle.get()).isEqualTo(QueryParamStyle.COMMA_SEPARATED); - assertThat(clientConfig.hostnameVerifier).isPresent(); - assertThat(clientConfig.hostnameVerifier.get()) - .isEqualTo("io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyHostnameVerifier"); if (checkExtraProperties) { assertThat(clientConfig.connectionTTL).isPresent(); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/GlobalConfigurationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/GlobalConfigurationTest.java index 77609b1a1e5d80..b5cac7498d8df2 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/GlobalConfigurationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/GlobalConfigurationTest.java @@ -64,8 +64,6 @@ void checkGlobalConfigValues() { assertThat(configRoot.readTimeout).isEqualTo(2001); assertThat(configRoot.userAgent.get()).isEqualTo("agent"); assertThat(configRoot.headers).isEqualTo(Collections.singletonMap("foo", "bar")); - assertThat(configRoot.hostnameVerifier.get()) - .isEqualTo("io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyHostnameVerifier"); assertThat(configRoot.connectionTTL.get()).isEqualTo(20000); // value in ms, will be converted to seconds assertThat(configRoot.connectionPoolSize.get()).isEqualTo(2); assertThat(configRoot.keepAliveEnabled.get()).isTrue(); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java index aa317d2dd030ee..1e192e4575d314 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java @@ -1,8 +1,5 @@ package io.quarkus.rest.client.reactive; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; - import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -31,11 +28,4 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re } } - class MyHostnameVerifier implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - } - } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties index d240d98e8330d4..5d886106d9c306 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties @@ -2,7 +2,6 @@ io.quarkus.rest.client.reactive.HelloClientWithBaseUri/mp-rest/url=http://localhost:${quarkus.http.test-port:8081}/invalid-endpoint io.quarkus.rest.client.reactive.HelloClientWithBaseUri/mp-rest/scope=InvalidScope io.quarkus.rest.client.reactive.HelloClientWithBaseUri/mp-rest/providers=InvalidProvider -io.quarkus.rest.client.reactive.HelloClientWithBaseUri/mp-rest/hostnameVerifier=InvalidVerifier # client identified by class name quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".url=http://localhost:${quarkus.http.test-port:8081}/hello @@ -13,7 +12,6 @@ quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".rea quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".follow-redirects=true #quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".proxy-address=localhost:8080 quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".query-param-style=COMMA_SEPARATED -quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".hostname-verifier=io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyHostnameVerifier quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".connection-ttl=30000 quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".connection-pool-size=10 quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".keep-alive-enabled=false @@ -30,7 +28,6 @@ quarkus.rest-client.client-prefix.read-timeout=6000 quarkus.rest-client.client-prefix.follow-redirects=true quarkus.rest-client.client-prefix.proxy-address=localhost:8080 quarkus.rest-client.client-prefix.query-param-style=COMMA_SEPARATED -quarkus.rest-client.client-prefix.hostname-verifier=io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyHostnameVerifier quarkus.rest-client.client-prefix.connection-ttl=30000 quarkus.rest-client.client-prefix.connection-pool-size=10 quarkus.rest-client.client-prefix.keep-alive-enabled=false @@ -54,4 +51,3 @@ mp-client-prefix/mp-rest/readTimeout=6000 mp-client-prefix/mp-rest/followRedirects=true mp-client-prefix/mp-rest/proxyAddress=localhost:8080 mp-client-prefix/mp-rest/queryParamStyle=COMMA_SEPARATED -mp-client-prefix/mp-rest/hostnameVerifier=io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyHostnameVerifier diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/global-configuration-test-application.properties b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/global-configuration-test-application.properties index 473ed229a62595..1a1964e6f63d77 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/global-configuration-test-application.properties +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/global-configuration-test-application.properties @@ -18,7 +18,6 @@ quarkus.rest-client.connect-timeout=2000 quarkus.rest-client.read-timeout=2001 quarkus.rest-client.user-agent=agent quarkus.rest-client.headers.foo=bar -quarkus.rest-client.hostname-verifier=io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyHostnameVerifier quarkus.rest-client.connection-ttl=20000 quarkus.rest-client.connection-pool-size=2 quarkus.rest-client.keep-alive-enabled=true diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index b5a42ff8518379..7a60af0b2d8425 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -12,9 +12,6 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; - import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientResponseContext; import jakarta.ws.rs.client.ClientResponseFilter; @@ -102,7 +99,6 @@ public void testClientSpecificConfigs() { Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.USER_AGENT, "agent1"); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.STATIC_HEADERS, Collections.singletonMap("header1", "value")); - Mockito.verify(restClientBuilderMock).hostnameVerifier(Mockito.any(MyHostnameVerifier1.class)); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.CONNECTION_TTL, 10); // value converted to seconds Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.CONNECTION_POOL_SIZE, 103); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.KEEP_ALIVE_ENABLED, false); @@ -145,7 +141,6 @@ public void testGlobalConfigs() { Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.USER_AGENT, "agent2"); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.STATIC_HEADERS, Collections.singletonMap("header2", "value")); - Mockito.verify(restClientBuilderMock).hostnameVerifier(Mockito.any(MyHostnameVerifier2.class)); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.CONNECTION_TTL, 20); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.CONNECTION_POOL_SIZE, 203); Mockito.verify(restClientBuilderMock).property(QuarkusRestClientProperties.KEEP_ALIVE_ENABLED, true); @@ -174,8 +169,6 @@ private static RestClientsConfig createSampleConfigRoot() { configRoot.readTimeout = 201L; configRoot.userAgent = Optional.of("agent2"); configRoot.headers = Collections.singletonMap("header2", "value"); - configRoot.hostnameVerifier = Optional - .of("io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilderTest$MyHostnameVerifier2"); configRoot.connectionTTL = Optional.of(20000); // value in ms, will be converted to seconds configRoot.connectionPoolSize = Optional.of(203); configRoot.keepAliveEnabled = Optional.of(true); @@ -213,8 +206,6 @@ private static RestClientConfig createSampleClientConfig() { clientConfig.readTimeout = Optional.of(101L); clientConfig.userAgent = Optional.of("agent1"); clientConfig.headers = Collections.singletonMap("header1", "value"); - clientConfig.hostnameVerifier = Optional - .of("io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilderTest$MyHostnameVerifier1"); clientConfig.connectionTTL = Optional.of(10000); // value in milliseconds, will be converted to seconds clientConfig.connectionPoolSize = Optional.of(103); clientConfig.keepAliveEnabled = Optional.of(false); @@ -252,18 +243,4 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re } } - public static class MyHostnameVerifier1 implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - } - - public static class MyHostnameVerifier2 implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - } - } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index 6fd323050231ef..0a2afe54ed60fe 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -91,7 +91,7 @@ public ClientBuilder withConfig(Configuration config) { @Override public ClientBuilder sslContext(SSLContext sslContext) { // TODO - throw new RuntimeException("Specifying SSLContext is not supported at the moment"); + throw new UnsupportedOperationException("Specifying SSLContext is not supported at the moment"); } @Override @@ -114,8 +114,8 @@ public ClientBuilder trustStore(KeyStore trustStore, char[] password) { @Override public ClientBuilder hostnameVerifier(HostnameVerifier verifier) { - this.hostnameVerifier = verifier; - return this; + // TODO + throw new UnsupportedOperationException("Specifying HostnameVerifier is not supported at the moment"); } @Override From 18f6f4cad6a96d7b10ec63594b167467d48b36d8 Mon Sep 17 00:00:00 2001 From: polarctos Date: Thu, 27 Apr 2023 00:23:18 +0200 Subject: [PATCH 203/333] Enabling hostname verification by default Since introduction of the setting 'verifyHost' the hostname verification was disabled by default for the resteasy-reactive-client, as the default value for boolean (primitive) is false. This disabled default makes the reactive client vulnerable to MITM attacks. In the meantime setting the config explicitly is a workaround e.g. with 'quarkus.rest-client.verify-host=true'. This change now adds a proper default both in the configuration and for the field in the reactive client builder implementation. Add test case for enabled host verification default --- .../restclient/config/RestClientConfig.java | 3 ++- .../restclient/config/RestClientsConfig.java | 3 ++- .../reactive/client/impl/ClientBuilderImpl.java | 2 +- .../rest/client/main/ClientCallingResource.java | 13 +++++++++++++ .../main/wronghost/WrongHostRejectedClient.java | 16 ++++++++++++++++ .../src/main/resources/application.properties | 5 ++++- .../wronghost/ExternalWrongHostTestCase.java | 10 ++++++++++ 7 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostRejectedClient.java diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java index 1ac9d41c56ef77..daef9193a55a04 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java @@ -139,7 +139,8 @@ public class RestClientConfig { public Optional queryParamStyle; /** - * Set whether hostname verification is enabled. + * Set whether hostname verification is enabled. Default is enabled. + * This setting should not be disabled in production as it makes the client vulnerable to MITM attacks. */ @ConfigItem public Optional verifyHost; diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java index 0cc9e1eca802df..eed133d3ca4d0b 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java @@ -227,7 +227,8 @@ public class RestClientsConfig { public Optional queryParamStyle; /** - * Set whether hostname verification is enabled. + * Set whether hostname verification is enabled. Default is enabled. + * This setting should not be disabled in production as it makes the client vulnerable to MITM attacks. * * Can be overwritten by client-specific settings. */ diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index 0a2afe54ed60fe..2c60d09f2b7ae8 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -67,7 +67,7 @@ public class ClientBuilderImpl extends ClientBuilder { private boolean followRedirects; private boolean trustAll; - private boolean verifyHost; + private boolean verifyHost = true; private LoggingScope loggingScope; private Integer loggingBodySize = 100; diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 6c4f6498d007b7..3002647b66fc61 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -22,6 +22,7 @@ import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; import io.quarkus.it.rest.client.main.selfsigned.ExternalSelfSignedClient; import io.quarkus.it.rest.client.main.wronghost.WrongHostClient; +import io.quarkus.it.rest.client.main.wronghost.WrongHostRejectedClient; import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.smallrye.mutiny.Uni; import io.vertx.core.Future; @@ -52,6 +53,9 @@ public class ClientCallingResource { @RestClient WrongHostClient wrongHostClient; + @RestClient + WrongHostRejectedClient wrongHostRejectedClient; + @Inject InMemorySpanExporter inMemorySpanExporter; @@ -198,6 +202,15 @@ void init(@Observes Router router) { router.get("/wrong-host").blockingHandler( rc -> rc.response().setStatusCode(200).end(String.valueOf(wrongHostClient.invoke().getStatus()))); + + router.get("/wrong-host-rejected").blockingHandler(rc -> { + try { + int result = wrongHostRejectedClient.invoke().getStatus(); + rc.response().setStatusCode(200).end(String.valueOf(result)); + } catch (Exception e) { + rc.response().setStatusCode(500).end(e.getCause().getClass().getSimpleName()); + } + }); } private Future success(RoutingContext rc, String body) { diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostRejectedClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostRejectedClient.java new file mode 100644 index 00000000000000..3d2fc489c686d0 --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostRejectedClient.java @@ -0,0 +1,16 @@ +package io.quarkus.it.rest.client.main.wronghost; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "https://wrong.host.badssl.com/", configKey = "wrong-host-rejected") +public interface WrongHostRejectedClient { + + @GET + @Produces(MediaType.TEXT_PLAIN) + Response invoke(); +} diff --git a/integration-tests/rest-client-reactive/src/main/resources/application.properties b/integration-tests/rest-client-reactive/src/main/resources/application.properties index 4bf8d7c8f6404a..7258d0ec3f4f91 100644 --- a/integration-tests/rest-client-reactive/src/main/resources/application.properties +++ b/integration-tests/rest-client-reactive/src/main/resources/application.properties @@ -5,7 +5,10 @@ io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} # Self-Signed client quarkus.rest-client.self-signed.trust-store=${self-signed.trust-store} quarkus.rest-client.self-signed.trust-store-password=${self-signed.trust-store-password} -# Wrong Host client +# Wrong Host client (connection accepted, as host verification is turned off) quarkus.rest-client.wrong-host.trust-store=${wrong-host.trust-store} quarkus.rest-client.wrong-host.trust-store-password=${wrong-host.trust-store-password} quarkus.rest-client.wrong-host.verify-host=false +# Wrong Host client verified (connection rejected, as host verification is turned on by default) +quarkus.rest-client.wrong-host-rejected.trust-store=${wrong-host.trust-store} +quarkus.rest-client.wrong-host-rejected.trust-store-password=${wrong-host.trust-store-password} diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java index d963c850c0dce3..838cea76d07c33 100644 --- a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java @@ -1,6 +1,7 @@ package io.quarkus.it.rest.client.wronghost; import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import org.junit.jupiter.api.Test; @@ -17,4 +18,13 @@ public void restClient() { .statusCode(200) .body(is("200")); } + + @Test + public void restClientRejected() { + when() + .get("/wrong-host-rejected") + .then() + .statusCode(500) + .body(containsString("SSLHandshakeException")); + } } From 26deb7891c2d522651c9968281f680bf98f97b45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 22:03:42 +0000 Subject: [PATCH 204/333] Bump elasticsearch-opensource-components.version from 8.7.0 to 8.7.1 Bumps `elasticsearch-opensource-components.version` from 8.7.0 to 8.7.1. Updates `elasticsearch-rest-client` from 8.7.0 to 8.7.1 - [Release notes](https://github.com/elastic/elasticsearch/releases) - [Changelog](https://github.com/elastic/elasticsearch/blob/main/CHANGELOG.md) - [Commits](https://github.com/elastic/elasticsearch/compare/v8.7.0...v8.7.1) Updates `elasticsearch-java` from 8.7.0 to 8.7.1 - [Release notes](https://github.com/elastic/elasticsearch-java/releases) - [Changelog](https://github.com/elastic/elasticsearch-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/elastic/elasticsearch-java/compare/v8.7.0...v8.7.1) Updates `elasticsearch-rest-client-sniffer` from 8.7.0 to 8.7.1 - [Release notes](https://github.com/elastic/elasticsearch/releases) - [Changelog](https://github.com/elastic/elasticsearch/blob/main/CHANGELOG.md) - [Commits](https://github.com/elastic/elasticsearch/compare/v8.7.0...v8.7.1) --- updated-dependencies: - dependency-name: org.elasticsearch.client:elasticsearch-rest-client dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: co.elastic.clients:elasticsearch-java dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.elasticsearch.client:elasticsearch-rest-client-sniffer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 e5855e75416ae7..19932d6f8408ef 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -106,7 +106,7 @@ 6.0.1.Final 2.1 8.0.0.Final - 8.7.0 + 8.7.1 7.10.2 2.2.21 From a3178c1763e7a2b81d3328265db0a089a4ad4571 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 22:06:31 +0000 Subject: [PATCH 205/333] Bump jakarta.activation-api from 2.1.1 to 2.1.2 Bumps [jakarta.activation-api](https://github.com/jakartaee/jaf-api) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/jakartaee/jaf-api/releases) - [Commits](https://github.com/jakartaee/jaf-api/compare/2.1.1...2.1.2) --- updated-dependencies: - dependency-name: jakarta.activation:jakarta.activation-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 e5855e75416ae7..7be5ba9edbb4d7 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -70,7 +70,7 @@ 3.3.0 4.5.0 2.1.0 - 2.1.1 + 2.1.2 2.1.1 4.0.1 2.0.1 From d58121b1fbaf0dce2358b03ef09140a7f7ca60f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 22:13:05 +0000 Subject: [PATCH 206/333] Bump jacoco.version from 0.8.9 to 0.8.10 Bumps `jacoco.version` from 0.8.9 to 0.8.10. Updates `org.jacoco.core` from 0.8.9 to 0.8.10 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.9...v0.8.10) Updates `org.jacoco.report` from 0.8.9 to 0.8.10 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.9...v0.8.10) Updates `org.jacoco.agent` from 0.8.9 to 0.8.10 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.9...v0.8.10) Updates `org.jacoco.agent` from 0.8.9 to 0.8.10 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.9...v0.8.10) Updates `jacoco-maven-plugin` from 0.8.9 to 0.8.10 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.9...v0.8.10) --- updated-dependencies: - dependency-name: org.jacoco:org.jacoco.core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jacoco:org.jacoco.report dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jacoco:org.jacoco.agent dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jacoco:org.jacoco.agent:runtime dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jacoco:jacoco-maven-plugin 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 7d84fb426b9699..507adef1011aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ false - 0.8.9 + 0.8.10 6.5.1 From ec29707acb15fae817123e75673047e1d079cb0d Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 2 May 2023 19:49:36 -0300 Subject: [PATCH 207/333] Revert "Cleanup Flyway Config" This reverts commit 6cf647c7cbd3739d1309acfac9073c419669a3a5. --- .../flyway/FlywayCallbacksLocator.java | 2 +- .../io/quarkus/flyway/FlywayProcessor.java | 2 +- extensions/flyway/runtime/pom.xml | 5 - .../flyway/runtime/FlywayBuildTimeConfig.java | 29 ++--- .../runtime/FlywayContainerProducer.java | 4 +- .../quarkus/flyway/runtime/FlywayCreator.java | 84 ++++++------ .../FlywayDataSourceBuildTimeConfig.java | 29 +++-- .../FlywayDataSourceRuntimeConfig.java | 122 +++++++++++------- .../flyway/runtime/FlywayRecorder.java | 2 +- .../flyway/runtime/FlywayRuntimeConfig.java | 32 ++--- .../devconsole/FlywayDevConsoleRecorder.java | 4 +- .../flyway/runtime/FlywayCreatorTest.java | 105 ++++++--------- 12 files changed, 214 insertions(+), 206 deletions(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java index b4e0f4fbc9cd5b..8d90513e8de457 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayCallbacksLocator.java @@ -76,7 +76,7 @@ public Map> getCallbacks() */ private Collection callbacksForDataSource(String dataSourceName) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException { - final Optional> callbackConfig = flywayBuildConfig.getConfigForDataSourceName(dataSourceName).callbacks(); + final Optional> callbackConfig = flywayBuildConfig.getConfigForDataSourceName(dataSourceName).callbacks; if (!callbackConfig.isPresent()) { return Collections.emptyList(); } diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index b107b878e2b199..ce3fa9db00e214 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -99,7 +99,7 @@ MigrationStateBuildItem build(BuildProducer featureProducer, Map> applicationMigrationsToDs = new HashMap<>(); for (var i : dataSourceNames) { Collection migrationLocations = discoverApplicationMigrations( - flywayBuildConfig.getConfigForDataSourceName(i).locations()); + flywayBuildConfig.getConfigForDataSourceName(i).locations); applicationMigrationsToDs.put(i, migrationLocations); } Set datasourcesWithMigrations = new HashSet<>(); diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml index e49a44b3d13a71..c09e75f82456d3 100644 --- a/extensions/flyway/runtime/pom.xml +++ b/extensions/flyway/runtime/pom.xml @@ -55,11 +55,6 @@ quarkus-junit5-internal test - - io.quarkus - quarkus-junit5-mockito - test - diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java index 58ae166ac6531d..47e7a4878c7f4e 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java @@ -1,40 +1,35 @@ package io.quarkus.flyway.runtime; +import java.util.Collections; import java.util.Map; import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -import io.smallrye.config.ConfigMapping; -import io.smallrye.config.WithParentName; -@ConfigMapping(prefix = "quarkus.flyway") -@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) -public interface FlywayBuildTimeConfig { +@ConfigRoot(name = "flyway", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public final class FlywayBuildTimeConfig { /** * Gets the {@link FlywayDataSourceBuildTimeConfig} for the given datasource name. */ - default FlywayDataSourceBuildTimeConfig getConfigForDataSourceName(String dataSourceName) { + public FlywayDataSourceBuildTimeConfig getConfigForDataSourceName(String dataSourceName) { if (DataSourceUtil.isDefault(dataSourceName)) { - return defaultDataSource(); + return defaultDataSource; } - FlywayDataSourceBuildTimeConfig config = namedDataSources().get(dataSourceName); - if (config == null) { - config = defaultDataSource(); - } - return config; + return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceBuildTimeConfig.defaultConfig()); } /** * Flyway configuration for the default datasource. */ - @WithParentName - FlywayDataSourceBuildTimeConfig defaultDataSource(); + @ConfigItem(name = ConfigItem.PARENT) + public FlywayDataSourceBuildTimeConfig defaultDataSource; /** * Flyway configurations for named datasources. */ - @WithParentName - Map namedDataSources(); -} + @ConfigItem(name = ConfigItem.PARENT) + public Map namedDataSources = Collections.emptyMap(); +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java index 9149f5f4b22a7d..d58bb692839ed9 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerProducer.java @@ -51,8 +51,8 @@ public FlywayContainer createFlyway(DataSource dataSource, String dataSourceName final Flyway flyway = new FlywayCreator(matchingRuntimeConfig, matchingBuildTimeConfig, matchingConfigCustomizers( configCustomizerInstances, dataSourceName)).withCallbacks(callbacks) .createFlyway(dataSource); - return new FlywayContainer(flyway, matchingRuntimeConfig.cleanAtStart(), matchingRuntimeConfig.migrateAtStart(), - matchingRuntimeConfig.repairAtStart(), matchingRuntimeConfig.validateAtStart(), + return new FlywayContainer(flyway, matchingRuntimeConfig.cleanAtStart, matchingRuntimeConfig.migrateAtStart, + matchingRuntimeConfig.repairAtStart, matchingRuntimeConfig.validateAtStart, dataSourceName, hasMigrations, createPossible); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java index 863c4e895a4459..844be7c8fb053e 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java @@ -49,63 +49,63 @@ public FlywayCreator withCallbacks(Collection callbacks) { public Flyway createFlyway(DataSource dataSource) { FluentConfiguration configure = Flyway.configure(); - if (flywayRuntimeConfig.jdbcUrl().isPresent()) { - if (flywayRuntimeConfig.username().isPresent() && flywayRuntimeConfig.password().isPresent()) { - configure.dataSource(flywayRuntimeConfig.jdbcUrl().get(), flywayRuntimeConfig.username().get(), - flywayRuntimeConfig.password().get()); + if (flywayRuntimeConfig.jdbcUrl.isPresent()) { + if (flywayRuntimeConfig.username.isPresent() && flywayRuntimeConfig.password.isPresent()) { + configure.dataSource(flywayRuntimeConfig.jdbcUrl.get(), flywayRuntimeConfig.username.get(), + flywayRuntimeConfig.password.get()); } else { throw new ConfigurationException( "Username and password must be defined when a JDBC URL is provided in the Flyway configuration"); } } else { - if (flywayRuntimeConfig.username().isPresent() && flywayRuntimeConfig.password().isPresent()) { + if (flywayRuntimeConfig.username.isPresent() && flywayRuntimeConfig.password.isPresent()) { AgroalDataSource agroalDataSource = (AgroalDataSource) dataSource; String jdbcUrl = agroalDataSource.getConfiguration().connectionPoolConfiguration() .connectionFactoryConfiguration().jdbcUrl(); - configure.dataSource(jdbcUrl, flywayRuntimeConfig.username().get(), - flywayRuntimeConfig.password().get()); + configure.dataSource(jdbcUrl, flywayRuntimeConfig.username.get(), + flywayRuntimeConfig.password.get()); } else { configure.dataSource(dataSource); } } - if (flywayRuntimeConfig.initSql().isPresent()) { - configure.initSql(flywayRuntimeConfig.initSql().get()); + if (flywayRuntimeConfig.initSql.isPresent()) { + configure.initSql(flywayRuntimeConfig.initSql.get()); } - if (flywayRuntimeConfig.connectRetries().isPresent()) { - configure.connectRetries(flywayRuntimeConfig.connectRetries().getAsInt()); + if (flywayRuntimeConfig.connectRetries.isPresent()) { + configure.connectRetries(flywayRuntimeConfig.connectRetries.getAsInt()); } - if (flywayRuntimeConfig.defaultSchema().isPresent()) { - configure.defaultSchema(flywayRuntimeConfig.defaultSchema().get()); + if (flywayRuntimeConfig.defaultSchema.isPresent()) { + configure.defaultSchema(flywayRuntimeConfig.defaultSchema.get()); } - if (flywayRuntimeConfig.schemas().isPresent()) { - configure.schemas(flywayRuntimeConfig.schemas().get().toArray(EMPTY_ARRAY)); + if (flywayRuntimeConfig.schemas.isPresent()) { + configure.schemas(flywayRuntimeConfig.schemas.get().toArray(EMPTY_ARRAY)); } - if (flywayRuntimeConfig.table().isPresent()) { - configure.table(flywayRuntimeConfig.table().get()); + if (flywayRuntimeConfig.table.isPresent()) { + configure.table(flywayRuntimeConfig.table.get()); } - configure.locations(flywayBuildTimeConfig.locations().toArray(EMPTY_ARRAY)); - if (flywayRuntimeConfig.sqlMigrationPrefix().isPresent()) { - configure.sqlMigrationPrefix(flywayRuntimeConfig.sqlMigrationPrefix().get()); + configure.locations(flywayBuildTimeConfig.locations.toArray(EMPTY_ARRAY)); + if (flywayRuntimeConfig.sqlMigrationPrefix.isPresent()) { + configure.sqlMigrationPrefix(flywayRuntimeConfig.sqlMigrationPrefix.get()); } - if (flywayRuntimeConfig.repeatableSqlMigrationPrefix().isPresent()) { - configure.repeatableSqlMigrationPrefix(flywayRuntimeConfig.repeatableSqlMigrationPrefix().get()); + if (flywayRuntimeConfig.repeatableSqlMigrationPrefix.isPresent()) { + configure.repeatableSqlMigrationPrefix(flywayRuntimeConfig.repeatableSqlMigrationPrefix.get()); } - configure.cleanDisabled(flywayRuntimeConfig.cleanDisabled()); - configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate()); - configure.validateOnMigrate(flywayRuntimeConfig.validateOnMigrate()); - configure.validateMigrationNaming(flywayRuntimeConfig.validateMigrationNaming()); + configure.cleanDisabled(flywayRuntimeConfig.cleanDisabled); + configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate); + configure.validateOnMigrate(flywayRuntimeConfig.validateOnMigrate); + configure.validateMigrationNaming(flywayRuntimeConfig.validateMigrationNaming); final String[] ignoreMigrationPatterns; - if (flywayRuntimeConfig.ignoreMigrationPatterns().isPresent()) { - ignoreMigrationPatterns = flywayRuntimeConfig.ignoreMigrationPatterns().get(); + if (flywayRuntimeConfig.ignoreMigrationPatterns.isPresent()) { + ignoreMigrationPatterns = flywayRuntimeConfig.ignoreMigrationPatterns.get(); } else { List patterns = new ArrayList<>(2); - if (flywayRuntimeConfig.ignoreMissingMigrations()) { + if (flywayRuntimeConfig.ignoreMissingMigrations) { patterns.add("*:Missing"); } - if (flywayRuntimeConfig.ignoreFutureMigrations()) { + if (flywayRuntimeConfig.ignoreFutureMigrations) { patterns.add("*:Future"); } // Default is *:Future @@ -113,21 +113,21 @@ public Flyway createFlyway(DataSource dataSource) { } configure.ignoreMigrationPatterns(ignoreMigrationPatterns); - configure.cleanOnValidationError(flywayRuntimeConfig.cleanOnValidationError()); - configure.outOfOrder(flywayRuntimeConfig.outOfOrder()); - if (flywayRuntimeConfig.baselineVersion().isPresent()) { - configure.baselineVersion(flywayRuntimeConfig.baselineVersion().get()); + configure.cleanOnValidationError(flywayRuntimeConfig.cleanOnValidationError); + configure.outOfOrder(flywayRuntimeConfig.outOfOrder); + if (flywayRuntimeConfig.baselineVersion.isPresent()) { + configure.baselineVersion(flywayRuntimeConfig.baselineVersion.get()); } - if (flywayRuntimeConfig.baselineDescription().isPresent()) { - configure.baselineDescription(flywayRuntimeConfig.baselineDescription().get()); + if (flywayRuntimeConfig.baselineDescription.isPresent()) { + configure.baselineDescription(flywayRuntimeConfig.baselineDescription.get()); } - configure.placeholders(flywayRuntimeConfig.placeholders()); - configure.createSchemas(flywayRuntimeConfig.createSchemas()); - if (flywayRuntimeConfig.placeholderPrefix().isPresent()) { - configure.placeholderPrefix(flywayRuntimeConfig.placeholderPrefix().get()); + configure.placeholders(flywayRuntimeConfig.placeholders); + configure.createSchemas(flywayRuntimeConfig.createSchemas); + if (flywayRuntimeConfig.placeholderPrefix.isPresent()) { + configure.placeholderPrefix(flywayRuntimeConfig.placeholderPrefix.get()); } - if (flywayRuntimeConfig.placeholderSuffix().isPresent()) { - configure.placeholderSuffix(flywayRuntimeConfig.placeholderSuffix().get()); + if (flywayRuntimeConfig.placeholderSuffix.isPresent()) { + configure.placeholderSuffix(flywayRuntimeConfig.placeholderSuffix.get()); } if (!callbacks.isEmpty()) { configure.callbacks(callbacks.toArray(new Callback[0])); diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java index fa6382cf513aaf..7668f4ca191ea3 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java @@ -1,17 +1,18 @@ package io.quarkus.flyway.runtime; +import java.util.Collections; import java.util.List; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConvertWith; import io.quarkus.runtime.configuration.TrimmedStringConverter; -import io.smallrye.config.WithConverter; -import io.smallrye.config.WithDefault; @ConfigGroup -public interface FlywayDataSourceBuildTimeConfig { +public final class FlywayDataSourceBuildTimeConfig { - String DEFAULT_LOCATION = "db/migration"; + private static final String DEFAULT_LOCATION = "db/migration"; /** * Comma-separated list of locations to scan recursively for migrations. The location type is determined by its prefix. @@ -22,9 +23,9 @@ public interface FlywayDataSourceBuildTimeConfig { * Locations starting with filesystem: point to a directory on the filesystem, may only contain SQL migrations and are only * scanned recursively down non-hidden directories. */ - @WithDefault(DEFAULT_LOCATION) - @WithConverter(TrimmedStringConverter.class) - List locations(); + @ConfigItem(defaultValue = DEFAULT_LOCATION) + @ConvertWith(TrimmedStringConverter.class) + public List locations; /** * Comma-separated list of fully qualified class names of Callback implementations @@ -32,5 +33,17 @@ public interface FlywayDataSourceBuildTimeConfig { * The {@link org.flywaydb.core.api.callback.Callback} subclass must have a no-args constructor and must not be abstract. * These classes must also not have any fields that hold state (unless that state is initialized in the constructor). */ - Optional> callbacks(); + @ConfigItem + public Optional> callbacks = Optional.empty(); + + /** + * Creates a {@link FlywayDataSourceBuildTimeConfig} with default settings. + * + * @return {@link FlywayDataSourceBuildTimeConfig} + */ + public static FlywayDataSourceBuildTimeConfig defaultConfig() { + FlywayDataSourceBuildTimeConfig defaultConfig = new FlywayDataSourceBuildTimeConfig(); + defaultConfig.locations = Collections.singletonList(DEFAULT_LOCATION); + return defaultConfig; + } } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java index 625a13025ca347..5f63532ea1e2ec 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java @@ -1,21 +1,32 @@ package io.quarkus.flyway.runtime; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import io.quarkus.runtime.annotations.ConfigGroup; -import io.smallrye.config.WithDefault; +import io.quarkus.runtime.annotations.ConfigItem; @ConfigGroup -public interface FlywayDataSourceRuntimeConfig { +public final class FlywayDataSourceRuntimeConfig { + + /** + * Creates a {@link FlywayDataSourceRuntimeConfig} with default settings. + * + * @return {@link FlywayDataSourceRuntimeConfig} + */ + public static FlywayDataSourceRuntimeConfig defaultConfig() { + return new FlywayDataSourceRuntimeConfig(); + } /** * The maximum number of retries when attempting to connect to the database. After each failed attempt, Flyway will wait 1 * second before attempting to connect again, up to the maximum number of times specified by connectRetries. */ - OptionalInt connectRetries(); + @ConfigItem + public OptionalInt connectRetries = OptionalInt.empty(); /** * Sets the default schema managed by Flyway. This schema name is case-sensitive. If not specified, but schemas @@ -29,32 +40,37 @@ public interface FlywayDataSourceRuntimeConfig { *

  • This schema will be the default for the database connection (provided the database supports this concept).
  • * */ - Optional defaultSchema(); + @ConfigItem + public Optional defaultSchema = Optional.empty(); /** * The JDBC URL that Flyway uses to connect to the database. * Falls back to the datasource URL if not specified. */ - Optional jdbcUrl(); + @ConfigItem + public Optional jdbcUrl = Optional.empty(); /** * The username that Flyway uses to connect to the database. * If no specific JDBC URL is configured, falls back to the datasource username if not specified. */ - Optional username(); + @ConfigItem + public Optional username = Optional.empty(); /** * The password that Flyway uses to connect to the database. * If no specific JDBC URL is configured, falls back to the datasource password if not specified. */ - Optional password(); + @ConfigItem + public Optional password = Optional.empty(); /** * Comma-separated case-sensitive list of schemas managed by Flyway. * The first schema in the list will be automatically set as the default one during the migration. * It will also be the one containing the schema history table. */ - Optional> schemas(); + @ConfigItem + public Optional> schemas = Optional.empty(); /** * The name of Flyway's schema history table. @@ -63,136 +79,149 @@ public interface FlywayDataSourceRuntimeConfig { * When the flyway.schemas property is set (multi-schema mode), the schema history table is placed in the first schema of * the list. */ - Optional table(); + @ConfigItem + public Optional table = Optional.empty(); /** * The file name prefix for versioned SQL migrations. - *

    + * * Versioned SQL migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix , which using * the defaults translates to V1.1__My_description.sql */ - Optional sqlMigrationPrefix(); + @ConfigItem + public Optional sqlMigrationPrefix = Optional.empty(); /** * The file name prefix for repeatable SQL migrations. - *

    + * * Repeatable SQL migrations have the following file name structure: prefixSeparatorDESCRIPTIONsuffix , which using the * defaults translates to R__My_description.sql */ - Optional repeatableSqlMigrationPrefix(); + @ConfigItem + public Optional repeatableSqlMigrationPrefix = Optional.empty(); /** * true to execute Flyway clean command automatically when the application starts, false otherwise. + * */ - @WithDefault("false") - boolean cleanAtStart(); + @ConfigItem + public boolean cleanAtStart; /** * true to prevent Flyway clean operations, false otherwise. */ - @WithDefault("false") - boolean cleanDisabled(); + @ConfigItem + public boolean cleanDisabled; /** * true to automatically call clean when a validation error occurs, false otherwise. */ - @WithDefault("false") - boolean cleanOnValidationError(); + @ConfigItem + public boolean cleanOnValidationError; /** * true to execute Flyway automatically when the application starts, false otherwise. + * */ - @WithDefault("false") - boolean migrateAtStart(); + @ConfigItem + public boolean migrateAtStart; /** * true to execute a Flyway repair command when the application starts, false otherwise. + * */ - @WithDefault("false") - boolean repairAtStart(); + @ConfigItem + public boolean repairAtStart; /** * true to execute a Flyway validate command when the application starts, false otherwise. + * */ - @WithDefault("false") - boolean validateAtStart(); + @ConfigItem + public boolean validateAtStart; /** * Enable the creation of the history table if it does not exist already. */ - @WithDefault("false") - boolean baselineOnMigrate(); + @ConfigItem + public boolean baselineOnMigrate; /** * The initial baseline version. */ - Optional baselineVersion(); + @ConfigItem + public Optional baselineVersion = Optional.empty(); /** * The description to tag an existing schema with when executing baseline. */ - Optional baselineDescription(); + @ConfigItem + public Optional baselineDescription = Optional.empty(); /** * Whether to automatically call validate when performing a migration. */ - @WithDefault("true") - boolean validateOnMigrate(); + @ConfigItem(defaultValue = "true") + public boolean validateOnMigrate = true; /** * Allows migrations to be run "out of order". */ - @WithDefault("false") - boolean outOfOrder(); + @ConfigItem + public boolean outOfOrder; /** * Ignore missing migrations when reading the history table. When set to true migrations from older versions present in the * history table but absent in the configured locations will be ignored (and logged as a warning), when false (the default) * the validation step will fail. */ - @WithDefault("false") - boolean ignoreMissingMigrations(); + @ConfigItem + public boolean ignoreMissingMigrations; /** * Ignore future migrations when reading the history table. When set to true migrations from newer versions present in the * history table but absent in the configured locations will be ignored (and logged as a warning), when false (the default) * the validation step will fail. */ - @WithDefault("false") - boolean ignoreFutureMigrations(); + @ConfigItem + public boolean ignoreFutureMigrations; /** * Sets the placeholders to replace in SQL migration scripts. */ - Map placeholders(); + @ConfigItem + public Map placeholders = Collections.emptyMap(); /** * Whether Flyway should attempt to create the schemas specified in the schemas property */ - @WithDefault("true") - boolean createSchemas(); + @ConfigItem(defaultValue = "true") + public boolean createSchemas; /** * Prefix of every placeholder (default: ${ ) */ - Optional placeholderPrefix(); + @ConfigItem + public Optional placeholderPrefix = Optional.empty(); /** * Suffix of every placeholder (default: } ) */ - Optional placeholderSuffix(); + @ConfigItem + public Optional placeholderSuffix = Optional.empty(); /** * The SQL statements to run to initialize a new database connection immediately after opening it. */ - Optional initSql(); + @ConfigItem + public Optional initSql = Optional.empty(); /** * Whether to validate migrations and callbacks whose scripts do not obey the correct naming convention. A failure can be * useful to check that errors such as case sensitivity in migration prefixes have been corrected. */ - @WithDefault("false") - boolean validateMigrationNaming(); + @ConfigItem + public boolean validateMigrationNaming; /** * Ignore migrations during validate and repair according to a given list of patterns (see @@ -200,5 +229,6 @@ public interface FlywayDataSourceRuntimeConfig { * When this configuration is set, the ignoreFutureMigrations and ignoreMissingMigrations settings are ignored. Patterns are * comma separated. */ - Optional ignoreMigrationPatterns(); + @ConfigItem + public Optional ignoreMigrationPatterns = Optional.empty(); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 36ded39d7b507d..94c7919fe46159 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -76,7 +76,7 @@ public Flyway get() { } public void doStartActions() { - if (!config.getValue().enabled()) { + if (!config.getValue().enabled) { return; } for (FlywayContainer flywayContainer : FLYWAY_CONTAINERS) { diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java index 13efcd42e082a3..481ee6e8215392 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -1,48 +1,42 @@ package io.quarkus.flyway.runtime; +import java.util.Collections; import java.util.Map; import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -import io.smallrye.config.ConfigMapping; -import io.smallrye.config.WithDefault; -import io.smallrye.config.WithParentName; -@ConfigMapping(prefix = "quarkus.flyway") -@ConfigRoot(phase = ConfigPhase.RUN_TIME) -public interface FlywayRuntimeConfig { +@ConfigRoot(name = "flyway", phase = ConfigPhase.RUN_TIME) +public final class FlywayRuntimeConfig { /** * Gets the {@link FlywayDataSourceRuntimeConfig} for the given datasource name. */ - default FlywayDataSourceRuntimeConfig getConfigForDataSourceName(String dataSourceName) { + public FlywayDataSourceRuntimeConfig getConfigForDataSourceName(String dataSourceName) { if (DataSourceUtil.isDefault(dataSourceName)) { - return defaultDataSource(); + return defaultDataSource; } - FlywayDataSourceRuntimeConfig config = namedDataSources().get(dataSourceName); - if (config == null) { - config = defaultDataSource(); - } - return config; + return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceRuntimeConfig.defaultConfig()); } /** * Flag to enable / disable Flyway. * */ - @WithDefault("true") - boolean enabled(); + @ConfigItem(defaultValue = "true") + public boolean enabled; /** * Flyway configuration for the default datasource. */ - @WithParentName - FlywayDataSourceRuntimeConfig defaultDataSource(); + @ConfigItem(name = ConfigItem.PARENT) + public FlywayDataSourceRuntimeConfig defaultDataSource = FlywayDataSourceRuntimeConfig.defaultConfig(); /** * Flyway configurations for named datasources. */ - @WithParentName - Map namedDataSources(); + @ConfigItem(name = ConfigItem.PARENT) + public Map namedDataSources = Collections.emptyMap(); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java index fff138b184cd8b..ef51023a14c121 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/devconsole/FlywayDevConsoleRecorder.java @@ -76,7 +76,7 @@ protected void handlePostAsync(RoutingContext event, MultiMap form) throws Excep return; } FlywayDataSourceBuildTimeConfig config = buildTimeConfig.getConfigForDataSourceName(name); - if (config.locations().isEmpty()) { + if (config.locations.isEmpty()) { flashMessage(event, "Datasource has no locations configured"); return; } @@ -90,7 +90,7 @@ protected void handlePostAsync(RoutingContext event, MultiMap form) throws Excep // In the current project only Path path = resourcesDir.get(0); - Path migrationDir = path.resolve(config.locations().get(0)); + Path migrationDir = path.resolve(config.locations.get(0)); Files.createDirectories(migrationDir); Path file = migrationDir.resolve( "V1.0.0__" + artifactId + ".sql"); diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java index 48e818499b4726..17764887791ed8 100644 --- a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java @@ -18,41 +18,23 @@ import org.flywaydb.core.api.configuration.Configuration; import org.flywaydb.core.api.pattern.ValidatePattern; import org.flywaydb.core.internal.util.ValidatePatternUtils; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; - -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; class FlywayCreatorTest { - private FlywayDataSourceRuntimeConfig runtimeConfig; - private FlywayDataSourceBuildTimeConfig buildConfig; - private final Configuration defaultConfig = Flyway.configure().load().getConfiguration(); + private FlywayDataSourceRuntimeConfig runtimeConfig = FlywayDataSourceRuntimeConfig.defaultConfig(); + private FlywayDataSourceBuildTimeConfig buildConfig = FlywayDataSourceBuildTimeConfig.defaultConfig(); + private Configuration defaultConfig = Flyway.configure().load().getConfiguration(); /** * class under test. */ private FlywayCreator creator; - @BeforeEach - void mockConfig() { - SmallRyeConfig config = new SmallRyeConfigBuilder().addDiscoveredSources().addDefaultSources().addDiscoveredConverters() - .withMapping(FlywayRuntimeConfig.class, "quarkus.flyway") - .withMapping(FlywayBuildTimeConfig.class, "quarkus.flyway") - .build(); - - FlywayRuntimeConfig flywayRuntimeConfig = config.getConfigMapping(FlywayRuntimeConfig.class); - FlywayBuildTimeConfig flywayBuildTimeConfig = config.getConfigMapping(FlywayBuildTimeConfig.class); - this.runtimeConfig = Mockito.spy(flywayRuntimeConfig.defaultDataSource()); - this.buildConfig = Mockito.spy(flywayBuildTimeConfig.defaultDataSource()); - } - @Test @DisplayName("locations default matches flyway default") void testLocationsDefault() { @@ -63,9 +45,9 @@ void testLocationsDefault() { @Test @DisplayName("locations carried over from configuration") void testLocationsOverridden() { - Mockito.when(buildConfig.locations()).thenReturn(Arrays.asList("db/migrations", "db/something")); + buildConfig.locations = Arrays.asList("db/migrations", "db/something"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(buildConfig.locations(), pathList(createdFlywayConfig().getLocations())); + assertEquals(buildConfig.locations, pathList(createdFlywayConfig().getLocations())); } @Test @@ -85,9 +67,9 @@ void testBaselineDescriptionDefault() { @Test @DisplayName("baseline description carried over from configuration") void testBaselineDescriptionOverridden() { - Mockito.when(runtimeConfig.baselineDescription()).thenReturn(Optional.of("baselineDescription")); + runtimeConfig.baselineDescription = Optional.of("baselineDescription"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.baselineDescription().get(), createdFlywayConfig().getBaselineDescription()); + assertEquals(runtimeConfig.baselineDescription.get(), createdFlywayConfig().getBaselineDescription()); } @Test @@ -100,9 +82,9 @@ void testBaselineVersionDefault() { @Test @DisplayName("baseline version carried over from configuration") void testBaselineVersionOverridden() { - Mockito.when(runtimeConfig.baselineVersion()).thenReturn(Optional.of("0.1.2")); + runtimeConfig.baselineVersion = Optional.of("0.1.2"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.baselineVersion().get(), createdFlywayConfig().getBaselineVersion().getVersion()); + assertEquals(runtimeConfig.baselineVersion.get(), createdFlywayConfig().getBaselineVersion().getVersion()); } @Test @@ -115,9 +97,9 @@ void testConnectionRetriesDefault() { @Test @DisplayName("connection retries carried over from configuration") void testConnectionRetriesOverridden() { - Mockito.when(runtimeConfig.connectRetries()).thenReturn(OptionalInt.of(12)); + runtimeConfig.connectRetries = OptionalInt.of(12); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.connectRetries().getAsInt(), createdFlywayConfig().getConnectRetries()); + assertEquals(runtimeConfig.connectRetries.getAsInt(), createdFlywayConfig().getConnectRetries()); } @Test @@ -130,10 +112,9 @@ void testRepeatableSqlMigrationPrefixDefault() { @Test @DisplayName("repeatable SQL migration prefix carried over from configuration") void testRepeatableSqlMigrationPrefixOverridden() { - Mockito.when(runtimeConfig.repeatableSqlMigrationPrefix()).thenReturn(Optional.of("A")); + runtimeConfig.repeatableSqlMigrationPrefix = Optional.of("A"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.repeatableSqlMigrationPrefix().get(), - createdFlywayConfig().getRepeatableSqlMigrationPrefix()); + assertEquals(runtimeConfig.repeatableSqlMigrationPrefix.get(), createdFlywayConfig().getRepeatableSqlMigrationPrefix()); } @Test @@ -146,9 +127,9 @@ void testSchemasDefault() { @Test @DisplayName("schemas carried over from configuration") void testSchemasOverridden() { - Mockito.when(runtimeConfig.schemas()).thenReturn(Optional.of(asList("TEST_SCHEMA_1", "TEST_SCHEMA_2"))); + runtimeConfig.schemas = Optional.of(Arrays.asList("TEST_SCHEMA_1", "TEST_SCHEMA_2")); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.schemas().get(), asList(createdFlywayConfig().getSchemas())); + assertEquals(runtimeConfig.schemas.get(), asList(createdFlywayConfig().getSchemas())); } @Test @@ -161,9 +142,9 @@ void testSqlMigrationPrefixDefault() { @Test @DisplayName("SQL migration prefix carried over from configuration") void testSqlMigrationPrefixOverridden() { - Mockito.when(runtimeConfig.sqlMigrationPrefix()).thenReturn(Optional.of("A")); + runtimeConfig.sqlMigrationPrefix = Optional.of("M"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.sqlMigrationPrefix().get(), createdFlywayConfig().getSqlMigrationPrefix()); + assertEquals(runtimeConfig.sqlMigrationPrefix.get(), createdFlywayConfig().getSqlMigrationPrefix()); } @Test @@ -176,31 +157,31 @@ void testTableDefault() { @Test @DisplayName("table carried over from configuration") void testTableOverridden() { - Mockito.when(runtimeConfig.table()).thenReturn(Optional.of("flyway_history_test_table")); + runtimeConfig.table = Optional.of("flyway_history_test_table"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.table().get(), createdFlywayConfig().getTable()); + assertEquals(runtimeConfig.table.get(), createdFlywayConfig().getTable()); } @Test @DisplayName("validate on migrate default matches to true") void testValidateOnMigrate() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.validateOnMigrate(), createdFlywayConfig().isValidateOnMigrate()); - assertTrue(runtimeConfig.validateOnMigrate()); + assertEquals(runtimeConfig.validateOnMigrate, createdFlywayConfig().isValidateOnMigrate()); + assertTrue(runtimeConfig.validateOnMigrate); } @Test @DisplayName("clean disabled default matches to false") void testCleanDisabled() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.cleanDisabled(), createdFlywayConfig().isCleanDisabled()); - assertFalse(runtimeConfig.cleanDisabled()); + assertEquals(runtimeConfig.cleanDisabled, createdFlywayConfig().isCleanDisabled()); + assertFalse(runtimeConfig.cleanDisabled); - Mockito.when(runtimeConfig.cleanDisabled()).thenReturn(false); + runtimeConfig.cleanDisabled = false; creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(createdFlywayConfig().isCleanDisabled()); - Mockito.when(runtimeConfig.cleanDisabled()).thenReturn(true); + runtimeConfig.cleanDisabled = true; creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isCleanDisabled()); } @@ -208,11 +189,11 @@ void testCleanDisabled() { @Test @DisplayName("outOfOrder is correctly set") void testOutOfOrder() { - Mockito.when(runtimeConfig.outOfOrder()).thenReturn(false); + runtimeConfig.outOfOrder = false; creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(createdFlywayConfig().isOutOfOrder()); - Mockito.when(runtimeConfig.outOfOrder()).thenReturn(true); + runtimeConfig.outOfOrder = true; creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isOutOfOrder()); } @@ -220,11 +201,11 @@ void testOutOfOrder() { @Test @DisplayName("ignoreMissingMigrations is correctly set") void testIgnoreMissingMigrations() { - Mockito.when(runtimeConfig.ignoreMissingMigrations()).thenReturn(false); + runtimeConfig.ignoreMissingMigrations = false; creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(ValidatePatternUtils.isMissingIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); - Mockito.when(runtimeConfig.ignoreMissingMigrations()).thenReturn(true); + runtimeConfig.ignoreMissingMigrations = true; creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(ValidatePatternUtils.isMissingIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); } @@ -232,11 +213,11 @@ void testIgnoreMissingMigrations() { @Test @DisplayName("ignoreFutureMigrations is correctly set") void testIgnoreFutureMigrations() { - Mockito.when(runtimeConfig.ignoreFutureMigrations()).thenReturn(false); + runtimeConfig.ignoreFutureMigrations = false; creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(ValidatePatternUtils.isFutureIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); - Mockito.when(runtimeConfig.ignoreFutureMigrations()).thenReturn(true); + runtimeConfig.ignoreFutureMigrations = true; creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(ValidatePatternUtils.isFutureIgnored(createdFlywayConfig().getIgnoreMigrationPatterns())); } @@ -245,14 +226,14 @@ void testIgnoreFutureMigrations() { @DisplayName("cleanOnValidationError defaults to false and is correctly set") void testCleanOnValidationError() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.cleanOnValidationError(), createdFlywayConfig().isCleanOnValidationError()); - assertFalse(runtimeConfig.cleanOnValidationError()); + assertEquals(runtimeConfig.cleanOnValidationError, createdFlywayConfig().isCleanOnValidationError()); + assertFalse(runtimeConfig.cleanOnValidationError); - Mockito.when(runtimeConfig.cleanOnValidationError()).thenReturn(false); + runtimeConfig.cleanOnValidationError = false; creator = new FlywayCreator(runtimeConfig, buildConfig); assertFalse(createdFlywayConfig().isCleanOnValidationError()); - Mockito.when(runtimeConfig.cleanOnValidationError()).thenReturn(true); + runtimeConfig.cleanOnValidationError = true; creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isCleanOnValidationError()); } @@ -261,20 +242,20 @@ void testCleanOnValidationError() { @MethodSource("validateOnMigrateOverwritten") @DisplayName("validate on migrate overwritten in configuration") void testValidateOnMigrateOverwritten(final boolean input, final boolean expected) { - Mockito.when(runtimeConfig.validateOnMigrate()).thenReturn(input); + runtimeConfig.validateOnMigrate = input; creator = new FlywayCreator(runtimeConfig, buildConfig); assertEquals(createdFlywayConfig().isValidateOnMigrate(), expected); - assertEquals(runtimeConfig.validateOnMigrate(), expected); + assertEquals(runtimeConfig.validateOnMigrate, expected); } @Test @DisplayName("validateMigrationNaming defaults to false and it is correctly set") void testValidateMigrationNaming() { creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(runtimeConfig.validateMigrationNaming(), createdFlywayConfig().isValidateMigrationNaming()); - assertFalse(runtimeConfig.validateMigrationNaming()); + assertEquals(runtimeConfig.validateMigrationNaming, createdFlywayConfig().isValidateMigrationNaming()); + assertFalse(runtimeConfig.validateMigrationNaming); - Mockito.when(runtimeConfig.validateMigrationNaming()).thenReturn(true); + runtimeConfig.validateMigrationNaming = true; creator = new FlywayCreator(runtimeConfig, buildConfig); assertTrue(createdFlywayConfig().isValidateMigrationNaming()); } @@ -284,13 +265,13 @@ void testValidateMigrationNaming() { void testIgnoreMigrationPatterns() { creator = new FlywayCreator(runtimeConfig, buildConfig); assertEquals(0, createdFlywayConfig().getIgnoreMigrationPatterns().length); - assertFalse(runtimeConfig.ignoreMigrationPatterns().isPresent()); + assertFalse(runtimeConfig.ignoreMigrationPatterns.isPresent()); - Mockito.when(runtimeConfig.ignoreMigrationPatterns()).thenReturn(Optional.of(new String[] { "*:missing" })); + runtimeConfig.ignoreMigrationPatterns = Optional.of(new String[] { "*:missing" }); creator = new FlywayCreator(runtimeConfig, buildConfig); final ValidatePattern[] existingIgnoreMigrationPatterns = createdFlywayConfig().getIgnoreMigrationPatterns(); assertEquals(1, existingIgnoreMigrationPatterns.length); - final String[] ignoreMigrationPatterns = runtimeConfig.ignoreMigrationPatterns().get(); + final String[] ignoreMigrationPatterns = runtimeConfig.ignoreMigrationPatterns.get(); final ValidatePattern[] validatePatterns = Arrays.stream(ignoreMigrationPatterns) .map(ValidatePattern::fromPattern).toArray(ValidatePattern[]::new); assertArrayEquals(validatePatterns, existingIgnoreMigrationPatterns); From fd48630e1e28a13731d3268d6e2cc0ff6492b0be Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 3 May 2023 09:58:42 +0200 Subject: [PATCH 208/333] ArC - static method interception fix - fix the problem with dev mode and external libraries - also make sure the generated method does not start with a number - fixes #32990 --- .../InterceptedStaticMethodBuildItem.java | 8 ++++ .../InterceptedStaticMethodsProcessor.java | 43 +++++++++++++++---- .../InterceptedStaticMethodsRecorder.java | 1 + 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java index 991405d1cfd42c..bc565c4bf15cdf 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java @@ -60,4 +60,12 @@ public String getHash() { return hash; } + /** + * + * @return the name of the generated forwarding method + */ + public String getForwardingMethodName() { + return "_" + hash; + } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java index 6e5f32ac07e652..0e20e735c60e22 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java @@ -17,6 +17,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Predicate; import java.util.stream.Collectors; import jakarta.enterprise.context.spi.Contextual; @@ -39,6 +40,7 @@ import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; +import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem; import io.quarkus.arc.deployment.InterceptorResolverBuildItem; import io.quarkus.arc.deployment.TransformedAnnotationsBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; @@ -146,6 +148,7 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, BeanRegistrationPhaseBuildItem phase, List interceptedStaticMethods, + CompletedApplicationClassPredicateBuildItem applicationClassPredicate, BuildProducer generatedClasses, BuildProducer transformers, BuildProducer reflectiveMethods) { @@ -154,7 +157,27 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, return; } - ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true); + // org.acme.Foo -> org.acme.Foo_InterceptorInitializer + Map baseToGeneratedInitializer = new HashMap<>(); + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, new Predicate() { + + @Override + public boolean test(String name) { + // For example, for base org.acme.Foo we generate org.acme.Foo_InterceptorInitializer + // and possibly anonymous classes like org.acme.Foo_InterceptorInitializer$$function$$1 + name = name.replace('/', '.'); + DotName base = null; + for (Entry e : baseToGeneratedInitializer.entrySet()) { + if (e.getValue().equals(name) || name.startsWith(e.getValue())) { + base = e.getKey(); + } + } + if (base == null) { + throw new IllegalStateException("Unable to find the base class for the generated: " + name); + } + return applicationClassPredicate.test(base); + } + }); // declaring class -> intercepted static methods Map> interceptedStaticMethodsMap = new HashMap<>(); @@ -172,14 +195,14 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, // 1. registers all interceptor chains inside an "init_static_intercepted_methods" method // 2. adds static methods to invoke the interceptor chain and delegate to the copy of the original static method // declaring class -> initializer class - Map initializers = new HashMap<>(); + String initAllMethodName = "init_static_intercepted_methods"; for (Entry> entry : interceptedStaticMethodsMap.entrySet()) { String packageName = DotNames.internalPackageNameWithTrailingSlash(entry.getKey()); String initializerName = packageName.replace("/", ".") + entry.getKey().withoutPackagePrefix() + INITIALIZER_CLASS_SUFFIX; - initializers.put(entry.getKey(), initializerName); + baseToGeneratedInitializer.put(entry.getKey(), initializerName); ClassCreator initializer = ClassCreator.builder().classOutput(classOutput) .className(initializerName).setFinal(true).build(); @@ -206,16 +229,17 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, // For each intercepted static methods create a copy and modify the original method to delegate to the relevant initializer for (Entry> entry : interceptedStaticMethodsMap.entrySet()) { transformers.produce(new BytecodeTransformerBuildItem(entry.getKey().toString(), - new InterceptedStaticMethodsEnhancer(initializers.get(entry.getKey()), entry.getValue()))); + new InterceptedStaticMethodsEnhancer(baseToGeneratedInitializer.get(entry.getKey()), entry.getValue()))); } - // Generate a global initializer that calls all other initializers - ClassCreator globalInitializer = ClassCreator.builder().classOutput(classOutput) + // Generate a global initializer that calls all other initializers; this initializer must be loaded by the runtime ClassLoader + ClassCreator globalInitializer = ClassCreator.builder() + .classOutput(new GeneratedClassGizmoAdaptor(generatedClasses, true)) .className(InterceptedStaticMethodsRecorder.INITIALIZER_CLASS_NAME.replace('.', '/')).setFinal(true).build(); MethodCreator staticInit = globalInitializer.getMethodCreator("", void.class) .setModifiers(ACC_STATIC); - for (String initializerClass : initializers.values()) { + for (String initializerClass : baseToGeneratedInitializer.values()) { staticInit.invokeStaticMethod( MethodDescriptor.ofMethod(initializerClass, initAllMethodName, void.class)); } @@ -242,7 +266,8 @@ private void implementForward(ClassCreator initializer, paramTypes[i] = DescriptorUtils.typeToString(params.get(i)); } MethodCreator forward = initializer - .getMethodCreator(interceptedStaticMethod.getHash(), DescriptorUtils.typeToString(method.returnType()), + .getMethodCreator(interceptedStaticMethod.getForwardingMethodName(), + DescriptorUtils.typeToString(method.returnType()), paramTypes) .setModifiers(ACC_PUBLIC | ACC_FINAL | ACC_STATIC); ResultHandle argArray = forward.newArray(Object.class, params.size()); @@ -491,7 +516,7 @@ public void visitEnd() { paramSlot += AsmUtil.getParameterSize(paramType); } superVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - initializerClassName.replace('.', '/'), interceptedStaticMethod.getHash(), + initializerClassName.replace('.', '/'), interceptedStaticMethod.getForwardingMethodName(), descriptor.getDescriptor().toString(), false); superVisitor.visitInsn(AsmUtil.getReturnInstruction(interceptedStaticMethod.getMethod().returnType())); diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java index 2c62ba21a5458f..86a993c174a393 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java @@ -5,6 +5,7 @@ @Recorder public class InterceptedStaticMethodsRecorder { + // This class is generated and calls all generated static interceptor initializers to register metadata in the InterceptedStaticMethods class public static final String INITIALIZER_CLASS_NAME = "io.quarkus.arc.runtime.InterceptedStaticMethodsInitializer"; public void callInitializer() { From 5e1b9daa6d343678b749b84848ca1b3391aaf75f Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 2 May 2023 16:25:37 +0200 Subject: [PATCH 209/333] Gradle modules build: Allow Java 20 Gradle's Kotlin scripts don't (yet) build against Java 20. Force Java 11 for build scripts. --- devtools/gradle/build-logic/build.gradle.kts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/devtools/gradle/build-logic/build.gradle.kts b/devtools/gradle/build-logic/build.gradle.kts index 4dc03934dc0838..05ffa8ec29dd1f 100644 --- a/devtools/gradle/build-logic/build.gradle.kts +++ b/devtools/gradle/build-logic/build.gradle.kts @@ -6,5 +6,13 @@ dependencies { implementation(plugin("com.gradle.plugin-publish", "1.2.0")) } +java { toolchain { + // this is fine, even for Java 1.x + val javaMajor = JavaVersion.current().majorVersion.toInt() + // Need to limit the Java version for Kotlin to 17, because 20 doesn't work. + // Also prefer the current version to prevent JDK downloads. + languageVersion.set(JavaLanguageVersion.of(javaMajor.coerceAtMost(17))) +} } + fun DependencyHandler.plugin(id: String, version: String) = create("$id:$id.gradle.plugin:$version") From a93245e8b59467ab5278f3f1aa56f2cb4f8a67c5 Mon Sep 17 00:00:00 2001 From: Romain Pelisse Date: Mon, 24 Apr 2023 10:00:43 +0200 Subject: [PATCH 210/333] Add a small tutorial on deploying Quarkus app with Ansible --- docs/src/main/asciidoc/ansible.adoc | 253 ++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 docs/src/main/asciidoc/ansible.adoc diff --git a/docs/src/main/asciidoc/ansible.adoc b/docs/src/main/asciidoc/ansible.adoc new file mode 100644 index 00000000000000..45288558aee2f1 --- /dev/null +++ b/docs/src/main/asciidoc/ansible.adoc @@ -0,0 +1,253 @@ += Automate Quarkus deployment with Ansible +include::_attributes.adoc[] +:categories: command-line +:summary: Build and deploy your Quarkus App using Ansible + + +Let’s see how to build and deploy a Quarkus app using https://docs.ansible.com/ansible/latest/index.html[Ansible]. We’ll see how we can automate the entire process, from the code checkout to the application compilation using Maven and then its deployment and start of the service, as a https://systemd.io/[systemd service], on the target system using Ansible and its collection for Quarkus. + +The first part, the application code checkout, compilation and packaging on the Ansible (where Ansible runs). We’ll use the getting-started sample application provided in its {quickstarts-tree-url}/getting-started[Quarkus QuickStarts directory] as a base for this tutorial. We’ll also leverage the https://galaxy.ansible.com/middleware_automation/quarkus[Quarkus collection] for Ansible, an extension for Ansible that alleviates the boilerplate required and to quickly build and deploy a Quarkus using Ansible. + +== Prerequisites + +:prerequisites-time: 10 minutes +:prerequisites-no-maven: +:prerequisites-no-cli: +include::{includes}/prerequisites.adoc[] + +You'll need to https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html[install Ansible] on your workstation. Once this is done, you can install this extension for Ansible dedicated to Quarkus with the following command: + +[source,bash] +---- +$ ansible-galaxy collection install middleware_automation.quarkus +---- + +=== Inventory file + +If you are not familiar with Ansible, please note that the inventory needs to be provided for the command above to run properly. This is a simple text file providing the information Ansible requires on the target system it manages. Please refer to the Ansible documentation for more information on https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html[Ansible inventory]. + +[source,bash] +---- +[all] + +10.0.0.1 +10.0.0.2 +---- + +To follow the tutorial, you may want to use only one machine (localhost) and skip the ssh authentication setup. This can be easily achieved by using the following inventory file: + +[source,bash] +---- +[all] +localhost ansible_connection=local +---- + +=== Root access on target system + +A few tasks performed by the Ansible collection for Quarkus will require administrative privileges on the target (create a group and user account, install packages). If Ansible does run as root, you'll need to add the following options to the `ansible-playbook` command line: + +[source,bash] +---- +$ ansible-playbook -i inventory --ask-become-pass ... +---- + +== Tutorial + +With the Ansible collection installed on the controller, you can already directly use a playbook provided with it to build and deploy your Quarkus application: + +[source,bash] +---- +ansible-playbook -i inventory \ + middleware_automation.quarkus.playbook \ + -e app_name=getting-started \ + -e quarkus_app_repo_url='https://github.com/quarkusio/quarkus-quickstarts.git' \ + -e quarkus_app_source_folder='getting-started' \ + -e quarkus_path_to_folder_to_deploy=/opt/quarkus_deploy +---- + +The four parameters provided to the playbook are pretty self-explanatory. The first one, `app_name`, is the name of the application, in our case, it's just `getting-started`. The second one, `quarkus_app_repo_url`, is the URL to the Git repository to checkout. The third one is optional, `quarkus_app_source_folder` specifies, if needed, which subfolder from the repo the source code is located. Finally, the fourth one indicates where to install the application on the target. + +=== Playbook run + +Once the command above has successfully run, you should have an output similar to the one below: + +[source,bash] +---- +… + +PLAY [Build and deploy a Quarkus app using Ansible] **************************** + +TASK [Build the Quarkus from https://github.com/quarkusio/quarkus-quickstarts.git.] *** + +TASK [middleware_automation.quarkus.quarkus : Ensure required parameters are provided.] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Define path to mvnw script.] ***** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure that builder host localhost has appropriate JDK installed: java-17-openjdk] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Delete previous workdir (if requested).] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure app workdir exists: /tmp/workdir] *** +changed: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Checkout the application source code.] *** +changed: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Build the App using Maven] ******* +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Display build application log] *** +skipping: [localhost] + +TASK [Deploy webapp on target.] ************************************************ + +TASK [middleware_automation.quarkus.quarkus : Ensure required parameters are provided.] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure required OpenJDK is installed on target.] *** +skipping: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure Quarkus system group exists on target system] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure Quarkus user exists on target system.] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure deployement directory exits: /opt/quarkus_deploy.] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Set Quarkus app source dir (if not defined).] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Deploy application from to target system] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Deploy Systemd configuration for Quarkus app] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Perform daemon-reload to ensure the changes are picked up] *** +skipping: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure Quarkus app service is running.] *** +ok: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Ensure firewalld is available.] *** +skipping: [localhost] + +TASK [middleware_automation.quarkus.quarkus : Configure firewall for 8080 ports] *** +skipping: [localhost] + +PLAY RECAP ********************************************************************* +localhost : ok=15 changed=2 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0 +… +---- + +The Ansible collection for Quarkus does all the `heavy lifting` here. First, it checks out the code from Github and builds the application from its sources. It also ensures the system used for this step does have the required OpenJDK installed. By default, the application is built on the localhost (the Ansible controller), but it can be performed on a remote system if needed. Once the application is built, the collection will take care of the deployment. + +Here again, it checks that the appropriate OpenJDK is available on the target system. Then we ensure that the required user and group exist on the target. This is recommended mostly to be able to run the Quarkus app with a regular user, rather than with the root account. + +With those requirements in place, the jar can be deployed on the target, along with the required configuration for the app integration into systemd as a service. Any change to the systemd configuration requires reloading its daemon, which the collection ensures will happen whenever it is needed. With all of that in place, the only thing that remains is to start the service itself, which Ansible will also take care of. + +=== Validate that deployment was successful + +For the purpose of this tutorial, you may want to check manually, that the playbook did indeed deployed the app properly. Here is the few ways to do so. + +First, because the collection integrated, we can check the status of the service on one of the targets: + +[source,bash] +---- +# systemctl status getting-started.service +● getting-started.service - A Quarkus service named getting-started + Loaded: loaded (/usr/lib/systemd/system/getting-started.service; enabled; vendor preset: disabled) + Active: active (running) since Thu 2023-04-13 12:48:18 UTC; 2min 40s ago + Main PID: 853 (java) + Tasks: 43 (limit: 1638) + Memory: 73.3M + CGroup: /system.slice/getting-started.service + └─853 /usr/bin/java -jar /opt/quarkus_deploy/quarkus-run.jar + +Apr 13 12:48:18 bd71f39642c8 systemd[1]: Started A Quarkus service named getting-started. +Apr 13 12:48:19 bd71f39642c8 java[853]: __ ____ __ _____ ___ __ ____ ______ +Apr 13 12:48:19 bd71f39642c8 java[853]: --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ +Apr 13 12:48:19 bd71f39642c8 java[853]: -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ +Apr 13 12:48:19 bd71f39642c8 java[853]: --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ +Apr 13 12:48:19 bd71f39642c8 java[853]: 2023-04-13 12:48:19,284 INFO [io.quarkus] (main) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.16.6.Final) started in 0.607s. Listening on: http://0.0.0.0:8080 +Apr 13 12:48:19 bd71f39642c8 java[853]: 2023-04-13 12:48:19,309 INFO [io.quarkus] (main) Profile prod activated. +Apr 13 12:48:19 bd71f39642c8 java[853]: 2023-04-13 12:48:19,310 INFO [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx] +---- + +Manually, you can also test if the app is reachable: + +[source,bash] +---- +# curl -I http://localhost:8080/ +HTTP/1.1 200 OK +accept-ranges: bytes +content-length: 3918 +cache-control: public, immutable, max-age=86400 +last-modified: Thu, 2 Mar 2023 11:03:18 GMT +date: Thu, 2 Mar 2023 11:03:18 GMT +---- + +We'll see how to automate those validation in the next and last part of this tutorial. + +=== Writing a playbook + +Of course, you’ll most likely need to build on this, so you may want to write up your own playbook, rather than just using the one provided by the collection. Below is an example of such playbook: + +[source,yml] +---- +- name: "Build and deploy a Quarkus app using Ansible" + hosts: all + gather_facts: true + vars: + quarkus_app_repo_url: 'https://github.com/quarkusio/quarkus-quickstarts.git' + app_name: getting-started + quarkus_app_source_folder: getting-started + quarkus_path_to_folder_to_deploy: /opt/quarkus_deploy + + pre_tasks: + - name: "Build the Quarkus from {{ quarkus_app_repo_url }}." + ansible.builtin.include_role: + name: quarkus + tasks_from: build.yml + tasks: + - name: "Deploy Quarkus app on target." + ansible.builtin.include_role: + name: quarkus + tasks_from: deploy.yml +---- + +To run this playbook, you use again the ansible-playbook command, but providing now the path to the playbook: + +[source,bash] +---- +$ ansible-playbook -i inventory playbook.yml +---- + +You also can automate the validation part we mentioned in the previous section. You can use the https://docs.ansible.com/ansible/latest/collections/ansible/builtin/assert_module.html[ansible.builtin.assert] module and populate the https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_facts_module.html#examples[service facts] to ensure the systemd service of the Quarkus app is running, along with https://docs.ansible.com/ansible/latest/collections/ansible/builtin/uri_module.html[ansible.builtin.uri] to check that the Quarkus app is accessible. + +``` + post_tasks: + - name: Populate service facts + ansible.builtin.service_facts: + + - name: "Check that systemd service {{ app_name }} is running." + ansible.builtin.assert: + that: + - ansible_facts.services is defined + - ansible_facts.services["{{ app_name }}.service"] is defined + - ansible_facts.services["{{ app_name }}.service"]['state'] == 'running' + - ansible_facts.services["{{ app_name }}.service"]['status'] == 'enabled' + quiet: true + + - name: "Check that Quarkus app is accessible" + ansible.builtin.uri: + url: 'http://localhost:8080/' +``` + +And that’s all, folks! From adb04e996d20c77c5e9edbf089afe5b68265a148 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 3 May 2023 11:43:33 +0200 Subject: [PATCH 211/333] Fix completion mode field when creating a K8s Job resource and Flyway Fix https://github.com/quarkusio/quarkus/issues/33085 --- .../deployment/CreateJobResourceFromImageDecorator.java | 6 ++++-- .../quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/CreateJobResourceFromImageDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/CreateJobResourceFromImageDecorator.java index 307f810095ece5..c69ca855c51a57 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/CreateJobResourceFromImageDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/CreateJobResourceFromImageDecorator.java @@ -6,6 +6,8 @@ import java.util.List; +import io.dekorate.kubernetes.annotation.JobCompletionMode; +import io.dekorate.kubernetes.annotation.JobRestartPolicy; import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.batch.v1.JobBuilder; @@ -15,8 +17,8 @@ **/ public class CreateJobResourceFromImageDecorator extends ResourceProvidingDecorator { - private static final String DEFAULT_RESTART_POLICY = "OnFailure"; - private static final String DEFAULT_COMPLETION_MODE = "OnFailure"; + private static final String DEFAULT_RESTART_POLICY = JobRestartPolicy.OnFailure.name(); + private static final String DEFAULT_COMPLETION_MODE = JobCompletionMode.NonIndexed.name(); private final String name; private final String image; diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java index 827c7b3c0c7f64..328352faae67f1 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java @@ -79,8 +79,10 @@ public void assertGeneratedResources() throws IOException { assertThat(job.get()).satisfies(j -> { assertThat(j.getSpec()).satisfies(jobSpec -> { + assertThat(jobSpec.getCompletionMode()).isEqualTo("NonIndexed"); assertThat(jobSpec.getTemplate()).satisfies(t -> { assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getRestartPolicy()).isEqualTo("OnFailure"); assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { assertThat(container.getName()).isEqualTo("flyway-init"); assertThat(container.getEnv()).filteredOn(env -> "QUARKUS_FLYWAY_ENABLED".equals(env.getName())) From 4badd39b7a3f20e4dbd7f23ecdb8f20a8b0d2ca6 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 3 May 2023 12:48:55 +0200 Subject: [PATCH 212/333] Gradle-Model: make kotlin-gradle-plugin-api a compileOnly dependency ... to avoid having potential kotlin plugin dependency issues. Also unifies the common code to configure compile tasks (bit more complicated, because the KotlinJvmCompile does not extend AbstractCompile like all other/most compile tasks). And moves the actual use of `KotlinJvmCompile` to a separate method, so that the safeguard `Class.forName("...KotlinJvmCompile") can actually trigger the intended behavior. --- devtools/gradle/gradle-model/build.gradle.kts | 2 +- .../GradleApplicationModelBuilder.java | 93 +++++++++---------- 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/devtools/gradle/gradle-model/build.gradle.kts b/devtools/gradle/gradle-model/build.gradle.kts index 1811928676d470..be5055edd3d8c4 100644 --- a/devtools/gradle/gradle-model/build.gradle.kts +++ b/devtools/gradle/gradle-model/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } dependencies { - implementation(libs.kotlin.gradle.plugin.api) + compileOnly(libs.kotlin.gradle.plugin.api) } group = "io.quarkus" diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java index 0344dda228d186..834f95206d32bc 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java @@ -19,10 +19,12 @@ import java.util.Set; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.initialization.IncludedBuild; @@ -479,54 +481,10 @@ private static void initProjectModule(Project project, WorkspaceModule.Mutable m // see https://github.com/quarkusio/quarkus/issues/20755 final List sourceDirs = new ArrayList<>(1); - project.getTasks().withType(AbstractCompile.class, t -> { - if (!t.getEnabled()) { - return; - } - final FileTree source = t.getSource(); - if (source.isEmpty()) { - return; - } - final File destDir = t.getDestinationDirectory().getAsFile().get(); - if (!allClassesDirs.contains(destDir)) { - return; - } - source.visit(a -> { - // we are looking for the root dirs containing sources - if (a.getRelativePath().getSegments().length == 1) { - final File srcDir = a.getFile().getParentFile(); - sourceDirs.add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(), Map.of("compiler", t.getName()))); - } - }); - }); + project.getTasks().withType(AbstractCompile.class, + t -> configureCompileTask(t.getSource(), t.getDestinationDirectory(), allClassesDirs, sourceDirs, t)); - // This "try/catch" is needed because of the way the "quarkus-cli" Gradle tests work. Without it, the tests fail. - try { - Class.forName("org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile"); - project.getTasks().withType(KotlinJvmCompile.class, t -> { - if (!t.getEnabled()) { - return; - } - final FileTree source = t.getSources().getAsFileTree(); - if (source.isEmpty()) { - return; - } - final File destDir = t.getDestinationDirectory().getAsFile().get(); - if (!allClassesDirs.contains(destDir)) { - return; - } - source.visit(a -> { - // we are looking for the root dirs containing sources - if (a.getRelativePath().getSegments().length == 1) { - final File srcDir = a.getFile().getParentFile(); - sourceDirs - .add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(), Map.of("compiler", t.getName()))); - } - }); - }); - } catch (ClassNotFoundException e) { - // ignore - } + maybeConfigureKotlinJvmCompile(project, allClassesDirs, sourceDirs); final LinkedHashMap resourceDirs = new LinkedHashMap<>(1); final File resourcesOutputDir = sourceSet.getOutput().getResourcesDir(); @@ -562,6 +520,47 @@ private static void initProjectModule(Project project, WorkspaceModule.Mutable m module.addArtifactSources(new DefaultArtifactSources(classifier, sourceDirs, resources)); } + private static void maybeConfigureKotlinJvmCompile(Project project, FileCollection allClassesDirs, + List sourceDirs) { + // This "try/catch" is needed because of the way the "quarkus-cli" Gradle tests work. Without it, the tests fail. + try { + Class.forName("org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile"); + doConfigureKotlinJvmCompile(project, allClassesDirs, sourceDirs); + } catch (ClassNotFoundException e) { + // ignore + } + } + + private static void doConfigureKotlinJvmCompile(Project project, FileCollection allClassesDirs, + List sourceDirs) { + // Use KotlinJvmCompile.class in a separate method to prevent that maybeConfigureKotlinJvmCompile() runs into + // a ClassNotFoundException due to actually using KotlinJvmCompile.class. + project.getTasks().withType(KotlinJvmCompile.class, t -> configureCompileTask(t.getSources().getAsFileTree(), + t.getDestinationDirectory(), allClassesDirs, sourceDirs, t)); + } + + private static void configureCompileTask(FileTree sources, DirectoryProperty destinationDirectory, + FileCollection allClassesDirs, List sourceDirs, Task task) { + if (!task.getEnabled()) { + return; + } + if (sources.isEmpty()) { + return; + } + final File destDir = destinationDirectory.getAsFile().get(); + if (!allClassesDirs.contains(destDir)) { + return; + } + sources.visit(a -> { + // we are looking for the root dirs containing sources + if (a.getRelativePath().getSegments().length == 1) { + final File srcDir = a.getFile().getParentFile(); + sourceDirs + .add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(), Map.of("compiler", task.getName()))); + } + }); + } + private void addSubstitutedProject(PathList.Builder paths, File projectFile) { File mainResourceDirectory = new File(projectFile, MAIN_RESOURCES_OUTPUT); if (mainResourceDirectory.exists()) { From 732b5b25a87fa35cd45908b54d3a9c3dad41ded5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 3 May 2023 14:22:37 +0200 Subject: [PATCH 213/333] Remove useless method call See the if() just below. --- .../annotation/processor/generate_doc/ConfigDocItemFinder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java index 0e5c0643f18eaf..69ef7f45bc3cb5 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java @@ -141,8 +141,6 @@ private List recursivelyFindConfigItems(Element element, String r } for (Element enclosedElement : element.getEnclosedElements()) { - shouldProcessElement(enclosedElement); - if (!shouldProcessElement(enclosedElement)) { continue; } From 96c295fd7889d89e3071e14d90d7c87e37875070 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 3 May 2023 14:23:09 +0200 Subject: [PATCH 214/333] Fix regression with defaultValueDocumentation for Lists and Optionals --- .../processor/generate_doc/ConfigDocItemFinder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java index 69ef7f45bc3cb5..7cf05a5099026f 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java @@ -330,6 +330,10 @@ private List recursivelyFindConfigItems(Element element, String r acceptedValues = extractEnumValues(realTypeMirror, useHyphenateEnumValue, clazz.getQualifiedName().toString()); configDocKey.setEnum(true); + } else { + if (!defaultValueDoc.isBlank()) { + defaultValue = defaultValueDoc; + } } } } else { From 152c2ff110dee884583e79694b6719656e1ca62c Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 3 May 2023 15:03:57 +0200 Subject: [PATCH 215/333] deps: Bump kubernetes-client-bom from 6.5.1 to 6.6.0 Signed-off-by: Marc Nuri --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 507adef1011aaa..a66ae48da1c8cd 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 0.8.10 - 6.5.1 + 6.6.0 1.54.1 From 5c659ef62849a9416f9e1221a926087be23037eb Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 28 Apr 2023 11:10:26 +0200 Subject: [PATCH 216/333] Dev mode - make it possibile to watch files matching a predicate - HotDeploymentWatchedFileBuildItem#builder().setPredicate(p).build() --- .../HotDeploymentWatchedFileBuildItem.java | 89 ++++++++++++++++++- .../HotDeploymentWatchedFileBuildStep.java | 15 +++- .../dev/RuntimeUpdatesProcessor.java | 39 +++++--- .../deployment/dev/testing/TestSupport.java | 3 +- .../quarkus/dev/testing/TestWatchedFiles.java | 46 +++++++++- 5 files changed, 172 insertions(+), 20 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java index a7efbbbc4f677e..359c546612e6ff 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java @@ -1,29 +1,83 @@ package io.quarkus.deployment.builditem; +import java.util.function.Predicate; + import io.quarkus.builder.item.MultiBuildItem; /** - * A file that if modified may result in a hot redeployment when in the dev mode. + * Identifies a file from a + * {@link io.quarkus.bootstrap.devmode.DependenciesFilter#getReloadableModules(io.quarkus.bootstrap.model.ApplicationModel) + * reloadable module} that, if modified, may result in a hot redeployment when in the dev mode. + *

    + * A file may be identified with an exact location or a matching predicate. See {@link Builder#setLocation(String)} and + * {@link Builder#setLocationPredicate(Predicate)}. + *

    + * If multiple build items match the same file then the final value of {@code restartNeeded} is computed as a logical OR of all + * the {@link #isRestartNeeded()} values. */ public final class HotDeploymentWatchedFileBuildItem extends MultiBuildItem { + public static Builder builder() { + return new Builder(); + } + private final String location; + private final Predicate locationPredicate; private final boolean restartNeeded; + /** + * + * @param location + * @see #builder() + */ public HotDeploymentWatchedFileBuildItem(String location) { this(location, true); } + /** + * + * @param location + * @param restartNeeded + * @see #builder() + */ public HotDeploymentWatchedFileBuildItem(String location, boolean restartNeeded) { + this(location, null, restartNeeded); + } + + private HotDeploymentWatchedFileBuildItem(String location, Predicate locationPredicate, boolean restartNeeded) { + if (location == null && locationPredicate == null) { + throw new IllegalArgumentException("Either location or predicate must be set"); + } this.location = location; + this.locationPredicate = locationPredicate; this.restartNeeded = restartNeeded; } + /** + * + * @return a location a file from a reloadable module + */ public String getLocation() { return location; } + public boolean hasLocation() { + return location != null; + } + + /** + * + * @return a predicate used to match a file from a reloadable module + */ + public Predicate getLocationPredicate() { + return locationPredicate; + } + + public boolean hasLocationPredicate() { + return locationPredicate != null; + } + /** * * @return {@code true} if a file change should result in an application restart, {@code false} otherwise @@ -32,4 +86,37 @@ public boolean isRestartNeeded() { return restartNeeded; } + public static class Builder { + + private String location; + private Predicate locationPredicate; + private boolean restartNeeded = true; + + public Builder setLocation(String location) { + if (locationPredicate != null) { + throw new IllegalArgumentException("Predicate already set"); + } + this.location = location; + return this; + } + + public Builder setLocationPredicate(Predicate locationPredicate) { + if (location != null) { + throw new IllegalArgumentException("Location already set"); + } + this.locationPredicate = locationPredicate; + return this; + } + + public Builder setRestartNeeded(boolean restartNeeded) { + this.restartNeeded = restartNeeded; + return this; + } + + public HotDeploymentWatchedFileBuildItem build() { + return new HotDeploymentWatchedFileBuildItem(location, locationPredicate, restartNeeded); + } + + } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java index d4b832420f7cb7..81c2c3a122b7fd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/HotDeploymentWatchedFileBuildStep.java @@ -2,6 +2,8 @@ import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Predicate; import java.util.stream.Collectors; import io.quarkus.deployment.annotations.BuildStep; @@ -18,14 +20,21 @@ ServiceStartBuildItem setupWatchedFileHotDeployment(List watchedFilePaths = files.stream() + + Map watchedFilePaths = files.stream().filter(HotDeploymentWatchedFileBuildItem::hasLocation) .collect(Collectors.toMap(HotDeploymentWatchedFileBuildItem::getLocation, HotDeploymentWatchedFileBuildItem::isRestartNeeded, (isRestartNeeded1, isRestartNeeded2) -> isRestartNeeded1 || isRestartNeeded2)); + + List, Boolean>> watchedFilePredicates = files.stream() + .filter(HotDeploymentWatchedFileBuildItem::hasLocationPredicate) + .map(f -> Map.entry(f.getLocationPredicate(), f.isRestartNeeded())) + .collect(Collectors.toUnmodifiableList()); + if (launchModeBuildItem.isAuxiliaryApplication()) { - TestWatchedFiles.setWatchedFilePaths(watchedFilePaths); + TestWatchedFiles.setWatchedFilePaths(watchedFilePaths, watchedFilePredicates); } else { - processor.setWatchedFilePaths(watchedFilePaths, false); + processor.setWatchedFilePaths(watchedFilePaths, watchedFilePredicates, false); } } return null; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index 63dad57889b286..c72ffcdf0f0f55 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -331,11 +332,10 @@ private void periodicTestCompile() { } Set filesChanges = new HashSet<>(checkForFileChange(s -> s.getTest().orElse(null), test)); filesChanges.addAll(checkForFileChange(DevModeContext.ModuleInfo::getMain, test)); - boolean configFileRestartNeeded = filesChanges.stream().map(test.watchedFilePaths::get) - .anyMatch(Boolean.TRUE::equals); + boolean fileRestartNeeded = filesChanges.stream().anyMatch(test::isWatchedFileRestartNeeded); ClassScanResult merged = ClassScanResult.merge(changedTestClassResult, changedApp); - if (configFileRestartNeeded) { + if (fileRestartNeeded) { if (testCompileProblem == null) { testSupport.runTests(null); } @@ -462,21 +462,19 @@ public boolean doScan(boolean userInitiated, boolean forceRestart) { main, false); Set filesChanged = checkForFileChange(DevModeContext.ModuleInfo::getMain, main); - boolean configFileRestartNeeded = forceRestart || filesChanged.stream().map(main.watchedFilePaths::get) - .anyMatch(Boolean.TRUE::equals); + boolean fileRestartNeeded = forceRestart || filesChanged.stream().anyMatch(main::isWatchedFileRestartNeeded); boolean instrumentationChange = false; List changedFilesForRestart = new ArrayList<>(); - if (configFileRestartNeeded) { - changedFilesForRestart - .addAll(filesChanged.stream().filter(fn -> Boolean.TRUE.equals(main.watchedFilePaths.get(fn))) - .map(Paths::get).collect(Collectors.toList())); + if (fileRestartNeeded) { + filesChanged.stream().filter(main::isWatchedFileRestartNeeded).map(Paths::get) + .forEach(changedFilesForRestart::add); } changedFilesForRestart.addAll(changedClassResults.getChangedClasses()); changedFilesForRestart.addAll(changedClassResults.getAddedClasses()); changedFilesForRestart.addAll(changedClassResults.getDeletedClasses()); - if (ClassChangeAgent.getInstrumentation() != null && lastStartIndex != null && !configFileRestartNeeded + if (ClassChangeAgent.getInstrumentation() != null && lastStartIndex != null && !fileRestartNeeded && devModeType != DevModeType.REMOTE_LOCAL_SIDE && instrumentationEnabled()) { //attempt to do an instrumentation based reload //if only code has changed and not the class structure, then we can do a reload @@ -530,7 +528,7 @@ public boolean doScan(boolean userInitiated, boolean forceRestart) { //all broken we just assume the reason that they have refreshed is because they have fixed something //trying to watch all resource files is complex and this is likely a good enough solution for what is already an edge case boolean restartNeeded = !instrumentationChange && (changedClassResults.isChanged() - || (IsolatedDevModeMain.deploymentProblem != null && userInitiated) || configFileRestartNeeded); + || (IsolatedDevModeMain.deploymentProblem != null && userInitiated) || fileRestartNeeded); if (restartNeeded) { String changeString = changedFilesForRestart.stream().map(Path::getFileName).map(Object::toString) .collect(Collectors.joining(", ")); @@ -1083,12 +1081,14 @@ public RuntimeUpdatesProcessor setDisableInstrumentationForIndexPredicate( return this; } - public RuntimeUpdatesProcessor setWatchedFilePaths(Map watchedFilePaths, boolean isTest) { + public RuntimeUpdatesProcessor setWatchedFilePaths(Map watchedFilePaths, + List, Boolean>> watchedFilePredicates, boolean isTest) { if (isTest) { setWatchedFilePathsInternal(watchedFilePaths, test, s -> s.getTest().isPresent() ? asList(s.getTest().get(), s.getMain()) : singletonList(s.getMain())); } else { main.watchedFileTimestamps.clear(); + main.watchedFilePredicates = watchedFilePredicates; setWatchedFilePathsInternal(watchedFilePaths, main, s -> singletonList(s.getMain())); } return this; @@ -1225,6 +1225,7 @@ static class TimestampSet { final Map classFilePathToSourceFilePath = new ConcurrentHashMap<>(); // file path -> isRestartNeeded private volatile Map watchedFilePaths = Collections.emptyMap(); + volatile List, Boolean>> watchedFilePredicates = Collections.emptyList(); public void merge(TimestampSet other) { watchedFileTimestamps.putAll(other.watchedFileTimestamps); @@ -1233,7 +1234,21 @@ public void merge(TimestampSet other) { Map newVal = new HashMap<>(watchedFilePaths); newVal.putAll(other.watchedFilePaths); watchedFilePaths = newVal; + // The list of predicates should be effectively immutable + watchedFilePredicates = other.watchedFilePredicates; + } + boolean isWatchedFileRestartNeeded(String changedFile) { + Boolean ret = watchedFilePaths.get(changedFile); + if (ret == null) { + ret = false; + for (Entry, Boolean> e : watchedFilePredicates) { + if (e.getKey().test(changedFile)) { + ret = ret || e.getValue(); + } + } + } + return ret; } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java index b72f5753c298ec..a03138e2f7e3a5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java @@ -154,7 +154,8 @@ private static Pattern getCompiledPatternOrNull(Optional patternStr) { public void init() { if (moduleRunners.isEmpty()) { - TestWatchedFiles.setWatchedFilesListener((s) -> RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(s, true)); + TestWatchedFiles.setWatchedFilesListener( + (paths, predicates) -> RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(paths, predicates, true)); final Pattern includeModulePattern = getCompiledPatternOrNull(config.includeModulePattern); final Pattern excludeModulePattern = getCompiledPatternOrNull(config.excludeModulePattern); for (var module : context.getAllModules()) { diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java index dd42190200d8cb..2744d7f6367785 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java @@ -1,7 +1,11 @@ package io.quarkus.dev.testing; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Predicate; /** * provides a way for a test run to tell the external application about watched paths. @@ -11,19 +15,55 @@ public class TestWatchedFiles { private static volatile Map watchedFilePaths; - private static volatile Consumer> watchedFilesListener; + private static volatile BiConsumer, List, Boolean>>> watchedFilesListener; + private static volatile List, Boolean>> watchedFilePredicates; + /** + * + * @param watchedFilePaths + * @deprecated Use {@link #setWatchedFilePaths(Map, List)} instead. + */ + @Deprecated(forRemoval = true) public synchronized static void setWatchedFilePaths(Map watchedFilePaths) { TestWatchedFiles.watchedFilePaths = watchedFilePaths; if (watchedFilesListener != null) { - watchedFilesListener.accept(watchedFilePaths); + watchedFilesListener.accept(watchedFilePaths, List.of()); } } + /** + * + * @param watchedFilesListener + * @deprecated Use {@link #setWatchedFilesListener(BiConsumer)} instead. + */ + @Deprecated(forRemoval = true) public synchronized static void setWatchedFilesListener(Consumer> watchedFilesListener) { - TestWatchedFiles.watchedFilesListener = watchedFilesListener; + TestWatchedFiles.watchedFilesListener = new BiConsumer, List, Boolean>>>() { + + @Override + public void accept(Map files, List, Boolean>> predicates) { + watchedFilesListener.accept(files); + } + }; if (watchedFilePaths != null) { watchedFilesListener.accept(watchedFilePaths); } } + + public synchronized static void setWatchedFilePaths(Map watchedFilePaths, + List, Boolean>> watchedFilePredicates) { + TestWatchedFiles.watchedFilePaths = watchedFilePaths; + TestWatchedFiles.watchedFilePredicates = watchedFilePredicates; + if (watchedFilesListener != null) { + watchedFilesListener.accept(watchedFilePaths, watchedFilePredicates); + } + } + + public synchronized static void setWatchedFilesListener( + BiConsumer, List, Boolean>>> watchedFilesListener) { + TestWatchedFiles.watchedFilesListener = watchedFilesListener; + if (watchedFilePaths != null) { + watchedFilesListener.accept(watchedFilePaths, watchedFilePredicates); + } + } } From 37d2a6c04b1675a8d63ae0d9aa72f24f2e26dfe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 3 May 2023 15:37:08 +0200 Subject: [PATCH 217/333] Workaround for unnecessary info logs in Hibernate ORM (HHH-16546) --- .../hibernate/orm/deployment/HibernateLogFilterBuildStep.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateLogFilterBuildStep.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateLogFilterBuildStep.java index 77e9b82f537f75..46a3844e672b8b 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateLogFilterBuildStep.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateLogFilterBuildStep.java @@ -30,5 +30,7 @@ void setupLogFilters(BuildProducer filters) { // Silence incubating settings warnings as we will use some for compatibility filters.produce(new LogCleanupFilterBuildItem("org.hibernate.orm.incubating", "HHH90006001")); + // https://hibernate.atlassian.net/browse/HHH-16546 + filters.produce(new LogCleanupFilterBuildItem("org.hibernate.tuple.entity.EntityMetamodel", "HHH000157")); } } From d4846546b44374c99f7f42bd9d997db1c40a44f2 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 3 May 2023 16:59:23 +0300 Subject: [PATCH 218/333] Allow for a larger timeout for the Logging-Gelf integration tests --- integration-tests/logging-gelf/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/logging-gelf/pom.xml b/integration-tests/logging-gelf/pom.xml index 0e5c6fd30b8b7b..70df7c5971d102 100644 --- a/integration-tests/logging-gelf/pom.xml +++ b/integration-tests/logging-gelf/pom.xml @@ -199,7 +199,7 @@ Logstash: default - cyan + magenta @@ -213,7 +213,7 @@ 200 - + elasticsearch From d3f29a2c6d628500a7e42f5b787d043bd046a2d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 19:56:41 +0000 Subject: [PATCH 219/333] Bump com.gradle.enterprise from 3.13 to 3.13.1 in /devtools/gradle Bumps com.gradle.enterprise from 3.13 to 3.13.1. --- updated-dependencies: - dependency-name: com.gradle.enterprise dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- devtools/gradle/settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/settings.gradle.kts b/devtools/gradle/settings.gradle.kts index 16b2d96281d92e..2495d30fe406ab 100644 --- a/devtools/gradle/settings.gradle.kts +++ b/devtools/gradle/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.enterprise") version "3.13" + id("com.gradle.enterprise") version "3.13.1" } gradleEnterprise { From 08846739a4d176cf82f63c2c21955b2ee6239ba6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 22:02:52 +0000 Subject: [PATCH 220/333] Bump junit-bom from 5.9.2 to 5.9.3 Bumps [junit-bom](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 527547b01be611..3e2fa65ccaa178 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -136,7 +136,7 @@ 11.5.8.0 1.2.6 5.3.0 - 5.9.2 + 5.9.3 1.5.0 14.0.8.Final 4.6.2.Final diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index a0432406050f3d..4fb305a4dbd489 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -52,7 +52,7 @@ 2.1.0 3.24.2 - 5.9.2 + 5.9.3 1.8.10 1.6.4 5.3.1 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 2e61981edcd3b4..c1a2bf5f4b7429 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -40,7 +40,7 @@ 11 11 11 - 5.9.2 + 5.9.3 3.24.2 3.1.1 1.6.1.Final diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 224bcc7f78e66b..aa129330d9e0a6 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -49,7 +49,7 @@ 4.0.1 3.1.1 1.12.12 - 5.9.2 + 5.9.3 3.8.8 3.24.2 3.5.0.Final From c35dbd01c1ac7655ebf0a5d73118a653fbd5be36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 22:06:51 +0000 Subject: [PATCH 221/333] Bump asciidoctorj from 2.5.7 to 2.5.8 Bumps [asciidoctorj](https://github.com/asciidoctor/asciidoctorj) from 2.5.7 to 2.5.8. - [Release notes](https://github.com/asciidoctor/asciidoctorj/releases) - [Changelog](https://github.com/asciidoctor/asciidoctorj/blob/v2.5.8/CHANGELOG.adoc) - [Commits](https://github.com/asciidoctor/asciidoctorj/compare/v2.5.7...v2.5.8) --- updated-dependencies: - dependency-name: org.asciidoctor:asciidoctorj dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 93e8289e907987..127f57e9c4fd58 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -37,7 +37,7 @@ 3.1.1 1.0.0 - 2.5.7 + 2.5.8 2.70.0 3.25.2 2.0.3.Final From 6a37afc3485761d7920fc89066a2f8b3e0caeed5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 22:16:34 +0000 Subject: [PATCH 222/333] Bump build-reporter-maven-extension from 2.3.1 to 3.0.0 Bumps [build-reporter-maven-extension](https://github.com/quarkusio/build-reporter) from 2.3.1 to 3.0.0. - [Release notes](https://github.com/quarkusio/build-reporter/releases) - [Commits](https://github.com/quarkusio/build-reporter/compare/2.3.1...3.0.0) --- updated-dependencies: - dependency-name: io.quarkus.bot:build-reporter-maven-extension dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 507adef1011aaa..3d69cf71482948 100644 --- a/pom.xml +++ b/pom.xml @@ -155,7 +155,7 @@ io.quarkus.bot build-reporter-maven-extension - 2.3.1 + 3.0.0 From ede5479c757aa7df2b5adeded3d576a45b24dcde Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Thu, 4 May 2023 09:20:26 +0200 Subject: [PATCH 223/333] Updates to Infinispan 14.0.9.Final --- 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 527547b01be611..253488d8f0913a 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -138,7 +138,7 @@ 5.3.0 5.9.2 1.5.0 - 14.0.8.Final + 14.0.9.Final 4.6.2.Final 3.1.5 4.1.90.Final From 5552e707352752f7900d04c4153be8f70642bdbf Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 3 May 2023 17:16:34 +0200 Subject: [PATCH 224/333] Upgrade Kotlin with quarkus update Fixes #32768 --- .../quarkus/gradle/tasks/QuarkusUpdate.java | 2 +- .../java/io/quarkus/maven/UpdateMojo.java | 2 +- .../devtools/commands/UpdateProject.java | 6 ++-- .../handlers/UpdateProjectCommandHandler.java | 24 +++++++++++++-- .../project/update/QuarkusUpdates.java | 15 ++++++++-- .../UpgradeGradlePluginOperation.java | 29 +++++++++++++++++++ 6 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java index c47654b99d7018..6e33b913d8d3de 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java @@ -139,7 +139,7 @@ public void logUpdates() { } final UpdateProject invoker = new UpdateProject(quarkusProject); - invoker.latestCatalog(targetCatalog); + invoker.targetCatalog(targetCatalog); if (rewriteUpdateRecipesVersion != null) { invoker.rewriteUpdateRecipesVersion(rewriteUpdateRecipesVersion); } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/UpdateMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/UpdateMojo.java index f2dcb342faf0b9..113ca35670cdb8 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/UpdateMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/UpdateMojo.java @@ -112,7 +112,7 @@ protected void processProjectState(QuarkusProject quarkusProject) throws MojoExe "Failed to resolve the recommended Quarkus extension catalog from the configured extension registries", e); } final UpdateProject invoker = new UpdateProject(quarkusProject); - invoker.latestCatalog(targetCatalog); + invoker.targetCatalog(targetCatalog); invoker.targetPlatformVersion(platformVersion); invoker.perModule(perModule); invoker.appModel(resolveApplicationModel()); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/UpdateProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/UpdateProject.java index 10cd97549305d2..92409437a023d6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/UpdateProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/UpdateProject.java @@ -19,7 +19,7 @@ public class UpdateProject { public static final String APP_MODEL = "quarkus.update-project.app-model"; - public static final String LATEST_CATALOG = "quarkus.update-project.latest-catalog"; + public static final String TARGET_CATALOG = "quarkus.update-project.target-catalog"; public static final String PER_MODULE = "quarkus.update-project.per-module"; public static final String NO_REWRITE = "quarkus.update-project.rewrite.disabled"; public static final String TARGET_PLATFORM_VERSION = "quarkus.update-project.target-platform-version"; @@ -38,8 +38,8 @@ public UpdateProject(final QuarkusProject quarkusProject, final MessageWriter me this.invocation = new QuarkusCommandInvocation(quarkusProject, new HashMap<>(), messageWriter); } - public UpdateProject latestCatalog(ExtensionCatalog latestCatalog) { - invocation.setValue(LATEST_CATALOG, requireNonNull(latestCatalog, "latestCatalog is required")); + public UpdateProject targetCatalog(ExtensionCatalog latestCatalog) { + invocation.setValue(TARGET_CATALOG, requireNonNull(latestCatalog, "targetCatalog is required")); return this; } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java index 76316518984791..b65580726004c6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/UpdateProjectCommandHandler.java @@ -51,7 +51,7 @@ public class UpdateProjectCommandHandler implements QuarkusCommandHandler { @Override public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { final ApplicationModel appModel = invocation.getValue(UpdateProject.APP_MODEL); - final ExtensionCatalog latestCatalog = invocation.getValue(UpdateProject.LATEST_CATALOG); + final ExtensionCatalog targetCatalog = invocation.getValue(UpdateProject.TARGET_CATALOG); final String targetPlatformVersion = invocation.getValue(UpdateProject.TARGET_PLATFORM_VERSION); final boolean perModule = invocation.getValue(UpdateProject.PER_MODULE, false); @@ -68,14 +68,18 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws invocation.log().info("Instructions to update this project from '%s' to '%s':", projectQuarkusPlatformBom.getVersion(), targetPlatformVersion); final QuarkusProject quarkusProject = invocation.getQuarkusProject(); - logUpdates(currentState, latestCatalog, false, perModule, quarkusProject.log()); + logUpdates(currentState, targetCatalog, false, perModule, quarkusProject.log()); final boolean noRewrite = invocation.getValue(UpdateProject.NO_REWRITE, false); if (!noRewrite) { final BuildTool buildTool = quarkusProject.getExtensionManager().getBuildTool(); + String kotlinVersion = getMetadata(targetCatalog, "project", "properties", "kotlin-version"); + QuarkusUpdates.ProjectUpdateRequest request = new QuarkusUpdates.ProjectUpdateRequest( buildTool, - projectQuarkusPlatformBom.getVersion(), targetPlatformVersion); + projectQuarkusPlatformBom.getVersion(), + targetPlatformVersion, + kotlinVersion); Path recipe = null; try { recipe = Files.createTempFile("quarkus-project-recipe-", ".yaml"); @@ -534,4 +538,18 @@ boolean isEmpty() { return extensionInfo.isEmpty(); } } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private T getMetadata(ExtensionCatalog catalog, String... path) { + Object currentValue = catalog.getMetadata(); + for (String pathElement : path) { + if (!(currentValue instanceof Map)) { + return null; + } + + currentValue = ((Map) currentValue).get(pathElement); + } + + return (T) currentValue; + } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java index 7f306cdace2038..46140c32f074a3 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdates.java @@ -8,6 +8,7 @@ import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.update.QuarkusUpdatesRepository.FetchResult; import io.quarkus.devtools.project.update.operations.UpdatePropertyOperation; +import io.quarkus.devtools.project.update.operations.UpgradeGradlePluginOperation; public final class QuarkusUpdates { @@ -28,11 +29,17 @@ public static FetchResult createRecipe(MessageWriter log, Path target, MavenArti case MAVEN: recipe.addOperation(new UpdatePropertyOperation("quarkus.platform.version", request.targetVersion)) .addOperation(new UpdatePropertyOperation("quarkus.version", request.targetVersion)); + if (request.kotlinVersion != null) { + recipe.addOperation(new UpdatePropertyOperation("kotlin.version", request.kotlinVersion)); + } break; case GRADLE: case GRADLE_KOTLIN_DSL: recipe.addOperation(new UpdatePropertyOperation("quarkusPlatformVersion", request.targetVersion)) .addOperation(new UpdatePropertyOperation("quarkusPluginVersion", request.targetVersion)); + if (request.kotlinVersion != null) { + recipe.addOperation(new UpgradeGradlePluginOperation("org.jetbrains.kotlin.*", request.kotlinVersion)); + } break; } @@ -48,15 +55,17 @@ public static class ProjectUpdateRequest { public BuildTool buildTool; public String currentVersion; public String targetVersion; + public String kotlinVersion; - public ProjectUpdateRequest(String currentVersion, String targetVersion) { - this(BuildTool.MAVEN, currentVersion, targetVersion); + public ProjectUpdateRequest(String currentVersion, String targetVersion, String kotlinVersion) { + this(BuildTool.MAVEN, currentVersion, targetVersion, kotlinVersion); } - public ProjectUpdateRequest(BuildTool buildTool, String currentVersion, String targetVersion) { + public ProjectUpdateRequest(BuildTool buildTool, String currentVersion, String targetVersion, String kotlinVersion) { this.buildTool = buildTool; this.currentVersion = currentVersion; this.targetVersion = targetVersion; + this.kotlinVersion = kotlinVersion; } } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java new file mode 100644 index 00000000000000..2ad0899601f879 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/operations/UpgradeGradlePluginOperation.java @@ -0,0 +1,29 @@ +package io.quarkus.devtools.project.update.operations; + +import java.util.Map; + +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.update.RewriteOperation; + +public class UpgradeGradlePluginOperation implements RewriteOperation { + + public String pluginIdPattern; + public String newVersion; + + public UpgradeGradlePluginOperation(String pluginIdPattern, String newVersion) { + this.pluginIdPattern = pluginIdPattern; + this.newVersion = newVersion; + } + + @Override + public Map toMap(BuildTool buildTool) { + switch (buildTool) { + case GRADLE: + return Map.of( + "org.openrewrite.gradle.plugins.UpgradePluginVersion", + Map.of("pluginIdPattern", pluginIdPattern, "newVersion", newVersion)); + default: + throw new UnsupportedOperationException("This operation is only supported for Gradle projects"); + } + } +} From 74c0063c9e5e9e0f07df00d5ba7bf2a2feb756ca Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 2 May 2023 16:10:31 +0200 Subject: [PATCH 225/333] Let `ProcessBuilder` do the IO redirection No need to create two threads for it. --- .../io/quarkus/test/common/LauncherUtil.java | 35 +++---------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java b/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java index e6fe3df54729da..70ba20715bb645 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java @@ -3,8 +3,6 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; @@ -53,10 +51,10 @@ public static Config installAndGetSomeConfig() { * Launches a process using the supplied arguments and makes sure the process's output is drained to standard out */ static Process launchProcess(List args) throws IOException { - Process process = Runtime.getRuntime().exec(args.toArray(new String[0])); - new Thread(new ProcessReader(process.getInputStream())).start(); - new Thread(new ProcessReader(process.getErrorStream())).start(); - return process; + return new ProcessBuilder(args) + .redirectErrorStream(true) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .start(); } /** @@ -309,31 +307,6 @@ private void unableToDetermineData(String errorMessage) { } } - /** - * Used to drain the input of a launched process - */ - private static class ProcessReader implements Runnable { - - private final InputStream inputStream; - - private ProcessReader(InputStream inputStream) { - this.inputStream = inputStream; - } - - @Override - public void run() { - byte[] b = new byte[100]; - int i; - try { - while ((i = inputStream.read(b)) > 0) { - System.out.print(new String(b, 0, i, StandardCharsets.UTF_8)); - } - } catch (IOException e) { - //ignore - } - } - } - private static class SimpleContext implements IntegrationTestStartedNotifier.Context { private final Path logFile; From 8f98a46bb8cda4216097a494d19ae12a9272964d Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Thu, 4 May 2023 16:56:04 -0400 Subject: [PATCH 226/333] make evergreen --- ...-to-quarkus-3.adoc => update-quarkus.adoc} | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) rename docs/src/main/asciidoc/{update-to-quarkus-3.adoc => update-quarkus.adoc} (62%) diff --git a/docs/src/main/asciidoc/update-to-quarkus-3.adoc b/docs/src/main/asciidoc/update-quarkus.adoc similarity index 62% rename from docs/src/main/asciidoc/update-to-quarkus-3.adoc rename to docs/src/main/asciidoc/update-quarkus.adoc index ddc5b8d29e2166..33354559caa45b 100644 --- a/docs/src/main/asciidoc/update-to-quarkus-3.adoc +++ b/docs/src/main/asciidoc/update-quarkus.adoc @@ -3,22 +3,22 @@ This document is maintained in the main Quarkus repository, and pull requests sh https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// [id="update-projects-to-quarkus-3-automatically-howto"] -= Update projects to Quarkus 3.x automatically += Update projects to the latest version of Quarkus include::_attributes.adoc[] :categories: core :extension-status: "experimental" -:summary: Update projects from Quarkus 2.x to Quarkus 3.x. +:summary: Update your projects to the latest version of Quarkus include::{includes}/extension-status.adoc[] -You can update your projects from Quarkus 2.x to Quarkus 3.x by running an update command. +You can update your Quarkus projects to the latest version by running an update command. -The update command primarily uses OpenRewrite recipes to automate updating most of your project's dependencies, source code, and documentation. These recipes cover many but not all of the items described in the link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0[Migration Guide 3.0]. +The update command primarily uses OpenRewrite recipes to automate updating most of your project's dependencies, source code, and documentation. These recipes cover many but not all of the items described in the link:https://github.com/quarkusio/quarkus/wiki/Migration-Guides[Migration Guides]. -After updating the project, if you do not find all the updates you expect, there are two possible reasons: +After updating a project, if you do not find all the updates you expect, there are two possible reasons: - The recipe might not cover an item in your project. -- Your project might use an extension that does not support Quarkus 3 yet. +- Your project might use an extension that does not support the latest version of Quarkus yet. In either case, https://github.com/quarkusio/quarkus/issues[let us know by filing an issue] so we can improve the update command. @@ -30,27 +30,36 @@ The following update command only covers a few items in this quick reference. == Prerequisites +* A project based on Quarkus version 2.13 or later. :prerequisites-time: 30 minutes include::{includes}/prerequisites.adoc[] == Procedure . Use your version control system to create a working branch for your project or projects. -. Optional: To use the Quarkus CLI in the next step, link:https://quarkus.io/guides/cli-tooling#installing-the-cli[install version 3 of the Quarkus CLI]. Use `quarkus -v` to verify the version number. -. Update the project: + +. Optional: To use the Quarkus CLI in the next step, link:https://quarkus.io/guides/cli-tooling#installing-the-cli[install the latest version of the Quarkus CLI]. Use `quarkus -v` to verify the version number. + +. Change to the project directory and update the project: + [source, bash, subs=attributes+, role="primary asciidoc-tabs-sync-cli"] .CLI ---- -quarkus update --stream=3.0 +quarkus update <1> ---- +<1> Updates to the latest stream by default. To specify a stream, use the `stream` option; for example, `--stream=3.0`. + [source, bash, subs=attributes+, role="secondary asciidoc-tabs-sync-maven"] .Maven ---- -./mvnw io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:update -N -Dstream=3.0 +./mvnw io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:update -N <1> ---- +<1> Updates to the latest stream by default. To specify a stream, use the `Dstream` option; for example, `-Dstream=3.0`. + . Review the output from the update command for potential instructions and, if needed, perform the indicated tasks. + . Review all the changes using a diff tool. -. Review link:https://github.com/quarkusio/quarkus/wiki/Migration-Guide-3.0[the migration guide] for any items not covered by the upgrade command and perform additional steps, if needed. + +. Review the https://github.com/quarkusio/quarkus/wiki/Migration-Guides[Migration Guides] for any items not covered by the upgrade command and perform additional steps, if needed. + . Verify that the project builds without errors and that the application passes all tests and works as expected before releasing it to production. From d5d78d8df45b5c7a1e3f82ba554e6f89856aa0d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 22:05:51 +0000 Subject: [PATCH 227/333] Bump quarkus-platform-bom-maven-plugin from 0.0.81 to 0.0.88 Bumps [quarkus-platform-bom-maven-plugin](https://github.com/quarkusio/quarkus-platform-bom-generator) from 0.0.81 to 0.0.88. - [Release notes](https://github.com/quarkusio/quarkus-platform-bom-generator/releases) - [Commits](https://github.com/quarkusio/quarkus-platform-bom-generator/compare/0.0.81...0.0.88) --- updated-dependencies: - dependency-name: io.quarkus:quarkus-platform-bom-maven-plugin 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 3d69cf71482948..2f5f1987fe5c6f 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 1.6.8 4.4.1 - 0.0.81 + 0.0.88 false false From 3b9163a158981e85e743a72ef88680fe3cdcc573 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 22:15:09 +0000 Subject: [PATCH 228/333] Bump jackson-bom from 2.14.2 to 2.15.0 Bumps [jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.14.2 to 2.15.0. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.14.2...jackson-bom-2.15.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 3e2fa65ccaa178..df524ed25fb1d1 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -92,7 +92,7 @@ 22.3.0 ${graal-sdk.version} 1.6.1.Final - 2.14.2 + 2.15.0 1.0.0.Final 3.12.0 1.15 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index 0318bb2a0bff80..e6900ac1933e1d 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -42,7 +42,7 @@ 3.0.0 1.6.8 3.8.1 - 2.14.2 + 2.15.0 5.9.2 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index aa129330d9e0a6..502d4fd8e71371 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -66,7 +66,7 @@ 4.4.1 5.3.0 1.0.0.Final - 2.14.2 + 2.15.0 2.1.0 3.0.2 3.0.3 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index b1a23a3a501ff0..5352749dc19f18 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -53,7 +53,7 @@ 3.24.2 - 2.14.2 + 2.15.0 4.0.1 5.9.2 1.23.0 From baa7c0a114b67482d502a73080bc8b9fa46fe401 Mon Sep 17 00:00:00 2001 From: Jose Date: Fri, 5 May 2023 08:12:34 +0200 Subject: [PATCH 229/333] Fix tiny typo in Stork Kubernetes documentation --- docs/src/main/asciidoc/stork-kubernetes.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/stork-kubernetes.adoc b/docs/src/main/asciidoc/stork-kubernetes.adoc index ba587d054a9e16..ad4e619f8e0a24 100644 --- a/docs/src/main/asciidoc/stork-kubernetes.adoc +++ b/docs/src/main/asciidoc/stork-kubernetes.adoc @@ -287,7 +287,7 @@ There are a few interesting parts in this listing: <1> The Kubernetes Service resource, `color-service`, that Stork will discover. <2> The Red and Blue service instances behind the `color-service` Kubernetes service. -<3> A Kubernetes Ingress resource making the `color-service` accessible from the outside of the cluster at the `color-service.127.0.0.1.nip.io` url. Not that the Ingress is not needed for Stork however, it helps to check that the architecture is in place. +<3> A Kubernetes Ingress resource making the `color-service` accessible from the outside of the cluster at the `color-service.127.0.0.1.nip.io` url. Note that the Ingress is not needed for Stork however, it helps to check that the architecture is in place. Create a file named `kubernetes-setup.yml` with the content above at the root of the project and run the following commands to deploy all the resources in the Kubernetes cluster. Don't forget to create a dedicated namespace: From fcd28088b154c6bc5c1348e7c018409f4bdbf60d Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 5 May 2023 17:00:45 +1000 Subject: [PATCH 230/333] Fix Hibernate Reactive dev mode The session is being cached in a static so after dev mode restart users get a closed session. --- .../deployment/PanacheJpaCommonResourceProcessor.java | 7 +++++++ .../common/runtime/PanacheHibernateRecorder.java | 10 ++++++++++ .../panache/common/runtime/SessionOperations.java | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java index f2ab103f76e41f..8faf7a2510d688 100644 --- a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java +++ b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/common/deployment/PanacheJpaCommonResourceProcessor.java @@ -41,6 +41,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.util.JandexUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.hibernate.orm.deployment.HibernateOrmEnabled; @@ -208,6 +209,12 @@ void buildNamedQueryMap(List namedQueryEn panacheHibernateRecorder.setNamedQueryMap(namedQueryMap); } + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public void shutdown(ShutdownContextBuildItem shutdownContextBuildItem, PanacheHibernateRecorder panacheHibernateRecorder) { + panacheHibernateRecorder.clear(shutdownContextBuildItem); + } + private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set namedQueries) { ClassInfo classInfo = index.getComputingIndex().getClassByName(name); if (classInfo == null) { diff --git a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/PanacheHibernateRecorder.java b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/PanacheHibernateRecorder.java index 3a1083a57e0dad..7774ff963487de 100644 --- a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/PanacheHibernateRecorder.java +++ b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/PanacheHibernateRecorder.java @@ -3,6 +3,7 @@ import java.util.Map; import java.util.Set; +import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; @Recorder @@ -10,4 +11,13 @@ public class PanacheHibernateRecorder { public void setNamedQueryMap(Map> namedQueryMap) { NamedQueryUtil.setNamedQueryMap(namedQueryMap); } + + public void clear(ShutdownContext shutdownContext) { + shutdownContext.addShutdownTask(new Runnable() { + @Override + public void run() { + SessionOperations.clear(); + } + }); + } } diff --git a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/SessionOperations.java b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/SessionOperations.java index 7f5655acf0c599..99c15a8691d118 100644 --- a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/SessionOperations.java +++ b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/SessionOperations.java @@ -205,4 +205,8 @@ static Mutiny.SessionFactory getSessionFactory() { return SESSION_FACTORY.get(); } + static void clear() { + SESSION_FACTORY.clear(); + SESSION_KEY.clear(); + } } From c5eb7c357323b7e1854e77d9fffde5d7dba0ec80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 07:30:21 +0000 Subject: [PATCH 231/333] Bump junit-jupiter from 5.9.2 to 5.9.3 Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 7203a74940ddae..730366f50975ae 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -48,7 +48,7 @@ 3.24.2 0.9.5 3.5.0.Final - 5.9.2 + 5.9.3 3.9.1 0.3.5 3.7.1 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index e6900ac1933e1d..fb63ce98d42e7c 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -43,7 +43,7 @@ 1.6.8 3.8.1 2.15.0 - 5.9.2 + 5.9.3 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 5352749dc19f18..0af8f7ff8bb346 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -55,7 +55,7 @@ 3.24.2 2.15.0 4.0.1 - 5.9.2 + 5.9.3 1.23.0 3.5.0.Final 3.9.1 From 28bd3abfebc9211f89bde30de41db1d524924801 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Fri, 5 May 2023 11:41:43 +0300 Subject: [PATCH 232/333] Promote mandrel as the default builder image for native executables Closes: #30745 --- docs/src/main/asciidoc/amazon-lambda.adoc | 2 +- .../main/asciidoc/building-native-image.adoc | 140 +++++++++--------- docs/src/main/asciidoc/gradle-tooling.adoc | 14 +- docs/src/main/asciidoc/maven-tooling.adoc | 4 +- docs/src/main/asciidoc/platform.adoc | 2 +- 5 files changed, 84 insertions(+), 78 deletions(-) diff --git a/docs/src/main/asciidoc/amazon-lambda.adoc b/docs/src/main/asciidoc/amazon-lambda.adoc index df45427df1f81b..f9a70a91d3354d 100644 --- a/docs/src/main/asciidoc/amazon-lambda.adoc +++ b/docs/src/main/asciidoc/amazon-lambda.adoc @@ -577,7 +577,7 @@ To extract the required ssl, you must start up a Docker container in the backgro First, let's start the GraalVM container, noting the container id output. [source,bash,subs=attributes+] ---- -docker run -it -d --entrypoint bash quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} +docker run -it -d --entrypoint bash quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} # This will output a container id, like 6304eea6179522aff69acb38eca90bedfd4b970a5475aa37ccda3585bc2abdde # Note this value as we will need it for the commands below diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index c087ba0617689b..a0a652df7085e9 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -16,7 +16,39 @@ This guide covers: This guide takes as input the application developed in the xref:getting-started.adoc[Getting Started Guide]. -== GraalVM +== Prerequisites + +:prerequisites-docker: +:prerequisites-graalvm-mandatory: +include::{includes}/prerequisites.adoc[] +* A xref:configuring-c-development[working C development environment] +* The code of the application developed in the xref:getting-started.adoc[Getting Started Guide]. + +.Supporting native compilation in C +[[configuring-c-development]] +[NOTE] +==== +What does having a working C developer environment mean? + +* On Linux, you will need GCC, and the glibc and zlib headers. Examples for common distributions: ++ +[source,bash] +---- +# dnf (rpm-based) +sudo dnf install gcc glibc-devel zlib-devel libstdc++-static +# Debian-based distributions: +sudo apt-get install build-essential libz-dev zlib1g-dev +---- +* XCode provides the required dependencies on macOS: ++ +[source,bash] +---- +xcode-select --install +---- +* On Windows, you will need to install the https://aka.ms/vs/15/release/vs_buildtools.exe[Visual Studio 2017 Visual C++ Build Tools] +==== + +=== Background Building a native executable requires using a distribution of GraalVM. There are three distributions: @@ -49,47 +81,23 @@ Building native executables directly on bare metal Linux or Windows is possible, with details available in the https://github.com/graalvm/mandrel/blob/default/README.md[Mandrel README] and https://github.com/graalvm/mandrel/releases[Mandrel releases]. -== Prerequisites -:prerequisites-docker: -:prerequisites-graalvm-mandatory: -include::{includes}/prerequisites.adoc[] -* A xref:configuring-c-development[working C development environment] -* The code of the application developed in the xref:getting-started.adoc[Getting Started Guide]. +[[configuring-graalvm]] +=== Configuring GraalVM -.Supporting native compilation in C -[[configuring-c-development]] -[NOTE] +[TIP] ==== -What does having a working C developer environment mean? - -* On Linux, you will need GCC, and the glibc and zlib headers. Examples for common distributions: -+ -[source,bash] ----- -# dnf (rpm-based) -sudo dnf install gcc glibc-devel zlib-devel libstdc++-static -# Debian-based distributions: -sudo apt-get install build-essential libz-dev zlib1g-dev ----- -* XCode provides the required dependencies on macOS: -+ -[source,bash] ----- -xcode-select --install ----- -* On Windows, you will need to install the https://aka.ms/vs/15/release/vs_buildtools.exe[Visual Studio 2017 Visual C++ Build Tools] +This step is only required for generating native executables targeting non-Linux operating systems. +For generating native executables targeting Linux, you can optionally skip this section and <<#container-runtime,use a builder image>> instead. ==== -[[configuring-graalvm]] -=== Configuring GraalVM - [TIP] ==== -If you cannot install GraalVM, you can use a multi-stage Docker build to run Maven inside a Docker container that embeds GraalVM. There is an explanation of how to do this at the end of this guide. +If you cannot install GraalVM, you can use a multi-stage Docker build to run Maven inside a Docker container that embeds GraalVM. +There is an explanation of how to do this at <<#multistage-docker,the end of this guide>>. ==== -Version {graalvm-version} is required. Using the community edition is enough. +Version {graalvm-version} is required. 1. Install GraalVM if you haven't already. You have a few options for this: ** Download the appropriate archive from or , and unpack it like you would any other JDK. @@ -98,7 +106,7 @@ Version {graalvm-version} is required. Using the community edition is enough. + [source,bash] ---- -export GRAALVM_HOME=$HOME/Development/graalvm/ +export GRAALVM_HOME=$HOME/Development/mandrel/ ---- + On macOS (not supported by Mandrel), point the variable to the `Home` sub-directory: @@ -114,7 +122,7 @@ On Windows, you will have to go through the Control Panel to set your environmen ==== Installing via scoop will do this for you. ==== -3. (Only for Oracle GraalVM CE/EE) Install the `native-image` tool using `gu install`: +3. (Only for GraalVM CE/EE) Install the `native-image` tool using `gu install`: + [source,bash] ---- @@ -242,29 +250,30 @@ Compiling fully static binaries is done by statically linking https://musl.libc. Producing a native executable can lead to a few issues, and so it's also a good idea to run some tests against the application running in the native file. The reasoning is explained in the link:getting-started-testing#quarkus-integration-test[Testing Guide]. To see the `GreetingResourceIT` run against the native executable, use `./mvnw verify -Pnative`: -[source,shell] +[source,shell,subs=attributes+] ---- $ ./mvnw verify -Pnative ... -[getting-started-1.0.0-SNAPSHOT-runner:18820] universe: 587.26 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] (parse): 2,247.59 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] (inline): 1,985.70 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] (compile): 14,922.77 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] compile: 20,361.28 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] image: 2,228.30 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] write: 364.35 ms -[getting-started-1.0.0-SNAPSHOT-runner:18820] [total]: 52,777.76 ms +Finished generating 'getting-started-1.0.0-SNAPSHOT-runner' in 22.0s. +[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildRunner] docker run --env LANG=C --rm --user 1000:1000 -v /home/zakkak/code/quarkus-quickstarts/getting-started/target/getting-started-1.0.0-SNAPSHOT-native-image-source-jar:/project:z --entrypoint /bin/bash quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} -c objcopy --strip-debug getting-started-1.0.0-SNAPSHOT-runner +[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 70686ms [INFO] -[INFO] --- maven-failsafe-plugin:2.22.1:integration-test (default) @ getting-started --- +[INFO] --- maven-failsafe-plugin:3.0.0-M7:integration-test (default) @ getting-started --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- -[INFO] Running org.acme.quickstart.GreetingResourceIT -Executing [/data/home/gsmet/git/quarkus-quickstarts/getting-started/target/getting-started-1.0.0-SNAPSHOT-runner, -Dquarkus.http.port=8081, -Dtest.url=http://localhost:8081, -Dquarkus.log.file.path=build/quarkus.log] -2019-04-15 11:33:20,348 INFO [io.quarkus] (main) Quarkus 999-SNAPSHOT started in 0.002s. Listening on: http://[::]:8081 -2019-04-15 11:33:20,348 INFO [io.quarkus] (main) Installed features: [cdi, resteasy-reactive] -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.387 s - in org.acme.quickstart.GreetingResourceIT +[INFO] Running org.acme.getting.started.GreetingResourceIT +Executing "/home/zakkak/code/quarkus-quickstarts/getting-started/target/getting-started-1.0.0-SNAPSHOT-runner -Dquarkus.http.port=8081 -Dquarkus.http.ssl-port=8444 -Dtest.url=http://localhost:8081 -Dquarkus.log.file.path=/home/zakkak/code/quarkus-quickstarts/getting-started/target/quarkus.log -Dquarkus.log.file.enable=true -Dquarkus.log.category."io.quarkus".level=INFO" +__ ____ __ _____ ___ __ ____ ______ + --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ + -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ +--\___\_\____/_/ |_/_/|_/_/|_|\____/___/ +2023-05-05 10:55:52,068 INFO [io.quarkus] (main) getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 3.0.2.Final) started in 0.009s. Listening on: http://0.0.0.0:8081 +2023-05-05 10:55:52,069 INFO [io.quarkus] (main) Profile prod activated. +2023-05-05 10:55:52,069 INFO [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx] +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.99 s - in org.acme.getting.started.GreetingResourceIT ... ---- @@ -406,7 +415,7 @@ For podman: include::{includes}/devtools/build-native-container-parameters.adoc[] :!build-additional-parameters: -These are normal Quarkus config properties, so if you always want to build in a container +These are regular Quarkus config properties, so if you always want to build in a container it is recommended you add these to your `application.properties` in order to avoid specifying them every time. ==== @@ -425,9 +434,9 @@ The reason for this is that the local build driver invoked through `-Dquarkus.na [TIP] ==== -Building with Mandrel requires a custom builder image parameter to be passed additionally: +Building with GraalVM instead of Mandrel requires a custom builder image parameter to be passed additionally: -:build-additional-parameters: -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} +:build-additional-parameters: -Dquarkus.native.builder-image=graalvm include::{includes}/devtools/build-native-container-parameters.adoc[] :!build-additional-parameters: @@ -562,7 +571,7 @@ Sample Dockerfile for building with Maven: [source,dockerfile,subs=attributes+] ---- ## Stage 1 : build with maven builder image with native capabilities -FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} AS build +FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} AS build COPY --chown=quarkus:quarkus mvnw /code/mvnw COPY --chown=quarkus:quarkus .mvn /code/.mvn COPY --chown=quarkus:quarkus pom.xml /code/ @@ -600,7 +609,7 @@ Sample Dockerfile for building with Gradle: [source,dockerfile,subs=attributes+] ---- ## Stage 1 : build with maven builder image with native capabilities -FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} AS build +FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} AS build USER root RUN microdnf install findutils COPY --chown=quarkus:quarkus gradlew /code/gradlew @@ -645,12 +654,7 @@ Please see xref:native-and-ssl.adoc#working-with-containers[our Using SSL With N [NOTE,subs=attributes+] ==== -To use Mandrel instead of GraalVM CE, update the `FROM` clause to: `FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} AS build`. -==== - -[NOTE] -==== -Starting with 22.3, Mandrel does not provide a `-java11` variant anymore. Use the `-java17` image instead. +To use GraalVM CE instead of Mandrel, update the `FROM` clause to: `FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} AS build`. ==== === Using a Distroless base image @@ -691,7 +695,7 @@ Sample multistage Dockerfile for building an image from `scratch`: [source,dockerfile,subs=attributes+] ---- ## Stage 1 : build with maven builder image with native capabilities -FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} AS build +FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} AS build USER root RUN microdnf install make gcc COPY --chown=quarkus:quarkus mvnw /code/mvnw @@ -813,10 +817,12 @@ Depending on what the final desired output of the CI/CD pipeline is, the generat == Debugging native executable -Starting with Oracle GraalVM 20.2 or Mandrel 20.1, -debug symbols for native executables can be generated for Linux environments -(Windows support is still under development, macOS is not supported). -These symbols can be used to debug native executables with tools such as `gdb`. +Native executables can be debugged using tools such as `gdb`. +For this to be possible native executables need to be generated with debug symbols. + +[NOTE] +Debug symbol generation is only supported on Linux. +Windows support is still under development, while macOS is not supported. To generate debug symbols, add `-Dquarkus.native.debug.enabled=true` flag when generating the native executable. @@ -825,7 +831,7 @@ You will find the debug symbols for the native executable in a `.debug` file nex [NOTE] ==== The generation of the `.debug` file depends on `objcopy`. -On common Linux distributions you will need to install the `binutils` package: +As a result, when using a local GraalVM installation on common Linux distributions you will need to install the `binutils` package: [source,bash] ---- diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index 097d49343181cc..1ee21accf7085c 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -410,7 +410,7 @@ Once executed, you will be able to safely run quarkus task with `--offline` flag Native executables make Quarkus applications ideal for containers and serverless workloads. -Make sure to have `GRAALVM_HOME` configured and pointing to the latest release of GraalVM version {graalvm-version} (Make sure to use a Java 11 version of GraalVM). +Make sure to have `GRAALVM_HOME` configured and pointing to the latest release of GraalVM version {graalvm-version}. Create a native executable using: @@ -429,13 +429,13 @@ Configuring the `quarkusBuild` task can be done as following: quarkusBuild { nativeArgs { containerBuild = true <1> - builderImage = "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor}" <2> + builderImage = "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor}" <2> } } ---- <1> Set `quarkus.native.container-build` property to `true` -<2> Set `quarkus.native.builder-image` property to `quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor}` +<2> Set `quarkus.native.builder-image` property to `quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor}` **** [role="secondary asciidoc-tabs-sync-kotlin"] @@ -446,13 +446,13 @@ quarkusBuild { tasks.quarkusBuild { nativeArgs { "container-build" to true <1> - "builder-image" to "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor}" <2> + "builder-image" to "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor}" <2> } } ---- <1> Set `quarkus.native.container-build` property to `true` -<2> Set `quarkus.native.builder-image` property to `quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor}` +<2> Set `quarkus.native.builder-image` property to `quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor}` **** [WARNING] @@ -475,12 +475,12 @@ Note that in this case the build itself runs in a Docker container too, so you d [TIP] ==== -By default, the native executable will be generated using the `quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor}` Docker image. +By default, the native executable will be generated using the `quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor}` Docker image. If you want to build a native executable with a different Docker image (for instance to use a different GraalVM version), use the `-Dquarkus.native.builder-image=` build argument. -The list of the available Docker images can be found on https://quay.io/repository/quarkus/ubi-quarkus-graalvmce-builder-image?tab=tags[quay.io]. +The list of the available Docker images can be found on https://quay.io/repository/quarkus/ubi-quarkus-mandrel-builder-image?tab=tags[quay.io]. Be aware that a given Quarkus version might not be compatible with all the images available. ==== diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index c5d176c83bd30c..50cfb25486352f 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -448,12 +448,12 @@ Note that in this case the build itself runs in a Docker container too, so you d [TIP] ==== -By default, the native executable will be generated using the `quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor}` Docker image. +By default, the native executable will be generated using the `quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor}` Docker image. If you want to build a native executable with a different Docker image (for instance to use a different GraalVM version), use the `-Dquarkus.native.builder-image=` build argument. -The list of the available Docker images can be found on https://quay.io/repository/quarkus/ubi-quarkus-graalvmce-builder-image?tab=tags[quay.io]. +The list of the available Docker images can be found on https://quay.io/repository/quarkus/ubi-quarkus-mandrel-builder-image?tab=tags[quay.io]. Be aware that a given Quarkus version might not be compatible with all the images available. ==== diff --git a/docs/src/main/asciidoc/platform.adoc b/docs/src/main/asciidoc/platform.adoc index 3ce6edb4ee5b90..75916d6ed8ed6f 100644 --- a/docs/src/main/asciidoc/platform.adoc +++ b/docs/src/main/asciidoc/platform.adoc @@ -128,7 +128,7 @@ A platform properties file for the example above would contain: [source,text,subs=attributes+] ---- -platform.quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:{graalvm-flavor} +platform.quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} ---- There is also a Maven plugin goal that validates the platform properties content and its artifact coordinates and also checks whether the platform properties artifact is present in the platform's BOM. Here is a sample plugin configuration: From 464e0f11332252083b918f03a912f017fbee315f Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Fri, 5 May 2023 12:07:28 +0200 Subject: [PATCH 233/333] refactor: remove redundant modifiers for interface fields from NativeConfig.java --- .../src/main/java/io/quarkus/deployment/pkg/NativeConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index af84606ab813bf..c51f7915711eb3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -19,8 +19,8 @@ @ConfigMapping(prefix = "quarkus.native") public interface NativeConfig { - public static final String DEFAULT_GRAALVM_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17"; - public static final String DEFAULT_MANDREL_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17"; + String DEFAULT_GRAALVM_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17"; + String DEFAULT_MANDREL_BUILDER_IMAGE = "quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17"; /** * Comma-separated, additional arguments to pass to the build process. From 1f7cfc00bf22cd2e27000ebfe8386a39aa7bcc21 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 5 May 2023 11:26:15 +0200 Subject: [PATCH 234/333] Fix https://github.com/quarkusio/quarkus/issues/33106. In addition to Multi, also handle the Flow.Publisher case. --- .../deployment/ResteasyReactiveProcessor.java | 2 ++ .../it/resteasy/mutiny/MutinyResource.java | 9 ++++++ .../it/resteasy/mutiny/MutinyTest.java | 32 +++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 37e8f0c6ca81db..a758d2a5201485 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -6,6 +6,7 @@ import static java.util.stream.Collectors.toList; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.DATE_FORMAT; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUBLISHER; import java.io.File; import java.io.IOException; @@ -552,6 +553,7 @@ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { if (paramsRequireReflection || MULTI.toString().equals(entry.getResourceMethod().getSimpleReturnType()) || + PUBLISHER.toString().equals(entry.getResourceMethod().getSimpleReturnType()) || filtersAccessResourceMethod(resourceInterceptorsBuildItem.getResourceInterceptors()) || entry.additionalRegisterClassForReflectionCheck()) { minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer); diff --git a/integration-tests/resteasy-mutiny/src/main/java/io/quarkus/it/resteasy/mutiny/MutinyResource.java b/integration-tests/resteasy-mutiny/src/main/java/io/quarkus/it/resteasy/mutiny/MutinyResource.java index ba4d9996cf52d5..fdbc1057153d59 100644 --- a/integration-tests/resteasy-mutiny/src/main/java/io/quarkus/it/resteasy/mutiny/MutinyResource.java +++ b/integration-tests/resteasy-mutiny/src/main/java/io/quarkus/it/resteasy/mutiny/MutinyResource.java @@ -1,6 +1,7 @@ package io.quarkus.it.resteasy.mutiny; import java.io.IOException; +import java.util.concurrent.Flow; import jakarta.inject.Inject; import jakarta.ws.rs.GET; @@ -66,6 +67,14 @@ public Multi sse() { return service.getMorePets(); } + @GET + @Path("/pets/flow") + @Produces(MediaType.SERVER_SENT_EVENTS) + @RestStreamElementType(MediaType.APPLICATION_JSON) + public Flow.Publisher sseFlow() { + return service.getMorePets(); + } + @Inject @RestClient MyRestService client; diff --git a/integration-tests/resteasy-mutiny/src/test/java/io/quarkus/it/resteasy/mutiny/MutinyTest.java b/integration-tests/resteasy-mutiny/src/test/java/io/quarkus/it/resteasy/mutiny/MutinyTest.java index 7bffac78fde117..40fe297fb158f2 100644 --- a/integration-tests/resteasy-mutiny/src/test/java/io/quarkus/it/resteasy/mutiny/MutinyTest.java +++ b/integration-tests/resteasy-mutiny/src/test/java/io/quarkus/it/resteasy/mutiny/MutinyTest.java @@ -111,6 +111,38 @@ public void accept(UniEmitter> uniEmitter) { } } + @Test + public void testSSEWithFlowPublisher() { + Client client = ClientBuilder.newClient(); + WebTarget target = client.target("http://localhost:" + RestAssured.port + "/mutiny/pets/flow"); + try (SseEventSource eventSource = SseEventSource.target(target).build()) { + Uni> petList = Uni.createFrom().emitter(new Consumer>>() { + @Override + public void accept(UniEmitter> uniEmitter) { + List pets = new CopyOnWriteArrayList<>(); + eventSource.register(event -> { + Pet pet = event.readData(Pet.class, MediaType.APPLICATION_JSON_TYPE); + pets.add(pet); + if (pets.size() == 5) { + uniEmitter.complete(pets); + } + }, ex -> { + uniEmitter.fail(new IllegalStateException("SSE failure", ex)); + }); + eventSource.open(); + + } + }); + List pets = petList.await().atMost(Duration.ofMinutes(1)); + Assertions.assertEquals(5, pets.size()); + Assertions.assertEquals("neo", pets.get(0).getName()); + Assertions.assertEquals("indy", pets.get(1).getName()); + Assertions.assertEquals("plume", pets.get(2).getName()); + Assertions.assertEquals("titi", pets.get(3).getName()); + Assertions.assertEquals("rex", pets.get(4).getName()); + } + } + @Test public void testClientReturningUni() { get("/mutiny/client") From 6932ede0319ce9ed19c0ee29aec1365f1e1c6629 Mon Sep 17 00:00:00 2001 From: Marco Collovati Date: Fri, 5 May 2023 16:42:43 +0200 Subject: [PATCH 235/333] test: fixed order of actual and expectation in SecurityTestUtils assertion The order of expectation and actual object are inverted in `SecurityTestUtils.assertSuccess`, providing misleading error messages in case of failure of the test. This change fixes the order of the parameters in the assertion. --- .../java/io/quarkus/security/test/utils/SecurityTestUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/SecurityTestUtils.java b/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/SecurityTestUtils.java index a319764bf12be3..e0fa8642b5389e 100644 --- a/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/SecurityTestUtils.java +++ b/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/SecurityTestUtils.java @@ -17,7 +17,7 @@ public class SecurityTestUtils { public static void assertSuccess(Supplier action, T expectedResult, AuthData... auth) { for (AuthData authData : auth) { setUpAuth(authData); - Assertions.assertEquals(action.get(), expectedResult); + Assertions.assertEquals(expectedResult, action.get()); } } From fd1dec1ed16f96a29ebe39a03cfbe40a399e596c Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 5 May 2023 19:13:42 +0200 Subject: [PATCH 236/333] Also provide the gRPC reflection service 1.0.alpha. --- .../asciidoc/grpc-service-implementation.adoc | 2 + .../client/MultipleStubsInjectionTest.java | 14 +- .../grpc/server/GrpcReflectionAlphaTest.java | 296 ++++++++++++++++++ .../FailingInInterceptorTest.java | 6 +- .../resources/reflection-config.properties | 6 +- .../grpc/runtime/GrpcServerRecorder.java | 17 +- .../runtime/config/GrpcConfiguration.java | 6 +- ...nService.java => ReflectionServiceV1.java} | 16 +- .../reflection/ReflectionServiceV1alpha.java | 180 +++++++++++ .../main/proto/reflection/v1alpha/README.md | 2 + .../proto/reflection/v1alpha/reflection.proto | 136 ++++++++ 11 files changed, 663 insertions(+), 18 deletions(-) create mode 100644 extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/GrpcReflectionAlphaTest.java rename extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/{ReflectionService.java => ReflectionServiceV1.java} (91%) create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1alpha.java create mode 100644 extensions/grpc/stubs/src/main/proto/reflection/v1alpha/README.md create mode 100644 extensions/grpc/stubs/src/main/proto/reflection/v1alpha/reflection.proto diff --git a/docs/src/main/asciidoc/grpc-service-implementation.adoc b/docs/src/main/asciidoc/grpc-service-implementation.adoc index 3f9c3bf7e20b24..f3dd6fa72a6fa9 100644 --- a/docs/src/main/asciidoc/grpc-service-implementation.adoc +++ b/docs/src/main/asciidoc/grpc-service-implementation.adoc @@ -203,6 +203,8 @@ This service allows tools like https://github.com/fullstorydev/grpcurl[grpcurl] The reflection service is enabled by default in _dev_ mode. In test or production mode, you need to enable it explicitly by setting `quarkus.grpc.server.enable-reflection-service` to `true`. +NOTE: Quarkus exposes both the reflection service `v1` and `v1alpha`. + == Scaling By default, quarkus-grpc starts a single gRPC server running on a single event loop. diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MultipleStubsInjectionTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MultipleStubsInjectionTest.java index 25b4569c7faf1a..fff2045986c74a 100644 --- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MultipleStubsInjectionTest.java +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MultipleStubsInjectionTest.java @@ -13,8 +13,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.grpc.Channel; -import io.grpc.examples.goodbyeworld.*; -import io.grpc.examples.helloworld.*; +import io.grpc.examples.goodbyeworld.FarewellGrpc; +import io.grpc.examples.goodbyeworld.GoodbyeReply; +import io.grpc.examples.goodbyeworld.GoodbyeReplyOrBuilder; +import io.grpc.examples.goodbyeworld.GoodbyeRequest; +import io.grpc.examples.goodbyeworld.GoodbyeRequestOrBuilder; +import io.grpc.examples.goodbyeworld.MutinyFarewellGrpc; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloReplyOrBuilder; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.helloworld.HelloRequestOrBuilder; +import io.grpc.examples.helloworld.MutinyGreeterGrpc; import io.quarkus.grpc.GrpcClient; import io.quarkus.grpc.server.services.GoodbyeService; import io.quarkus.grpc.server.services.HelloService; diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/GrpcReflectionAlphaTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/GrpcReflectionAlphaTest.java new file mode 100644 index 00000000000000..6fc1f65a1c7ee1 --- /dev/null +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/GrpcReflectionAlphaTest.java @@ -0,0 +1,296 @@ +package io.quarkus.grpc.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Flow; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.google.protobuf.ByteString; + +import grpc.health.v1.HealthGrpc; +import grpc.reflection.v1alpha.MutinyServerReflectionGrpc; +import grpc.reflection.v1alpha.Reflection; +import io.grpc.Status; +import io.grpc.reflection.testing.MutinyReflectableServiceGrpc; +import io.grpc.reflection.testing.ReflectionTestDepthThreeProto; +import io.grpc.reflection.testing.ReflectionTestDepthTwoProto; +import io.grpc.reflection.testing.ReflectionTestProto; +import io.grpc.reflection.testing.Reply; +import io.grpc.reflection.testing.Request; +import io.quarkus.grpc.GrpcClient; +import io.quarkus.grpc.GrpcService; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor; + +/** + * Check the behavior of the reflection service (v1alpha) + */ +public class GrpcReflectionAlphaTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addPackage(HealthGrpc.class.getPackage()) + .addPackage(MutinyReflectableServiceGrpc.class.getPackage()) + .addClass(MyReflectionService.class)) + .setFlatClassPath(true) + .withConfigurationResource("reflection-config.properties"); + + @GrpcClient("reflection-service") + MutinyServerReflectionGrpc.MutinyServerReflectionStub reflection; + + private UnicastProcessor processor; + private ResettableSubscriber subscriber; + + @BeforeEach + public void setUp() { + processor = UnicastProcessor.create(); + subscriber = new ResettableSubscriber<>(); + } + + @AfterEach + public void cleanUp() { + processor.onComplete(); + subscriber.cancel(); + } + + @Test + public void testRetrievingListOfServices() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder().setHost("localhost") + .setListServices("").build(); + + Reflection.ServerReflectionResponse response = invoke(request); + List list = response.getListServicesResponse().getServiceList(); + assertThat(list).hasSize(2) + .anySatisfy(r -> assertThat(r.getName()).isEqualTo("grpc.reflection.testing.ReflectableService")) + .anySatisfy(r -> assertThat(r.getName()).isEqualTo("grpc.health.v1.Health")); + } + + @Test + public void testRetrievingFilesByFileName() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileByFilename("reflection/reflection_test_depth_three.proto") + .build(); + + Reflection.ServerReflectionResponse expected = Reflection.ServerReflectionResponse.newBuilder() + .setValidHost("localhost") + .setOriginalRequest(request) + .setFileDescriptorResponse( + Reflection.FileDescriptorResponse.newBuilder() + .addFileDescriptorProto( + ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) + .build()) + .build(); + + Reflection.ServerReflectionResponse response = invoke(request); + assertThat(response).isEqualTo(expected); + } + + @Test + public void testRetrievingFilesByFileNameWithUnknownFileName() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileByFilename("reflection/unknown.proto") + .build(); + + Reflection.ServerReflectionResponse response = invoke(request); + assertThat(response.getErrorResponse().getErrorCode()).isEqualTo(Status.Code.NOT_FOUND.value()); + } + + private Reflection.ServerReflectionResponse invoke(Reflection.ServerReflectionRequest request) { + subscriber.reset(); + Multi multi = reflection.serverReflectionInfo(processor); + multi.subscribe().withSubscriber(subscriber); + subscriber.awaitForSubscription(); + processor.onNext(request); + return subscriber.awaitAndGetLast(); + } + + @Test + public void testRetrievingFilesContainingSymbol() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileContainingSymbol("grpc.reflection.testing.ReflectableService.Method") + .build(); + + List responses = Arrays.asList( + ReflectionTestProto.getDescriptor().toProto().toByteString(), + ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString(), + ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()); + + Reflection.ServerReflectionResponse response = invoke(request); + List list = response.getFileDescriptorResponse().getFileDescriptorProtoList(); + assertThat(list).containsExactlyInAnyOrderElementsOf(responses); + } + + @Test + public void testRetrievingFilesContainingUnknownSymbol() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileContainingSymbol("grpc.reflection.testing.ReflectableService.UnknownMethod") + .build(); + + Reflection.ServerReflectionResponse response = invoke(request); + List list = response.getFileDescriptorResponse().getFileDescriptorProtoList(); + assertThat(list).isEmpty(); + assertThat(response.getErrorResponse().getErrorMessage()).contains("UnknownMethod"); + assertThat(response.getErrorResponse().getErrorCode()).isEqualTo(Status.Code.NOT_FOUND.value()); + } + + @Test + public void testRetrievingFilesContainingNestedSymbol() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileContainingSymbol("grpc.reflection.testing.NestedTypeOuter.Middle.Inner") + .build(); + Reflection.ServerReflectionResponse expected = Reflection.ServerReflectionResponse.newBuilder() + .setValidHost("localhost") + .setOriginalRequest(request) + .setFileDescriptorResponse( + Reflection.FileDescriptorResponse.newBuilder() + .addFileDescriptorProto( + ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) + .build()) + .build(); + Reflection.ServerReflectionResponse resp = invoke(request); + assertThat(resp).isEqualTo(expected); + } + + @Test + public void testRetrievingFilesContainingExtension() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileContainingExtension( + Reflection.ExtensionRequest.newBuilder() + .setContainingType("grpc.reflection.testing.ThirdLevelType") + .setExtensionNumber(100) + .build()) + .build(); + + List expected = Arrays.asList( + ReflectionTestProto.getDescriptor().toProto().toByteString(), + ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString(), + ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()); + + Reflection.ServerReflectionResponse response = invoke(request); + assertThat(response.getFileDescriptorResponse().getFileDescriptorProtoList()) + .containsExactlyInAnyOrderElementsOf(expected); + } + + @Test + public void testRetrievingFilesContainingNestedExtension() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setFileContainingExtension( + Reflection.ExtensionRequest.newBuilder() + .setContainingType("grpc.reflection.testing.ThirdLevelType") + .setExtensionNumber(101) + .build()) + .build(); + + Reflection.ServerReflectionResponse expected = Reflection.ServerReflectionResponse.newBuilder() + .setValidHost("localhost") + .setOriginalRequest(request) + .setFileDescriptorResponse( + Reflection.FileDescriptorResponse.newBuilder() + .addFileDescriptorProto( + ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString()) + .addFileDescriptorProto( + ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) + .build()) + .build(); + + Reflection.ServerReflectionResponse response = invoke(request); + assertThat(response).isEqualTo(expected); + } + + @Test + public void testRetrievingAllExtensionNumbersOfType() { + Reflection.ServerReflectionRequest request = Reflection.ServerReflectionRequest.newBuilder() + .setHost("localhost") + .setAllExtensionNumbersOfType("grpc.reflection.testing.ThirdLevelType") + .build(); + + List expected = Arrays.asList(100, 101); + + Reflection.ServerReflectionResponse response = invoke(request); + List list = response.getAllExtensionNumbersResponse().getExtensionNumberList(); + assertThat(list).containsExactlyInAnyOrderElementsOf(expected); + } + + private static class ResettableSubscriber implements Flow.Subscriber { + + private Flow.Subscription subscription; + private volatile T last; + private boolean completed; + private Throwable failure; + + @Override + public void onSubscribe(Flow.Subscription subscription) { + this.subscription = subscription; + } + + public void reset() { + this.subscription = null; + } + + public void awaitForSubscription() { + await().until(() -> subscription != null); + } + + public T awaitAndGetLast() { + validate(); + last = null; + subscription.request(1); + await().until(() -> last != null); + return last; + } + + @Override + public void onNext(T t) { + last = t; + } + + @Override + public void onError(Throwable throwable) { + this.failure = throwable; + } + + @Override + public void onComplete() { + this.completed = true; + } + + private void validate() { + if (this.failure != null || this.completed) { + throw new IllegalStateException("Subscriber already in a terminal state"); + } + } + + public void cancel() { + this.subscription.cancel(); + } + } + + @GrpcService + public static class MyReflectionService extends MutinyReflectableServiceGrpc.ReflectableServiceImplBase { + @Override + public Uni method(Request request) { + String message = request.getMessage(); + return Uni.createFrom().item(Reply.newBuilder().setMessage(message).build()); + } + } + +} diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java index 35e46d90cf2926..b48a5e149bc216 100644 --- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java +++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/interceptors/FailingInInterceptorTest.java @@ -18,7 +18,11 @@ import io.grpc.ServerInterceptor; import io.grpc.Status; import io.grpc.StatusRuntimeException; -import io.grpc.examples.helloworld.*; +import io.grpc.examples.helloworld.Greeter; +import io.grpc.examples.helloworld.GreeterBean; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; import io.quarkus.grpc.GlobalInterceptor; import io.quarkus.grpc.GrpcClient; import io.quarkus.grpc.server.services.HelloService; diff --git a/extensions/grpc/deployment/src/test/resources/reflection-config.properties b/extensions/grpc/deployment/src/test/resources/reflection-config.properties index fb8074812b64ea..be2f47cdb7aa33 100644 --- a/extensions/grpc/deployment/src/test/resources/reflection-config.properties +++ b/extensions/grpc/deployment/src/test/resources/reflection-config.properties @@ -1,3 +1,7 @@ +quarkus.grpc.server.enable-reflection-service=true + quarkus.grpc.clients.reflection-service.host=localhost quarkus.grpc.clients.reflection-service.port=9001 -quarkus.grpc.server.enable-reflection-service=true \ No newline at end of file + +quarkus.grpc.clients.reflection-service-alpha.host=localhost +quarkus.grpc.clients.reflection-service-alpha.port=9001 diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java index 48f4d4b39f254c..010b9c442bbd3d 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java @@ -47,7 +47,8 @@ import io.quarkus.grpc.runtime.devmode.GrpcHotReplacementInterceptor; import io.quarkus.grpc.runtime.devmode.GrpcServerReloader; import io.quarkus.grpc.runtime.health.GrpcHealthStorage; -import io.quarkus.grpc.runtime.reflection.ReflectionService; +import io.quarkus.grpc.runtime.reflection.ReflectionServiceV1; +import io.quarkus.grpc.runtime.reflection.ReflectionServiceV1alpha; import io.quarkus.grpc.runtime.supports.CompressionInterceptor; import io.quarkus.grpc.runtime.supports.blocking.BlockingServerInterceptor; import io.quarkus.grpc.spi.GrpcBuilderProvider; @@ -162,10 +163,15 @@ private void buildGrpcServer(Vertx vertx, GrpcServerConfiguration configuration, if (reflectionServiceEnabled) { LOGGER.info("Registering gRPC reflection service"); - ReflectionService reflectionService = new ReflectionService(definitions); - ServerServiceDefinition serviceDefinition = ServerInterceptors.intercept(reflectionService, globalInterceptors); + ReflectionServiceV1 reflectionServiceV1 = new ReflectionServiceV1(definitions); + ReflectionServiceV1alpha reflectionServiceV1alpha = new ReflectionServiceV1alpha(definitions); + ServerServiceDefinition serviceDefinition = ServerInterceptors.intercept(reflectionServiceV1, globalInterceptors); GrpcServiceBridge bridge = GrpcServiceBridge.bridge(serviceDefinition); bridge.bind(server); + ServerServiceDefinition serviceDefinitionAlpha = ServerInterceptors.intercept(reflectionServiceV1alpha, + globalInterceptors); + GrpcServiceBridge bridgeAlpha = GrpcServiceBridge.bridge(serviceDefinitionAlpha); + bridgeAlpha.bind(server); } initHealthStorage(); @@ -386,7 +392,7 @@ private void devModeReload(GrpcContainer grpcContainer, Vertx vertx, GrpcServerC definitions.add(service.definition); } - ServerServiceDefinition reflectionService = new ReflectionService(definitions).bindService(); + ServerServiceDefinition reflectionService = new ReflectionServiceV1(definitions).bindService(); for (ServerMethodDefinition method : reflectionService.getMethods()) { methods.put(method.getMethodDescriptor().getFullMethodName(), method); @@ -491,7 +497,8 @@ private Map.Entry buildServer(Vertx vertx, GrpcServerConfigurat if (reflectionServiceEnabled) { LOGGER.info("Registering gRPC reflection service"); - builder.addService(new ReflectionService(definitions)); + builder.addService(new ReflectionServiceV1(definitions)); + builder.addService(new ReflectionServiceV1alpha(definitions)); } for (ServerInterceptor serverInterceptor : grpcContainer.getSortedGlobalInterceptors()) { diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcConfiguration.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcConfiguration.java index 9d6096960b5c57..1cc2406f666c66 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcConfiguration.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcConfiguration.java @@ -2,7 +2,11 @@ import java.util.Map; -import io.quarkus.runtime.annotations.*; +import io.quarkus.runtime.annotations.ConfigDocMapKey; +import io.quarkus.runtime.annotations.ConfigDocSection; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; /** * gRPC configuration root. diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionService.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1.java similarity index 91% rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionService.java rename to extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1.java index 47b9892028cf7a..0f57ad617c4f01 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionService.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/reflection/ReflectionServiceV1.java @@ -24,11 +24,11 @@ import io.grpc.reflection.v1.ServiceResponse; import io.smallrye.mutiny.Multi; -public class ReflectionService extends MutinyServerReflectionGrpc.ServerReflectionImplBase { +public class ReflectionServiceV1 extends MutinyServerReflectionGrpc.ServerReflectionImplBase { private final GrpcServerIndex index; - public ReflectionService(List definitions) { + public ReflectionServiceV1(List definitions) { index = new GrpcServerIndex(definitions); } @@ -40,17 +40,17 @@ public Multi serverReflectionInfo(Multi definitions) { + index = new GrpcServerIndex(definitions); + } + + @Override + public Multi serverReflectionInfo(Multi request) { + return request + .onItem().transform(new Function() { + @Override + public Reflection.ServerReflectionResponse apply(Reflection.ServerReflectionRequest req) { + switch (req.getMessageRequestCase()) { + case LIST_SERVICES: + return ReflectionServiceV1alpha.this.getServiceList(req); + case FILE_BY_FILENAME: + return ReflectionServiceV1alpha.this.getFileByName(req); + case FILE_CONTAINING_SYMBOL: + return ReflectionServiceV1alpha.this.getFileContainingSymbol(req); + case FILE_CONTAINING_EXTENSION: + return ReflectionServiceV1alpha.this.getFileByExtension(req); + case ALL_EXTENSION_NUMBERS_OF_TYPE: + return ReflectionServiceV1alpha.this.getAllExtensions(req); + default: + return ReflectionServiceV1alpha.this.getErrorResponse(req, Status.Code.UNIMPLEMENTED, + "not implemented " + req.getMessageRequestCase()); + + } + } + }); + } + + private Reflection.ServerReflectionResponse getServiceList(Reflection.ServerReflectionRequest request) { + Reflection.ListServiceResponse response = index.getServiceNames().stream() + .map(new Function() { // NOSONAR + @Override + public Reflection.ServiceResponse apply(String s) { + return Reflection.ServiceResponse.newBuilder().setName(s).build(); + } + }) + .collect(new Supplier() { + @Override + public Reflection.ListServiceResponse.Builder get() { + return Reflection.ListServiceResponse.newBuilder(); + } + }, + new BiConsumer() { + @Override + public void accept(Reflection.ListServiceResponse.Builder builder, + Reflection.ServiceResponse value) { + builder.addService(value); + } + }, + new BiConsumer() { // NOSONAR + @Override + public void accept(Reflection.ListServiceResponse.Builder b1, + Reflection.ListServiceResponse.Builder b2) { + b1.addAllService(b2.getServiceList()); + } + }) + .build(); + + return Reflection.ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setListServicesResponse(response) + .build(); + } + + private Reflection.ServerReflectionResponse getFileByName(Reflection.ServerReflectionRequest request) { + String name = request.getFileByFilename(); + FileDescriptor fd = index.getFileDescriptorByName(name); + if (fd != null) { + return getServerReflectionResponse(request, fd); + } else { + return getErrorResponse(request, Status.Code.NOT_FOUND, "File not found (" + name + ")"); + } + } + + private Reflection.ServerReflectionResponse getFileContainingSymbol(Reflection.ServerReflectionRequest request) { + String symbol = request.getFileContainingSymbol(); + FileDescriptor fd = index.getFileDescriptorBySymbol(symbol); + if (fd != null) { + return getServerReflectionResponse(request, fd); + } else { + return getErrorResponse(request, Status.Code.NOT_FOUND, "Symbol not found (" + symbol + ")"); + } + } + + private Reflection.ServerReflectionResponse getFileByExtension(Reflection.ServerReflectionRequest request) { + Reflection.ExtensionRequest extensionRequest = request.getFileContainingExtension(); + String type = extensionRequest.getContainingType(); + int extension = extensionRequest.getExtensionNumber(); + FileDescriptor fd = index.getFileDescriptorByExtensionAndNumber(type, extension); + if (fd != null) { + return getServerReflectionResponse(request, fd); + } else { + return getErrorResponse(request, Status.Code.NOT_FOUND, + "Extension not found (" + type + ", " + extension + ")"); + } + } + + private Reflection.ServerReflectionResponse getAllExtensions(Reflection.ServerReflectionRequest request) { + String type = request.getAllExtensionNumbersOfType(); + Set extensions = index.getExtensionNumbersOfType(type); + if (extensions != null) { + Reflection.ExtensionNumberResponse.Builder builder = Reflection.ExtensionNumberResponse.newBuilder() + .setBaseTypeName(type) + .addAllExtensionNumber(extensions); + return Reflection.ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setAllExtensionNumbersResponse(builder) + .build(); + } else { + return getErrorResponse(request, Status.Code.NOT_FOUND, "Type not found."); + } + } + + private Reflection.ServerReflectionResponse getServerReflectionResponse( + Reflection.ServerReflectionRequest request, FileDescriptor fd) { + Reflection.FileDescriptorResponse.Builder fdRBuilder = Reflection.FileDescriptorResponse.newBuilder(); + + // Traverse the descriptors to get the full list of dependencies and add them to the builder + Set seenFiles = new HashSet<>(); + Queue frontier = new ArrayDeque<>(); + seenFiles.add(fd.getName()); + frontier.add(fd); + while (!frontier.isEmpty()) { + FileDescriptor nextFd = frontier.remove(); + fdRBuilder.addFileDescriptorProto(nextFd.toProto().toByteString()); + for (FileDescriptor dependencyFd : nextFd.getDependencies()) { + if (!seenFiles.contains(dependencyFd.getName())) { + seenFiles.add(dependencyFd.getName()); + frontier.add(dependencyFd); + } + } + } + return Reflection.ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setFileDescriptorResponse(fdRBuilder) + .build(); + } + + private Reflection.ServerReflectionResponse getErrorResponse( + Reflection.ServerReflectionRequest request, Status.Code code, String message) { + return Reflection.ServerReflectionResponse.newBuilder() + .setValidHost(request.getHost()) + .setOriginalRequest(request) + .setErrorResponse( + Reflection.ErrorResponse.newBuilder() + .setErrorCode(code.value()) + .setErrorMessage(message)) + .build(); + + } + +} diff --git a/extensions/grpc/stubs/src/main/proto/reflection/v1alpha/README.md b/extensions/grpc/stubs/src/main/proto/reflection/v1alpha/README.md new file mode 100644 index 00000000000000..411a672d56314d --- /dev/null +++ b/extensions/grpc/stubs/src/main/proto/reflection/v1alpha/README.md @@ -0,0 +1,2 @@ +While the v1alpha is deprecated since 2018 (https://github.com/grpc/grpc-proto/commits/master/grpc/reflection/v1/reflection.proto), +most tools are still using it. So we keep it around. \ No newline at end of file diff --git a/extensions/grpc/stubs/src/main/proto/reflection/v1alpha/reflection.proto b/extensions/grpc/stubs/src/main/proto/reflection/v1alpha/reflection.proto new file mode 100644 index 00000000000000..462e85a222ae29 --- /dev/null +++ b/extensions/grpc/stubs/src/main/proto/reflection/v1alpha/reflection.proto @@ -0,0 +1,136 @@ +// Copyright 2016 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Service exported by server reflection + +syntax = "proto3"; + +package grpc.reflection.v1alpha; + +service ServerReflection { + // The reflection service is structured as a bidirectional stream, ensuring + // all related requests go to a single server. + rpc ServerReflectionInfo(stream ServerReflectionRequest) + returns (stream ServerReflectionResponse); +} + +// The message sent by the client when calling ServerReflectionInfo method. +message ServerReflectionRequest { + string host = 1; + // To use reflection service, the client should set one of the following + // fields in message_request. The server distinguishes requests by their + // defined field and then handles them using corresponding methods. + oneof message_request { + // Find a proto file by the file name. + string file_by_filename = 3; + + // Find the proto file that declares the given fully-qualified symbol name. + // This field should be a fully-qualified symbol name + // (e.g. .[.] or .). + string file_containing_symbol = 4; + + // Find the proto file which defines an extension extending the given + // message type with the given field number. + ExtensionRequest file_containing_extension = 5; + + // Finds the tag numbers used by all known extensions of the given message + // type, and appends them to ExtensionNumberResponse in an undefined order. + // Its corresponding method is best-effort: it's not guaranteed that the + // reflection service will implement this method, and it's not guaranteed + // that this method will provide all extensions. Returns + // StatusCode::UNIMPLEMENTED if it's not implemented. + // This field should be a fully-qualified type name. The format is + // . + string all_extension_numbers_of_type = 6; + + // List the full names of registered services. The content will not be + // checked. + string list_services = 7; + } +} + +// The type name and extension number sent by the client when requesting +// file_containing_extension. +message ExtensionRequest { + // Fully-qualified type name. The format should be . + string containing_type = 1; + int32 extension_number = 2; +} + +// The message sent by the server to answer ServerReflectionInfo method. +message ServerReflectionResponse { + string valid_host = 1; + ServerReflectionRequest original_request = 2; + // The server set one of the following fields accroding to the message_request + // in the request. + oneof message_response { + // This message is used to answer file_by_filename, file_containing_symbol, + // file_containing_extension requests with transitive dependencies. As + // the repeated label is not allowed in oneof fields, we use a + // FileDescriptorResponse message to encapsulate the repeated fields. + // The reflection service is allowed to avoid sending FileDescriptorProtos + // that were previously sent in response to earlier requests in the stream. + FileDescriptorResponse file_descriptor_response = 4; + + // This message is used to answer all_extension_numbers_of_type requst. + ExtensionNumberResponse all_extension_numbers_response = 5; + + // This message is used to answer list_services request. + ListServiceResponse list_services_response = 6; + + // This message is used when an error occurs. + ErrorResponse error_response = 7; + } +} + +// Serialized FileDescriptorProto messages sent by the server answering +// a file_by_filename, file_containing_symbol, or file_containing_extension +// request. +message FileDescriptorResponse { + // Serialized FileDescriptorProto messages. We avoid taking a dependency on + // descriptor.proto, which uses proto2 only features, by making them opaque + // bytes instead. + repeated bytes file_descriptor_proto = 1; +} + +// A list of extension numbers sent by the server answering +// all_extension_numbers_of_type request. +message ExtensionNumberResponse { + // Full name of the base type, including the package name. The format + // is . + string base_type_name = 1; + repeated int32 extension_number = 2; +} + +// A list of ServiceResponse sent by the server answering list_services request. +message ListServiceResponse { + // The information of each service may be expanded in the future, so we use + // ServiceResponse message to encapsulate it. + repeated ServiceResponse service = 1; +} + +// The information of a single service used by ListServiceResponse to answer +// list_services request. +message ServiceResponse { + // Full name of a registered service, including its package name. The format + // is . + string name = 1; +} + +// The error code and error message sent by the server when an error occurs. +message ErrorResponse { + // This field uses the error codes defined in grpc::StatusCode. + int32 error_code = 1; + string error_message = 2; +} \ No newline at end of file From 10a7c59fc7b926856cd144014d8a290afdac09de Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Fri, 5 May 2023 13:32:13 -0400 Subject: [PATCH 237/333] Do not configure auto security filter when `auto-add-security` is false Signed-off-by: Michael Edgar --- .../deployment/SmallRyeOpenApiProcessor.java | 2 +- .../AutoAddSecurityDisabledTestCase.java | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/AutoAddSecurityDisabledTestCase.java diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index 9ccfa58654fa51..5d9e6b6a220e17 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -347,7 +347,7 @@ void addAutoFilters(BuildProducer addToOpenAPID addToOpenAPIDefinitionProducer .produce(new AddToOpenAPIDefinitionBuildItem( new SecurityConfigFilter(config))); - } else { + } else if (config.autoAddSecurity) { OASFilter autoSecurityFilter = getAutoSecurityFilter(securityInformationBuildItems, config); if (autoSecurityFilter != null) { diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/AutoAddSecurityDisabledTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/AutoAddSecurityDisabledTestCase.java new file mode 100644 index 00000000000000..3bbe377991405e --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/AutoAddSecurityDisabledTestCase.java @@ -0,0 +1,34 @@ +package io.quarkus.smallrye.openapi.test.jaxrs; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +class AutoAddSecurityDisabledTestCase { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(OpenApiResource.class, ResourceBean.class) + .addAsResource( + new StringAsset("quarkus.smallrye-openapi.auto-add-security=false\n"), + "application.properties")); + + @Test + void testAutoSecurityRequirement() { + RestAssured.given().header("Accept", "application/json") + .when() + .get("/q/openapi") + .then() + .log().ifValidationFails() + .body("components", not(hasKey(equalTo("securitySchemes")))); + } + +} From b72674e5419c8c4a9a254f3be517d09a323445c2 Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Fri, 5 May 2023 15:04:38 -0400 Subject: [PATCH 238/333] OpenAPI: Add support for `apiKey` security scheme configuration Signed-off-by: Michael Edgar --- .../deployment/SmallRyeOpenApiConfig.java | 13 ++ .../filter/SecurityConfigFilter.java | 147 ++++++++++-------- .../filter/SecurityConfigFilterTest.java | 64 ++++++++ .../ApiKeySecurityWithConfigTestCase.java | 38 +++++ 4 files changed, 195 insertions(+), 67 deletions(-) create mode 100644 extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java create mode 100644 extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/ApiKeySecurityWithConfigTestCase.java diff --git a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java index ddbd205305c3cb..9e9f11f99cc0d8 100644 --- a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java +++ b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java @@ -92,6 +92,18 @@ public final class SmallRyeOpenApiConfig { @ConfigItem(defaultValue = "true") public boolean autoAddSecurity; + /** + * Required when using `apiKey` security. The location of the API key. Valid values are "query", "header" or "cookie". + */ + @ConfigItem + public Optional apiKeyParameterIn; + + /** + * Required when using `apiKey` security. The name of the header, query or cookie parameter to be used. + */ + @ConfigItem + public Optional apiKeyParameterName; + /** * Add a scheme value to the Basic HTTP Security Scheme */ @@ -213,6 +225,7 @@ public final class SmallRyeOpenApiConfig { public Optional operationIdStrategy; public enum SecurityScheme { + apiKey, basic, jwt, oauth2, diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilter.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilter.java index 77f8e0305733f8..2dec832d218b7c 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilter.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilter.java @@ -2,6 +2,8 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; import org.eclipse.microprofile.openapi.OASFactory; import org.eclipse.microprofile.openapi.OASFilter; @@ -11,6 +13,7 @@ import org.eclipse.microprofile.openapi.models.security.SecurityScheme; import org.jboss.logging.Logger; +import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig; /** @@ -26,78 +29,88 @@ public SecurityConfigFilter(SmallRyeOpenApiConfig config) { } @Override + /** + * Add a security scheme from config + */ public void filterOpenAPI(OpenAPI openAPI) { + if (config.securityScheme.isEmpty()) { + return; + } + + // Make sure components are created + if (openAPI.getComponents() == null) { + openAPI.setComponents(OASFactory.createComponents()); + } - // Add a security scheme from config - if (config.securityScheme.isPresent()) { - - // Make sure components are created - if (openAPI.getComponents() == null) { - openAPI.setComponents(OASFactory.createComponents()); - } - - Map securitySchemes = new HashMap<>(); - - // Add any existing security - if (openAPI.getComponents().getSecuritySchemes() != null - && !openAPI.getComponents().getSecuritySchemes().isEmpty()) { - securitySchemes.putAll(openAPI.getComponents().getSecuritySchemes()); - } - - SmallRyeOpenApiConfig.SecurityScheme securitySchemeOption = config.securityScheme.get(); - - SecurityScheme securityScheme = OASFactory.createSecurityScheme(); - securityScheme.setDescription(config.securitySchemeDescription); - config.getValidSecuritySchemeExtentions().forEach(securityScheme::addExtension); - - switch (securitySchemeOption) { - case basic: - securityScheme.setType(SecurityScheme.Type.HTTP); - securityScheme.setScheme(config.basicSecuritySchemeValue); - break; - case jwt: - securityScheme.setType(SecurityScheme.Type.HTTP); - securityScheme.setScheme(config.jwtSecuritySchemeValue); - securityScheme.setBearerFormat(config.jwtBearerFormat); - break; - case oauth2: - securityScheme.setType(SecurityScheme.Type.HTTP); - securityScheme.setScheme(config.oauth2SecuritySchemeValue); - securityScheme.setBearerFormat(config.oauth2BearerFormat); - break; - case oidc: - securityScheme.setType(SecurityScheme.Type.OPENIDCONNECT); - securityScheme.setOpenIdConnectUrl(config.oidcOpenIdConnectUrl.orElse(null)); - break; - case oauth2Implicit: - securityScheme.setType(SecurityScheme.Type.OAUTH2); - OAuthFlows oAuthFlows = OASFactory.createOAuthFlows(); - OAuthFlow oAuthFlow = OASFactory.createOAuthFlow(); - if (config.oauth2ImplicitAuthorizationUrl.isPresent()) { - oAuthFlow.authorizationUrl(config.oauth2ImplicitAuthorizationUrl.get()); - } - if (config.oauth2ImplicitRefreshUrl.isPresent()) { - oAuthFlow.refreshUrl(config.oauth2ImplicitRefreshUrl.get()); - } - if (config.oauth2ImplicitTokenUrl.isPresent()) { - oAuthFlow.tokenUrl(config.oauth2ImplicitTokenUrl.get()); - } - oAuthFlows.setImplicit(oAuthFlow); - securityScheme.setType(SecurityScheme.Type.OAUTH2); - securityScheme.setFlows(oAuthFlows); - break; - } - securitySchemes.put(config.securitySchemeName, securityScheme); - openAPI.getComponents().setSecuritySchemes(securitySchemes); + Map securitySchemes = new HashMap<>(); + + // Add any existing security + Optional.ofNullable(openAPI.getComponents().getSecuritySchemes()) + .ifPresent(securitySchemes::putAll); + + SmallRyeOpenApiConfig.SecurityScheme securitySchemeOption = config.securityScheme.get(); + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + securityScheme.setDescription(config.securitySchemeDescription); + config.getValidSecuritySchemeExtentions().forEach(securityScheme::addExtension); + + switch (securitySchemeOption) { + case apiKey: + configureApiKeySecurityScheme(securityScheme); + break; + case basic: + securityScheme.setType(SecurityScheme.Type.HTTP); + securityScheme.setScheme(config.basicSecuritySchemeValue); + break; + case jwt: + securityScheme.setType(SecurityScheme.Type.HTTP); + securityScheme.setScheme(config.jwtSecuritySchemeValue); + securityScheme.setBearerFormat(config.jwtBearerFormat); + break; + case oauth2: + securityScheme.setType(SecurityScheme.Type.HTTP); + securityScheme.setScheme(config.oauth2SecuritySchemeValue); + securityScheme.setBearerFormat(config.oauth2BearerFormat); + break; + case oidc: + securityScheme.setType(SecurityScheme.Type.OPENIDCONNECT); + securityScheme.setOpenIdConnectUrl(config.oidcOpenIdConnectUrl.orElse(null)); + break; + case oauth2Implicit: + securityScheme.setType(SecurityScheme.Type.OAUTH2); + OAuthFlows oAuthFlows = OASFactory.createOAuthFlows(); + OAuthFlow oAuthFlow = OASFactory.createOAuthFlow(); + config.oauth2ImplicitAuthorizationUrl.ifPresent(oAuthFlow::authorizationUrl); + config.oauth2ImplicitRefreshUrl.ifPresent(oAuthFlow::refreshUrl); + config.oauth2ImplicitTokenUrl.ifPresent(oAuthFlow::tokenUrl); + oAuthFlows.setImplicit(oAuthFlow); + securityScheme.setType(SecurityScheme.Type.OAUTH2); + securityScheme.setFlows(oAuthFlows); + break; } - if (openAPI.getComponents() != null && openAPI.getComponents().getSecuritySchemes() != null) { + securitySchemes.put(config.securitySchemeName, securityScheme); + openAPI.getComponents().setSecuritySchemes(securitySchemes); - Map securitySchemes = openAPI.getComponents().getSecuritySchemes(); - if (securitySchemes.size() > 1) { - log.warn("Detected multiple Security Schemes, only one scheme is supported at the moment " - + securitySchemes.keySet().toString()); - } + if (securitySchemes.size() > 1) { + log.warn("Detected multiple Security Schemes, only one scheme is supported at the moment " + + securitySchemes.keySet().toString()); } } + + void configureApiKeySecurityScheme(SecurityScheme securityScheme) { + securityScheme.setType(SecurityScheme.Type.APIKEY); + + securityScheme.setName(config.apiKeyParameterName + .orElseThrow( + () -> new ConfigurationException("Parameter `name` is required for `apiKey` OpenAPI security scheme"))); + + securityScheme.setIn(config.apiKeyParameterIn + .map(in -> Stream.of(SecurityScheme.In.values()) + .filter(v -> v.toString().equals(in)) + .findFirst() + .orElseThrow(() -> new ConfigurationException( + "Parameter `in` given for `apiKey` OpenAPI security schema is invalid: [" + in + ']'))) + .orElseThrow( + () -> new ConfigurationException("Parameter `in` is required for `apiKey` OpenAPI security scheme"))); + } } diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java new file mode 100644 index 00000000000000..32f90e47d07b9e --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/deployment/filter/SecurityConfigFilterTest.java @@ -0,0 +1,64 @@ +package io.quarkus.smallrye.openapi.deployment.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Optional; + +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.models.security.SecurityScheme; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig; + +class SecurityConfigFilterTest { + + SmallRyeOpenApiConfig config; + SecurityConfigFilter target; + + @BeforeEach + void setup() { + config = new SmallRyeOpenApiConfig(); + target = new SecurityConfigFilter(config); + } + + @Test + void testConfigureApiKeySecuritySchemeMissingName() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + config.securityScheme = Optional.of(SmallRyeOpenApiConfig.SecurityScheme.apiKey); + config.apiKeyParameterName = Optional.empty(); + assertThrows(ConfigurationException.class, () -> target.configureApiKeySecurityScheme(securityScheme)); + } + + @Test + void testConfigureApiKeySecuritySchemeMissingParamIn() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + config.securityScheme = Optional.of(SmallRyeOpenApiConfig.SecurityScheme.apiKey); + config.apiKeyParameterName = Optional.of("KeyParamName"); + config.apiKeyParameterIn = Optional.empty(); + assertThrows(ConfigurationException.class, () -> target.configureApiKeySecurityScheme(securityScheme)); + } + + @Test + void testConfigureApiKeySecuritySchemeInvalidParamIn() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + config.securityScheme = Optional.of(SmallRyeOpenApiConfig.SecurityScheme.apiKey); + config.apiKeyParameterName = Optional.of("KeyParamName"); + config.apiKeyParameterIn = Optional.of("path"); + assertThrows(ConfigurationException.class, () -> target.configureApiKeySecurityScheme(securityScheme)); + } + + @Test + void testConfigureApiKeySecuritySchemeSuccess() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + config.securityScheme = Optional.of(SmallRyeOpenApiConfig.SecurityScheme.apiKey); + config.apiKeyParameterName = Optional.of("KeyParamName"); + config.apiKeyParameterIn = Optional.of("header"); + target.configureApiKeySecurityScheme(securityScheme); + assertEquals("KeyParamName", securityScheme.getName()); + assertEquals(SecurityScheme.In.HEADER, securityScheme.getIn()); + } + +} diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/ApiKeySecurityWithConfigTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/ApiKeySecurityWithConfigTestCase.java new file mode 100644 index 00000000000000..049d86549c40e2 --- /dev/null +++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/ApiKeySecurityWithConfigTestCase.java @@ -0,0 +1,38 @@ +package io.quarkus.smallrye.openapi.test.jaxrs; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasEntry; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +class ApiKeySecurityWithConfigTestCase { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(OpenApiResource.class, ResourceBean.class) + .addAsResource( + new StringAsset("quarkus.smallrye-openapi.security-scheme=apiKey\n" + + "quarkus.smallrye-openapi.security-scheme-name=APIKeyCompanyAuthentication\n" + + "quarkus.smallrye-openapi.security-scheme-description=API Key Authentication\n" + + "quarkus.smallrye-openapi.api-key-parameter-in=cookie\n" + + "quarkus.smallrye-openapi.api-key-parameter-name=APIKEY"), + "application.properties")); + + @Test + void testApiKeyAuthentication() { + RestAssured.given().header("Accept", "application/json") + .when().get("/q/openapi") + .then() + .body("components.securitySchemes.APIKeyCompanyAuthentication", allOf( + hasEntry("type", "apiKey"), + hasEntry("description", "API Key Authentication"), + hasEntry("in", "cookie"), + hasEntry("name", "APIKEY"))); + } +} From 7016eaa97572c5f9d6bbdf7087bde6e88767d5b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 22:01:28 +0000 Subject: [PATCH 239/333] Bump mutiny from 2.1.0 to 2.2.0 Bumps [mutiny](https://github.com/smallrye/smallrye-mutiny) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/2.1.0...2.2.0) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- independent-projects/arc/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 4fb305a4dbd489..8e059c0240ef72 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -49,7 +49,7 @@ 1.6.1.Final 3.1.1 3.5.0.Final - 2.1.0 + 2.2.0 3.24.2 5.9.3 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index c1a2bf5f4b7429..0d28e7ff5f35d8 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -49,7 +49,7 @@ 3.2.1 3.0.0 1.6.8 - 2.1.0 + 2.2.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 502d4fd8e71371..4d8c4138af963b 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -61,7 +61,7 @@ 3.2.1 3.0.0 1.6.8 - 2.1.0 + 2.2.0 2.1.0 4.4.1 5.3.0 From a43c819138542664db13f5309b2b0b9fcf11968a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 22:15:15 +0000 Subject: [PATCH 240/333] Bump de.flapdoodle.embed.mongo from 4.6.2 to 4.6.3 Bumps [de.flapdoodle.embed.mongo](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo) from 4.6.2 to 4.6.3. - [Commits](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/compare/de.flapdoodle.embed.mongo-4.6.2...de.flapdoodle.embed.mongo-4.6.3) --- updated-dependencies: - dependency-name: de.flapdoodle.embed:de.flapdoodle.embed.mongo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 6ed62e74e3ceba..08d4c333e707bf 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -176,7 +176,7 @@ 3.14.9 1.17.2 0.2.1 - 4.6.2 + 4.6.3 5.2.SP7 2.1.SP2 5.4.Final From 9fedb165ad1abafca776ecac0647674d349923f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 22:17:35 +0000 Subject: [PATCH 241/333] Bump angus-activation from 2.0.0 to 2.0.1 Bumps [angus-activation](https://github.com/eclipse-ee4j/angus-activation) from 2.0.0 to 2.0.1. - [Release notes](https://github.com/eclipse-ee4j/angus-activation/releases) - [Commits](https://github.com/eclipse-ee4j/angus-activation/compare/2.0.0...2.0.1) --- updated-dependencies: - dependency-name: org.eclipse.angus:angus-activation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 6ed62e74e3ceba..10c10c86c06149 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -14,7 +14,7 @@ pom - 2.0.0 + 2.0.1 1.73 1.0.2.3 1.0.14.1 From a9cf5cd9b41eb5b3ac1e1f5c9ba9ef2282f165d9 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 7 May 2023 11:47:01 +0300 Subject: [PATCH 242/333] Fix small documentation issue in reactive Fixes: #33171 --- docs/src/main/asciidoc/getting-started-reactive.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/getting-started-reactive.adoc b/docs/src/main/asciidoc/getting-started-reactive.adoc index b89f7e75312504..f747f58b45a831 100644 --- a/docs/src/main/asciidoc/getting-started-reactive.adoc +++ b/docs/src/main/asciidoc/getting-started-reactive.adoc @@ -146,7 +146,7 @@ It interacts with the database without blocking the thread. In addition, this reactive `PanacheEntity` proposes a reactive API. We will use this API to implement the REST endpoint. -Before going further, open the `src/main/resource/application.properties` file and add: +Before going further, open the `src/main/resources/application.properties` file and add: [source, properties] ---- From 9a051934641cde0ecd37083871a8bb4b3e454caf Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 7 May 2023 11:54:32 +0300 Subject: [PATCH 243/333] Use Uni.replace() with instead of onItem().transform() in HR example This can be done because when the entity is saved, the id can be accessed by the Fruit parameter. Fixes: #33172 --- docs/src/main/asciidoc/getting-started-reactive.adoc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/getting-started-reactive.adoc b/docs/src/main/asciidoc/getting-started-reactive.adoc index b89f7e75312504..0771413edb6810 100644 --- a/docs/src/main/asciidoc/getting-started-reactive.adoc +++ b/docs/src/main/asciidoc/getting-started-reactive.adoc @@ -252,7 +252,7 @@ The `create` method allows adding a new fruit to the database: @POST public Uni create(Fruit fruit) { return Panache.withTransaction(fruit::persist) - .onItem().transform(inserted -> Response.created(URI.create("/fruits/" + inserted.id)).build()); + .replaceWith(inserted -> Response.created(URI.create("/fruits/" + inserted.id)).build()); } ---- @@ -264,8 +264,7 @@ This `Uni` emits the result of the insertion of the fruit in the database. Once the insertion completes (and that's our continuation), we create a `201 CREATED` response. RESTEasy Reactive automatically reads the request body as JSON and creates the `Fruit` instance. -NOTE: The `.onItem().transform(...)` can be replaced with `.map(...)`. -`map` is a shortcut. +NOTE: The `.onItem().transform(...)` can be replaced with `.replaceWith(...)`, as `replaceWith` is a shortcut version. If you have https://curl.se/[curl] on your machine, you can try the endpoint using: From 85088880316e47036a2cc7db4248c363a802859c Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 7 May 2023 12:04:52 +0300 Subject: [PATCH 244/333] Use RR RestResponse instead of JAX-RS Response in reactive doc The reason this makes more sense is because the former retains the generic type --- docs/src/main/asciidoc/getting-started-reactive.adoc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/getting-started-reactive.adoc b/docs/src/main/asciidoc/getting-started-reactive.adoc index 0771413edb6810..ae2c7e2dded6e2 100644 --- a/docs/src/main/asciidoc/getting-started-reactive.adoc +++ b/docs/src/main/asciidoc/getting-started-reactive.adoc @@ -250,9 +250,8 @@ The `create` method allows adding a new fruit to the database: [source, java] ---- @POST -public Uni create(Fruit fruit) { - return Panache.withTransaction(fruit::persist) - .replaceWith(inserted -> Response.created(URI.create("/fruits/" + inserted.id)).build()); +public Uni> create(Fruit fruit) { + return Panache.withTransaction(fruit::persist).replaceWith(RestResponse.status(CREATED, fruit)); } ---- @@ -264,8 +263,6 @@ This `Uni` emits the result of the insertion of the fruit in the database. Once the insertion completes (and that's our continuation), we create a `201 CREATED` response. RESTEasy Reactive automatically reads the request body as JSON and creates the `Fruit` instance. -NOTE: The `.onItem().transform(...)` can be replaced with `.replaceWith(...)`, as `replaceWith` is a shortcut version. - If you have https://curl.se/[curl] on your machine, you can try the endpoint using: [source, bash] From 9052bbf9800f3643a388f905bc6ebb3a98c538e4 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 7 May 2023 12:07:08 +0300 Subject: [PATCH 245/333] Improve the wording of the Hibernate Reactive / RR create code --- docs/src/main/asciidoc/getting-started-reactive.adoc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/getting-started-reactive.adoc b/docs/src/main/asciidoc/getting-started-reactive.adoc index ae2c7e2dded6e2..dacb4e1bd763bf 100644 --- a/docs/src/main/asciidoc/getting-started-reactive.adoc +++ b/docs/src/main/asciidoc/getting-started-reactive.adoc @@ -256,12 +256,9 @@ public Uni> create(Fruit fruit) { ---- The code is a bit more involved. -To write to a database, we need a transaction. -So we use `Panache.withTransaction` to get one (asynchronously) and call the `persist` method when we receive the transaction. -The `persist` method is also returning a `Uni`. -This `Uni` emits the result of the insertion of the fruit in the database. -Once the insertion completes (and that's our continuation), we create a `201 CREATED` response. -RESTEasy Reactive automatically reads the request body as JSON and creates the `Fruit` instance. +To write to a database, we need a transaction, therefore we use `Panache.withTransaction` to obtain one (asynchronously) and proceed to call the `persist` method. +The `persist` method returns a `Uni` that emits the result of the insertion of the fruit in the database. +Once the insertion completes (which plays the role of the continuation), we create a `201 CREATED` HTTP response. If you have https://curl.se/[curl] on your machine, you can try the endpoint using: From 1b4dbfcf26f554dd85444b0c57efe9229978e987 Mon Sep 17 00:00:00 2001 From: Chris Pitman Date: Sun, 7 May 2023 22:02:13 -0400 Subject: [PATCH 246/333] qute-reference: correct import for hidden fragment Using square brackets to refer to using a fragment inside the same template does not work, but using a dollar sign with no prefix does (as noted on like 1199). --- docs/src/main/asciidoc/qute-reference.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 77fba89455d2ed..036530d2665a3b 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -1215,16 +1215,16 @@ An interesting use case would be a fragment that can be used multiple-times insi

    My page

    This document -{#include [strong] val='contains' /} <2> +{#include $strong val='contains' /} <2> a lot of -{#include [strong] val='information' /} <3> +{#include $strong val='information' /} <3> !

    ---- <1> Defines a hidden fragment with identifier `strong`. In this particular case, we use the `false` boolean literal as the value of the `rendered` parameter. However, it's possible to use any expression there. <2> Include the fragment `strong` and pass the value. -Note the syntax `[strong]` which is translated to include the fragment `strong` from the current template. +Note the syntax `$strong` which is translated to include the fragment `strong` from the current template. <3> Include the fragment `strong` and pass the value. The snippet above renders something like: From 3990888bbc1c6b2e730de928ce6665491904e1a5 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 3 Apr 2023 20:32:12 +0300 Subject: [PATCH 247/333] Bump Kotlin to 1.8.21 --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- .../src/test/java/io/quarkus/gradle/QuarkusPluginTest.java | 2 +- independent-projects/arc/pom.xml | 2 +- .../conditional-dependencies-kotlin/build.gradle.kts | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 6ed62e74e3ceba..8e4c2751a989a5 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -157,7 +157,7 @@ 2.14.0 2.2.0 1.0.0 - 1.8.10 + 1.8.21 1.6.4 1.5.0 3.5.5 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 127f57e9c4fd58..f59aef296d173d 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -20,7 +20,7 @@ 3.11.0 - 1.8.10 + 1.8.21 1.8.10 2.13.8 4.8.1 diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index fff47002b33cac..d7f672ffc5ad8a 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -106,7 +106,7 @@ public void shouldReturnMultipleOutputSourceDirectories() { @Test public void shouldNotFailOnProjectDependenciesWithoutMain(@TempDir Path testProjectDir) throws IOException { - var kotlinVersion = System.getProperty("kotlin_version", "1.8.10"); + var kotlinVersion = System.getProperty("kotlin_version", "1.8.20"); var settingFile = testProjectDir.resolve("settings.gradle.kts"); var mppProjectDir = testProjectDir.resolve("mpp"); var quarkusProjectDir = testProjectDir.resolve("quarkus"); diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 4fb305a4dbd489..27163886f1d6de 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -53,7 +53,7 @@ 3.24.2 5.9.3 - 1.8.10 + 1.8.21 1.6.4 5.3.1 diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts index 4770bda577dc13..6d55ad6ebc73fb 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "1.8.10" - kotlin("plugin.allopen") version "1.8.10" + kotlin("jvm") version "1.8.21" + kotlin("plugin.allopen") version "1.8.21" id("io.quarkus") } From 2fba7362a01384ffbeb99849ad68cfdd8eca945a Mon Sep 17 00:00:00 2001 From: Kevin Dubois Date: Mon, 8 May 2023 09:47:16 +0200 Subject: [PATCH 248/333] Update apache link in the header to https --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index ff9ad4530f5716..62589edd12a37d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION From 6b00adb36e61a1002997dd5729e0d2ca682899e2 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 May 2023 13:06:59 +0300 Subject: [PATCH 249/333] Populate ResourceInfo when exception thrown from Resource returning CS Fixes: #32862 --- ...onseWithExceptionAndFiltersTargetTest.java | 223 ++++++++++++++++++ .../CompletionStageResponseHandler.java | 2 +- 2 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/AsyncResponseWithExceptionAndFiltersTargetTest.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/AsyncResponseWithExceptionAndFiltersTargetTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/AsyncResponseWithExceptionAndFiltersTargetTest.java new file mode 100644 index 00000000000000..3a56fe55b86883 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/AsyncResponseWithExceptionAndFiltersTargetTest.java @@ -0,0 +1,223 @@ +package io.quarkus.resteasy.reactive.server.test.customproviders; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.CoreMatchers.nullValue; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +import org.jboss.resteasy.reactive.server.ServerResponseFilter; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class AsyncResponseWithExceptionAndFiltersTargetTest { + + private static final String RESOURCE_INFO_CLASS_HEADER = "resourceInfoClass"; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(CsResource.class, UniResource.class, + CustomResponseFilter.class, + DummyException.class, DummyExceptionMapper.class, + DummyException2.class, DummyException2.class); + } + }); + + @Path("cs") + public static class CsResource { + + @GET + @Path("handled") + public CompletionStage handled() { + throw new DummyException(true); + } + + @GET + @Path("handled2") + public CompletionStage handled2() { + throw new DummyException2(true); + } + + @GET + @Path("unhandled") + public CompletionStage unhandled() { + throw new DummyException(false); + } + + @GET + @Path("unhandled2") + public CompletionStage unhandled2() { + throw new DummyException2(false); + } + } + + @Path("uni") + public static class UniResource { + + @GET + @Path("handled") + public CompletionStage handled() { + throw new DummyException(true); + } + + @GET + @Path("handled2") + public CompletionStage handled2() { + throw new DummyException2(true); + } + + @GET + @Path("unhandled") + public CompletionStage unhandled() { + throw new DummyException(false); + } + + @GET + @Path("unhandled2") + public CompletionStage unhandled2() { + throw new DummyException2(false); + } + } + + @Test + public void csHandled() { + when().get("/cs/handled") + .then() + .statusCode(999) + .header(RESOURCE_INFO_CLASS_HEADER, CsResource.class.getSimpleName()); + } + + @Test + public void csHandled2() { + when().get("/cs/handled2") + .then() + .statusCode(999) + .header(RESOURCE_INFO_CLASS_HEADER, CsResource.class.getSimpleName()); + } + + @Test + public void csUnhandled() { + when().get("/cs/unhandled") + .then() + .statusCode(500) + .header(RESOURCE_INFO_CLASS_HEADER, nullValue()); + } + + @Test + public void csUnhandled2() { + when().get("/cs/unhandled2") + .then() + .statusCode(204) + .header(RESOURCE_INFO_CLASS_HEADER, CsResource.class.getSimpleName()); + } + + @Test + public void uniHandled() { + when().get("/uni/handled") + .then() + .statusCode(999) + .header(RESOURCE_INFO_CLASS_HEADER, UniResource.class.getSimpleName()); + } + + @Test + public void uniHandled2() { + when().get("/uni/handled2") + .then() + .statusCode(999) + .header(RESOURCE_INFO_CLASS_HEADER, UniResource.class.getSimpleName()); + } + + @Test + public void uniUnhandled() { + when().get("/uni/unhandled") + .then() + .statusCode(500) + .header(RESOURCE_INFO_CLASS_HEADER, nullValue()); + } + + @Test + public void uniUnhandled2() { + when().get("/uni/unhandled2") + .then() + .statusCode(204) + .header(RESOURCE_INFO_CLASS_HEADER, UniResource.class.getSimpleName()); + } + + public static class CustomResponseFilter { + + @ServerResponseFilter + public void filter(ContainerResponseContext responseContext, ResourceInfo resourceInfo) { + responseContext.getHeaders().add(RESOURCE_INFO_CLASS_HEADER, resourceInfo.getResourceClass().getSimpleName()); + } + } + + @Provider + public static class DummyExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(DummyException exception) { + if (exception.isHandle()) { + return Response.status(999).build(); + } + throw exception; + } + } + + public static class DummyExceptionMapper2 { + + @ServerExceptionMapper + public Response handle(DummyException2 ex) { + if (ex.isHandle()) { + return Response.status(999).build(); + } + return null; + } + } + + public static class DummyException extends RuntimeException { + + private final boolean handle; + + public DummyException(boolean handle) { + super("dummy"); + this.handle = handle; + setStackTrace(new StackTraceElement[0]); + } + + public boolean isHandle() { + return handle; + } + } + + public static class DummyException2 extends RuntimeException { + + private final boolean handle; + + public DummyException2(boolean handle) { + super("dummy2"); + this.handle = handle; + setStackTrace(new StackTraceElement[0]); + } + + public boolean isHandle() { + return handle; + } + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java index f1af73d80a1e8a..502fece9d492ba 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java @@ -16,7 +16,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti result.handle((v, t) -> { if (t != null) { - requestContext.handleException(t); + requestContext.handleException(t, true); } else { requestContext.setResult(v); } From 46a8a36090dfc3899f25a4062430c6487753eabf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 13:34:56 +0000 Subject: [PATCH 250/333] Bump flyway.version from 9.16.3 to 9.17.0 Bumps `flyway.version` from 9.16.3 to 9.17.0. Updates `flyway-core` from 9.16.3 to 9.17.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/commits/flyway-9.17.0) Updates `flyway-sqlserver` from 9.16.3 to 9.17.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/commits/flyway-9.17.0) Updates `flyway-mysql` from 9.16.3 to 9.17.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/commits/flyway-9.17.0) --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.flywaydb:flyway-sqlserver dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.flywaydb:flyway-mysql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 6ed62e74e3ceba..5661b797904567 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -164,7 +164,7 @@ 3.2.0 4.2.0 1.1.1 - 9.16.3 + 9.17.0 3.0.3 4.21.0 2.0 From cd53842503699e2e63de1e296929fd7740b27f64 Mon Sep 17 00:00:00 2001 From: Michelle Purcell Date: Mon, 8 May 2023 08:50:21 +0100 Subject: [PATCH 251/333] Fix bad vale rule that breaks linter fix fix Remove rule requiring Microsoft to precede Azure --- docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml | 1 - docs/.vale/styles/Quarkus/TermsErrors.yml | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml b/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml index 6375f16cd9cdc1..5f4c7e48d2973b 100644 --- a/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml +++ b/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml @@ -13,7 +13,6 @@ swap: '(? Date: Mon, 8 May 2023 23:10:00 +0900 Subject: [PATCH 252/333] Fix pom.xml for owasp dependency check plugin --- .../security-vulnerability-detection-concept.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/security-vulnerability-detection-concept.adoc b/docs/src/main/asciidoc/security-vulnerability-detection-concept.adoc index 6bc97616d19a40..2c23667f3e9961 100644 --- a/docs/src/main/asciidoc/security-vulnerability-detection-concept.adoc +++ b/docs/src/main/asciidoc/security-vulnerability-detection-concept.adoc @@ -93,7 +93,7 @@ To detect less severe issues, adjust the value of `failBuildOnCVSS` to suppress Suppress the false positive CPE for Smallrye Mutiny to mutiny:mutiny ]]> - ^io\.smallrye.reactive:mutiny.*:.*$ + ^io\.smallrye\.reactive:mutiny.*:.*$ cpe:/a:mutiny:mutiny @@ -102,7 +102,7 @@ To detect less severe issues, adjust the value of `failBuildOnCVSS` to suppress Suppress the false positive CPE for Smallrye Mutiny to mutiny:mutiny ]]> - ^io\.smallrye.reactive:smallrye-mutiny.*:.*$ + ^io\.smallrye\.reactive:smallrye-mutiny.*:.*$ cpe:/a:mutiny:mutiny @@ -111,7 +111,7 @@ To detect less severe issues, adjust the value of `failBuildOnCVSS` to suppress Suppress the false positive CPE for Smallrye Mutiny to mutiny:mutiny ]]> - ^io\.smallrye.reactive:vertx-mutiny.*:.*$ + ^io\.smallrye\.reactive:vertx-mutiny.*:.*$ cpe:/a:mutiny:mutiny @@ -120,7 +120,7 @@ To detect less severe issues, adjust the value of `failBuildOnCVSS` to suppress Suppress the false positive CPE for graal-sdk to GraalVM (the JVM distribution) ]]> - ^org\.graalvm\.sdk:g like this + ^org\.graalvm\.sdk:graal-sdk.*:.*$ ---- From 8e478ea3c625c835f5f02fed331b7ea6307c309c Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 8 May 2023 17:16:55 +0300 Subject: [PATCH 253/333] fix: nesting of cli plugins --- .../main/java/io/quarkus/cli/plugin/PluginCommandFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java index 1f7b13d78f06a9..af236f4f08154a 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java @@ -81,7 +81,9 @@ public void populateCommands(CommandLine cmd, Map plugins) { plugins.entrySet().stream() .map(Map.Entry::getValue).forEach(plugin -> { CommandLine current = cmd; - String name = plugin.getName(); + // The plugin is stripped from its prefix when added to the catalog. + // Let's added back to ensure it matches the CLI root command. + String name = cmd.getCommandName() + "-" + plugin.getName(); while (current != null && current.getCommandName() != null && name.startsWith(current.getCommandName() + "-")) { String remaining = name.substring(current.getCommandName().length() + 1); From 9ae0d45adb9faf22fc796947f0ecbd0d4ffc6176 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 1 Mar 2023 12:35:39 +0000 Subject: [PATCH 254/333] Add owasp dependency check action --- .github/workflows/owasp-check.yml | 40 +++++++++++++++++++++++++++++++ build-parent/pom.xml | 25 ------------------- pom.xml | 39 ++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/owasp-check.yml diff --git a/.github/workflows/owasp-check.yml b/.github/workflows/owasp-check.yml new file mode 100644 index 00000000000000..11b0c21b35d03a --- /dev/null +++ b/.github/workflows/owasp-check.yml @@ -0,0 +1,40 @@ +name: "OWASP Dependency Check" + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0,3' + +jobs: + owasp: + name: OWASP Dependency Check Report + runs-on: ubuntu-latest + if: github.repository == 'quarkusio/quarkus' + + strategy: + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + ref: main + - name: Setup Java JDK + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + + + - name: Build Java + run: ./mvnw -B --settings .github/mvn-settings.xml -Dquickly-ci install + + - name: Perform OWASP Dependency Check Report + run: ./mvnw -Dowasp-report + + - uses: actions/upload-artifact@v3 + with: + name: dependency-check-report + path: target/dependency-check-report.html + retention-days: 5 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 127f57e9c4fd58..53bf32542f623b 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -176,8 +176,6 @@ 1.1.1 - 8.2.1 - 3.1.0 @@ -719,18 +717,6 @@ - - org.owasp - dependency-check-maven - ${owasp-dependency-check-plugin.version} - - - false - false - false - false - - org.apache.maven.plugins maven-plugin-plugin @@ -1267,17 +1253,6 @@
    - - owasp-check - - - owasp-check - - - - dependency-check:check - - Windows diff --git a/pom.xml b/pom.xml index 2f5f1987fe5c6f..659e8ff68c4677 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 11 11 true - + ${env.GRAALVM_HOME} jdbc:postgresql:hibernate_orm_test @@ -66,6 +66,8 @@ false false + 8.2.1 + 0.8.10 6.5.1 @@ -149,6 +151,18 @@ quarkus-platform-bom-maven-plugin ${quarkus-platform-bom-plugin.version} + + org.owasp + dependency-check-maven + ${owasp-dependency-check-plugin.version} + + false + false + false + false + false + + @@ -356,6 +370,27 @@ + + owasp-check + + + owasp-check + + + + org.owasp:dependency-check-maven:check + + + + owasp-report + + + owasp-report + + + + org.owasp:dependency-check-maven:aggregate + + - From 4c3bbf601a05d81f19b9df0287ca9839fa9a4932 Mon Sep 17 00:00:00 2001 From: pernelkanic Date: Mon, 8 May 2023 18:25:09 +0000 Subject: [PATCH 255/333] replaced return values --- .../datasource/value/ReactiveTransactionalValueCommands.java | 1 + .../java/io/quarkus/redis/datasource/value/ValueCommands.java | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ReactiveTransactionalValueCommands.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ReactiveTransactionalValueCommands.java index ab7bc9768d25f4..b6b1c20cc77173 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ReactiveTransactionalValueCommands.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ReactiveTransactionalValueCommands.java @@ -286,6 +286,7 @@ public interface ReactiveTransactionalValueCommands extends ReactiveTransa * * @param key the key * @param value the value + * @return A {@code Uni} emitting {@code null} when the command has been enqueued successfully in the transaction, a failure */ Uni setex(K key, long seconds, V value); diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ValueCommands.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ValueCommands.java index 48ad3e81489f76..19d96700cfa69d 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ValueCommands.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/value/ValueCommands.java @@ -195,7 +195,6 @@ public interface ValueCommands extends RedisCommands { * Requires Redis 1.0.1 * * @param map the key/value map containing the items to store - * @return a Uni producing a {@code null} item on success, a failure otherwise **/ void mset(Map map); @@ -219,7 +218,6 @@ public interface ValueCommands extends RedisCommands { * @param key the key * @param milliseconds the duration in ms * @param value the value - * @return a Uni producing a {@code null} item on success, a failure otherwise **/ void psetex(K key, long milliseconds, V value); @@ -231,7 +229,6 @@ public interface ValueCommands extends RedisCommands { * * @param key the key * @param value the value - * @return a Uni producing a {@code null} item on success, a failure otherwise **/ void set(K key, V value); @@ -244,7 +241,6 @@ public interface ValueCommands extends RedisCommands { * @param key the key * @param value the value * @param setArgs the set command extra-arguments - * @return a Uni producing a {@code null} item on success, a failure otherwise **/ void set(K key, V value, SetArgs setArgs); From c1cd73973c5c5d7e9bfcccd4904c0c66fef46c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Zangh=C3=AC?= Date: Mon, 1 May 2023 17:57:05 +0200 Subject: [PATCH 256/333] fix #33009 --- .../main/resources/dev-ui/qui/qui-badge.js | 1 - .../resources/dev-ui/qwc/qwc-extensions.js | 81 +++++++++++-------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-badge.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-badge.js index 56897542e76493..0e84448a5c485c 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-badge.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qui/qui-badge.js @@ -95,7 +95,6 @@ export class QuiBadge extends LitElement { this.background = null; this.color = null; this.small = false; - this.small = false; this.primary = false; this.pill = false; this.clickable = false; diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js index 81af272f9568ac..e1710c6b3ce2be 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js @@ -5,6 +5,8 @@ import { devuiState } from 'devui-state'; import { observeState } from 'lit-element-state'; import 'qwc/qwc-extension.js'; import 'qwc/qwc-extension-link.js'; +import 'qui-badge'; + /** * This component create cards of all the extensions @@ -40,7 +42,10 @@ export class QwcExtensions extends observeState(LitElement) { flex-flow: column wrap; padding-top: 5px; } - `; + .float-right { + align-self: flex-end; + } + `; render() { return html`
    @@ -48,15 +53,15 @@ export class QwcExtensions extends observeState(LitElement) { ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))}
    `; } - + _renderActive(extension){ - extension.cardPages.forEach(page => { + extension.cardPages.forEach(page => { if(page.embed){ // we need to register with the router import(page.componentRef); this.routerController.addRouteForExtension(page); } }); - + return html` `; - + return html`${unsafeHTML(customCardCode)}`; - + } _renderDefaultCardContent(extension){ - - return html`
    - ${extension.description} - ${this._renderCardLinks(extension)} -
    `; + return html` +
    + + ${this._renderExperimentalBadge(extension)} + ${extension.description} + + ${this._renderCardLinks(extension)} +
    `; } _renderCardLinks(extension){ - + return html`${extension.cardPages.map(page => html` -
    - ${extension.description} -
    -
    - `; + return html` +
    + ${this._renderExperimentalBadge(extension)} + ${extension.description} +
    +
    `; + } + } + + _renderExperimentalBadge(extension){ + if(extension.status === "experimental") { + return html`EXPERIMENTAL`; } } From bcfe472b3cfb7e2c24a8fe0c058ce308ab9c454c Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 May 2023 14:55:58 +0300 Subject: [PATCH 257/333] Introduce a way to set headers and status code for streaming response Relates to: #33130 --- docs/src/main/asciidoc/resteasy-reactive.adoc | 32 ++++++++ .../JaxrsClientReactiveProcessor.java | 6 +- .../ResteasyReactiveJacksonProcessor.java | 1 + .../test/streams/StreamResource.java | 8 +- .../ResteasyReactiveJaxbProcessor.java | 1 + .../deployment/LinksContainerFactory.java | 2 + .../deployment/ResteasyReactiveProcessor.java | 2 + .../test/headers/ResponseHeaderTest.java | 53 +++++++++++++ .../test/status/ResponseStatusTest.java | 32 ++++++++ .../common/processor/EndpointIndexer.java | 2 + .../processor/ResteasyReactiveDotNames.java | 2 + .../jboss/resteasy/reactive/RestMulti.java | 77 +++++++++++++++++++ .../scanning/AsyncReturnTypeScanner.java | 8 +- .../reactive/server/core/SseUtil.java | 2 - .../reactive/server/core/StreamingUtil.java | 1 - .../handlers/PublisherResponseHandler.java | 29 ++++++- 16 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index bbc232ca45c4e0..85bc78ead1d7e0 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -933,6 +933,38 @@ impression that you can set headers or HTTP status codes, which is not true afte response. Exception mappers are also not invoked because part of the response may already have been written. +[TIP] +==== +If you need to set custom HTTP headers and / or the HTTP response, then you can return `org.jboss.resteasy.reactive.RestMulti` instead, like this: + +[source,java] +---- +package org.acme.rest; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.eclipse.microprofile.reactive.messaging.Channel; + +import io.smallrye.mutiny.Multi; +import org.jboss.resteasy.reactive.RestMulti; + +@Path("logs") +public class Endpoint { + + @Inject + @Channel("log-out") + Multi logs; + + @GET + public Multi streamLogs() { + return RestMulti.from(logs).status(222).header("foo", "bar").build(); + } +} +---- +==== + === Server-Sent Event (SSE) support If you want to stream JSON objects in your response, you can use diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 4887dd1ae1a9a7..ff75d278888e03 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -14,6 +14,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OBJECT; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PART_TYPE_NAME; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.UNI; import java.io.Closeable; @@ -204,7 +205,7 @@ public class JaxrsClientReactiveProcessor { private static final DotName NOT_BODY = DotName.createSimple("io.quarkus.rest.client.reactive.NotBody"); - private static final Set ASYNC_RETURN_TYPES = Set.of(COMPLETION_STAGE, UNI, MULTI); + private static final Set ASYNC_RETURN_TYPES = Set.of(COMPLETION_STAGE, UNI, MULTI, REST_MULTI); public static final DotName BYTE = DotName.createSimple(Byte.class.getName()); public static final MethodDescriptor MULTIPART_RESPONSE_DATA_ADD_FILLER = MethodDescriptor .ofMethod(MultipartResponseDataBase.class, "addFiller", void.class, FieldFiller.class); @@ -1937,7 +1938,7 @@ private void handleReturn(ClassInfo restClientInterface, String defaultMediaType if (ASYNC_RETURN_TYPES.contains(paramType.name())) { returnCategory = paramType.name().equals(COMPLETION_STAGE) ? ReturnCategory.COMPLETION_STAGE - : paramType.name().equals(MULTI) + : paramType.name().equals(MULTI) || paramType.name().equals(REST_MULTI) ? ReturnCategory.MULTI : ReturnCategory.UNI; @@ -2738,6 +2739,7 @@ private enum ReturnCategory { COMPLETION_STAGE, UNI, MULTI, + REST_MULTI, COROUTINE } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index 679f4a3fbc915b..a2187966147cf4 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -308,6 +308,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r effectiveReturnType.name().equals(ResteasyReactiveDotNames.UNI) || effectiveReturnType.name().equals(ResteasyReactiveDotNames.COMPLETABLE_FUTURE) || effectiveReturnType.name().equals(ResteasyReactiveDotNames.COMPLETION_STAGE) || + effectiveReturnType.name().equals(ResteasyReactiveDotNames.REST_MULTI) || effectiveReturnType.name().equals(ResteasyReactiveDotNames.MULTI)) { if (effectiveReturnType.kind() != Type.Kind.PARAMETERIZED_TYPE) { continue; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java index b6a32a40456d0b..facd204287c1bf 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java @@ -13,6 +13,7 @@ import jakarta.ws.rs.sse.SseBroadcaster; import jakarta.ws.rs.sse.SseEventSink; +import org.jboss.resteasy.reactive.RestMulti; import org.jboss.resteasy.reactive.RestStreamElementType; import org.jboss.resteasy.reactive.common.util.RestMediaType; @@ -97,7 +98,8 @@ public void sseJson2(Sse sse, SseEventSink sink) throws IOException { @GET @RestStreamElementType(MediaType.APPLICATION_JSON) public Multi multiJson() { - return Multi.createFrom().items(new Message("hello"), new Message("stef")); + return RestMulti.from(Multi.createFrom().items(new Message("hello"), new Message("stef"))) + .header("foo", "bar").build(); } @Path("json/multi2") @@ -135,9 +137,9 @@ public Multi multiStreamJsonFast() { for (int i = 0; i < 5000; i++) { ids.add(UUID.randomUUID()); } - return Multi.createFrom().items(ids::stream) + return RestMulti.from(Multi.createFrom().items(ids::stream) .onItem().transform(id -> new Message(id.toString())) - .onOverflow().buffer(81920); + .onOverflow().buffer(81920)).header("foo", "bar").build(); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/main/java/io/quarkus/resteasy/reactive/jaxb/deployment/ResteasyReactiveJaxbProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/main/java/io/quarkus/resteasy/reactive/jaxb/deployment/ResteasyReactiveJaxbProcessor.java index 56b59165cf6c8c..4754235d71973e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/main/java/io/quarkus/resteasy/reactive/jaxb/deployment/ResteasyReactiveJaxbProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/main/java/io/quarkus/resteasy/reactive/jaxb/deployment/ResteasyReactiveJaxbProcessor.java @@ -158,6 +158,7 @@ private ClassInfo getEffectiveClassInfo(Type type, IndexView indexView) { effectiveType.name().equals(ResteasyReactiveDotNames.UNI) || effectiveType.name().equals(ResteasyReactiveDotNames.COMPLETABLE_FUTURE) || effectiveType.name().equals(ResteasyReactiveDotNames.COMPLETION_STAGE) || + effectiveType.name().equals(ResteasyReactiveDotNames.REST_MULTI) || effectiveType.name().equals(ResteasyReactiveDotNames.MULTI)) { if (effectiveType.kind() != Type.Kind.PARAMETERIZED_TYPE) { return null; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java index 5081617941c735..9c4ddafd41688e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-links/deployment/src/main/java/io/quarkus/resteasy/reactive/links/deployment/LinksContainerFactory.java @@ -3,6 +3,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COMPLETABLE_FUTURE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COMPLETION_STAGE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_RESPONSE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.UNI; @@ -161,6 +162,7 @@ private Type getNonAsyncReturnType(Type returnType) { || COMPLETABLE_FUTURE.equals(parameterizedType.name()) || UNI.equals(parameterizedType.name()) || MULTI.equals(parameterizedType.name()) + || REST_MULTI.equals(parameterizedType.name()) || REST_RESPONSE.equals(parameterizedType.name())) { return parameterizedType.arguments().get(0); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index a758d2a5201485..439bceb1405ff3 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -7,6 +7,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.DATE_FORMAT; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUBLISHER; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MULTI; import java.io.File; import java.io.IOException; @@ -553,6 +554,7 @@ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { if (paramsRequireReflection || MULTI.toString().equals(entry.getResourceMethod().getSimpleReturnType()) || + REST_MULTI.toString().equals(entry.getResourceMethod().getSimpleReturnType()) || PUBLISHER.toString().equals(entry.getResourceMethod().getSimpleReturnType()) || filtersAccessResourceMethod(resourceInterceptorsBuildItem.getResourceInterceptors()) || entry.additionalRegisterClassForReflectionCheck()) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java index eb0f85732cff5f..41e19c703830de 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java @@ -7,11 +7,14 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import org.jboss.resteasy.reactive.ResponseHeader; import org.jboss.resteasy.reactive.ResponseStatus; +import org.jboss.resteasy.reactive.RestMulti; +import org.jboss.resteasy.reactive.RestQuery; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -118,6 +121,40 @@ public void testStringThrowsException() { assertFalse(headers.hasHeaderWithName("Access-Control-Allow-Origin")); } + @Test + public void testReturnRestMulti() { + Map expectedHeaders = Map.of( + "Access-Control-Allow-Origin", "foo", + "Keep-Alive", "bar"); + RestAssured + .given() + .get("/test/rest-multi") + .then() + .statusCode(200) + .headers(expectedHeaders); + } + + @Test + public void testReturnRestMulti2() { + RestAssured + .given() + .get("/test/rest-multi2") + .then() + .statusCode(200) + .headers(Map.of( + "Access-Control-Allow-Origin", "foo", + "Keep-Alive", "bar")); + + RestAssured + .given() + .get("/test/rest-multi2?keepAlive=dummy") + .then() + .statusCode(200) + .headers(Map.of( + "Access-Control-Allow-Origin", "foo", + "Keep-Alive", "dummy")); + } + @Path("/test") public static class TestResource { @@ -190,6 +227,22 @@ public String throwExceptionPlain() { throw createException(); } + @ResponseHeader(name = "Access-Control-Allow-Origin", value = "*") + @ResponseHeader(name = "Keep-Alive", value = "timeout=5, max=997") + @GET + @Path("/rest-multi") + public RestMulti getTestRestMulti() { + return RestMulti.from(Multi.createFrom().item("test")).header("Access-Control-Allow-Origin", "foo") + .header("Keep-Alive", "bar").build(); + } + + @GET + @Path("/rest-multi2") + public RestMulti getTestRestMulti2(@DefaultValue("bar") @RestQuery String keepAlive) { + return RestMulti.from(Multi.createFrom().item("test")).header("Access-Control-Allow-Origin", "foo") + .header("Keep-Alive", keepAlive).build(); + } + private IllegalArgumentException createException() { IllegalArgumentException result = new IllegalArgumentException(); result.setStackTrace(EMPTY_STACK_TRACE); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java index a8496f9d1d8b9a..6321d4a7598bb1 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java @@ -7,6 +7,7 @@ import jakarta.ws.rs.Path; import org.jboss.resteasy.reactive.ResponseStatus; +import org.jboss.resteasy.reactive.RestMulti; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -93,6 +94,24 @@ public void testStringThrowsException() { .statusCode(500); } + @Test + public void testReturnRestMulti() { + RestAssured + .given() + .get("/test/rest-multi") + .then() + .statusCode(210); + } + + @Test + public void testReturnRestMulti2() { + RestAssured + .given() + .get("/test/rest-multi2") + .then() + .statusCode(211); + } + @Path("/test") public static class TestResource { @@ -154,6 +173,19 @@ public String throwExceptionPlain() { throw createException(); } + @ResponseStatus(202) + @GET + @Path("/rest-multi") + public RestMulti getTestRestMulti() { + return RestMulti.from(Multi.createFrom().item("test")).status(210).build(); + } + + @GET + @Path("/rest-multi2") + public RestMulti getTestRestMulti2() { + return RestMulti.from(Multi.createFrom().item("test")).status(211).build(); + } + private IllegalArgumentException createException() { IllegalArgumentException result = new IllegalArgumentException(); result.setStackTrace(EMPTY_STACK_TRACE); diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index ae927ba11f923a..e52a18d68850aa 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -57,6 +57,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_HEADER_PARAM; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MATRIX_PARAM; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_PATH_PARAM; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_QUERY_PARAM; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_RESPONSE; @@ -990,6 +991,7 @@ private Type getNonAsyncReturnType(Type returnType) { || COMPLETABLE_FUTURE.equals(parameterizedType.name()) || UNI.equals(parameterizedType.name()) || MULTI.equals(parameterizedType.name()) + || REST_MULTI.equals(parameterizedType.name()) || REST_RESPONSE.equals(parameterizedType.name())) { return parameterizedType.arguments().get(0); } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java index dd8e13857d185b..191449b5533108 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java @@ -88,6 +88,7 @@ import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestHeader; import org.jboss.resteasy.reactive.RestMatrix; +import org.jboss.resteasy.reactive.RestMulti; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; import org.jboss.resteasy.reactive.RestResponse; @@ -202,6 +203,7 @@ public final class ResteasyReactiveDotNames { public static final DotName UNI = DotName.createSimple(Uni.class.getName()); public static final DotName MULTI = DotName.createSimple(Multi.class.getName()); + public static final DotName REST_MULTI = DotName.createSimple(RestMulti.class.getName()); public static final DotName COMPLETION_STAGE = DotName.createSimple(CompletionStage.class.getName()); public static final DotName COMPLETABLE_FUTURE = DotName.createSimple(CompletableFuture.class.getName()); public static final DotName PUBLISHER = DotName.createSimple(Publisher.class.getName()); diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java new file mode 100644 index 00000000000000..34108bf1db7505 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java @@ -0,0 +1,77 @@ +package org.jboss.resteasy.reactive; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jboss.resteasy.reactive.common.util.CaseInsensitiveMap; +import org.jboss.resteasy.reactive.common.util.MultivaluedTreeMap; + +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.infrastructure.Infrastructure; +import io.smallrye.mutiny.operators.AbstractMulti; +import io.smallrye.mutiny.subscription.MultiSubscriber; + +/** + * A wrapper around {@link Multi} that gives resource methods a way to specify the HTTP status code and HTTP headers + * when streaming a result. + */ +public class RestMulti extends AbstractMulti { + + private final Multi multi; + private final Integer status; + private final MultivaluedTreeMap headers; + + public static RestMulti.Builder from(Multi multi) { + return new RestMulti.Builder<>(multi); + } + + private RestMulti(Builder builder) { + this.multi = builder.multi; + this.status = builder.status; + this.headers = builder.headers; + } + + @Override + public void subscribe(MultiSubscriber subscriber) { + multi.subscribe(Infrastructure.onMultiSubscription(multi, subscriber)); + } + + public Integer getStatus() { + return status; + } + + public Map> getHeaders() { + return headers; + } + + public static class Builder { + private final Multi multi; + + private Integer status; + + private final MultivaluedTreeMap headers = new CaseInsensitiveMap<>(); + + private Builder(Multi multi) { + this.multi = Objects.requireNonNull(multi, "multi cannot be null"); + } + + public Builder status(int status) { + this.status = status; + return this; + } + + public Builder header(String name, String value) { + if (value == null) { + headers.remove(name); + return this; + } + headers.add(name, value); + return this; + } + + public RestMulti build() { + return new RestMulti<>(this); + } + } +} diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/AsyncReturnTypeScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/AsyncReturnTypeScanner.java index 5cf0e9ce56ad48..53393c76317b64 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/AsyncReturnTypeScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/AsyncReturnTypeScanner.java @@ -5,6 +5,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LEGACY_PUBLISHER; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUBLISHER; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.UNI; import java.util.Collections; @@ -33,7 +34,8 @@ public List scan(MethodInfo method, ClassInfo actualEndp return Collections.singletonList(new FixedHandlerChainCustomizer(new UniResponseHandler(), HandlerChainCustomizer.Phase.AFTER_METHOD_INVOKE)); } - if (returnTypeName.equals(MULTI) || returnTypeName.equals(PUBLISHER) || returnTypeName.equals(LEGACY_PUBLISHER)) { + if (returnTypeName.equals(MULTI) || returnTypeName.equals(REST_MULTI) || returnTypeName.equals(PUBLISHER) + || returnTypeName.equals(LEGACY_PUBLISHER)) { return Collections.singletonList(new FixedHandlerChainCustomizer(new PublisherResponseHandler(), HandlerChainCustomizer.Phase.AFTER_METHOD_INVOKE)); } @@ -44,7 +46,7 @@ public List scan(MethodInfo method, ClassInfo actualEndp public boolean isMethodSignatureAsync(MethodInfo method) { DotName returnTypeName = method.returnType().name(); return returnTypeName.equals(COMPLETION_STAGE) || returnTypeName.equals(COMPLETABLE_FUTURE) || - returnTypeName.equals(UNI) || returnTypeName.equals(MULTI) || returnTypeName.equals(PUBLISHER) || - returnTypeName.equals(LEGACY_PUBLISHER); + returnTypeName.equals(UNI) || returnTypeName.equals(MULTI) || returnTypeName.equals(REST_MULTI) || + returnTypeName.equals(PUBLISHER) || returnTypeName.equals(LEGACY_PUBLISHER); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java index edef2f560405d0..c6f1aed6baf717 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java @@ -165,8 +165,6 @@ public static void setHeaders(ResteasyReactiveRequestContext context, ServerHttp for (int i = 0; i < customizers.size(); i++) { customizers.get(i).customize(response); } - // FIXME: other headers? - } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java index 8140b79ca2f394..b32281d6833d7c 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java @@ -88,7 +88,6 @@ public static void setHeaders(ResteasyReactiveRequestContext context, ServerHttp for (int i = 0; i < customizers.size(); i++) { customizers.get(i).customize(response); } - // FIXME: other headers? } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java index d10f6fcfdaecd0..98aefaf1c2f9ba 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java @@ -3,6 +3,7 @@ import static org.jboss.resteasy.reactive.server.jaxrs.SseEventSinkImpl.EMPTY_BUFFER; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -17,6 +18,7 @@ import jakarta.ws.rs.sse.OutboundSseEvent; import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.RestMulti; import org.jboss.resteasy.reactive.common.util.RestMediaType; import org.jboss.resteasy.reactive.common.util.ServerMediaType; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; @@ -294,14 +296,37 @@ private boolean requiresChunkedStream(MediaType mediaType) { } private void handleChunkedStreaming(ResteasyReactiveRequestContext requestContext, Publisher result, boolean json) { - result.subscribe(new ChunkedStreamingMultiSubscriber(requestContext, streamingResponseCustomizers, json)); + result.subscribe(new ChunkedStreamingMultiSubscriber(requestContext, determineCustomizers(result), json)); + } + + private List determineCustomizers(Publisher publisher) { + if (publisher instanceof RestMulti) { + RestMulti restMulti = (RestMulti) publisher; + Map> headers = restMulti.getHeaders(); + Integer status = restMulti.getStatus(); + if (headers.isEmpty() && (status == null)) { + return streamingResponseCustomizers; + } + List result = new ArrayList<>(streamingResponseCustomizers.size() + 2); + result.addAll(streamingResponseCustomizers); // these are added first so that the result specific values will take precedence if there are conflicts + if (!headers.isEmpty()) { + result.add(new StreamingResponseCustomizer.AddHeadersCustomizer(headers)); + } + if (status != null) { + result.add(new StreamingResponseCustomizer.StatusCustomizer(status)); + } + return result; + } + + return streamingResponseCustomizers; } private void handleStreaming(ResteasyReactiveRequestContext requestContext, Publisher result, boolean json) { - result.subscribe(new StreamingMultiSubscriber(requestContext, streamingResponseCustomizers, json)); + result.subscribe(new StreamingMultiSubscriber(requestContext, determineCustomizers(result), json)); } private void handleSse(ResteasyReactiveRequestContext requestContext, Publisher result) { + List streamingResponseCustomizers = determineCustomizers(result); SseUtil.setHeaders(requestContext, requestContext.serverResponse(), streamingResponseCustomizers); requestContext.suspend(); requestContext.serverResponse().write(EMPTY_BUFFER, new Consumer() { From 557058a52dca1e7a54cecc4978ddf48e8b17a22a Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 9 May 2023 10:50:41 +0200 Subject: [PATCH 258/333] Ignore malformed language ranges when resolving locales for validation --- .../locale/AbstractLocaleResolver.java | 21 ++++++++++++++----- .../SmallRyeGraphQLLocaleResolver.java | 14 +++++++++++-- .../HibernateValidatorFunctionalityTest.java | 10 +++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/locale/AbstractLocaleResolver.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/locale/AbstractLocaleResolver.java index 9e5412140892de..2c5674b07e85e8 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/locale/AbstractLocaleResolver.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/locale/AbstractLocaleResolver.java @@ -7,20 +7,23 @@ import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; import org.hibernate.validator.spi.messageinterpolation.LocaleResolverContext; +import org.jboss.logging.Logger; abstract class AbstractLocaleResolver implements LocaleResolver { + private static final Logger log = Logger.getLogger(AbstractLocaleResolver.class); + private static final String ACCEPT_HEADER = "Accept-Language"; + protected abstract Map> getHeaders(); @Override public Locale resolve(LocaleResolverContext context) { Optional> localePriorities = getAcceptableLanguages(); - if (!localePriorities.isPresent()) { + if (localePriorities.isEmpty()) { return null; } - List resolvedLocales = Locale.filter(localePriorities.get(), context.getSupportedLocales()); - if (resolvedLocales.size() > 0) { + if (!resolvedLocales.isEmpty()) { return resolvedLocales.get(0); } @@ -30,9 +33,17 @@ public Locale resolve(LocaleResolverContext context) { private Optional> getAcceptableLanguages() { Map> httpHeaders = getHeaders(); if (httpHeaders != null) { - List acceptLanguageList = httpHeaders.get("Accept-Language"); + List acceptLanguageList = httpHeaders.get(ACCEPT_HEADER); if (acceptLanguageList != null && !acceptLanguageList.isEmpty()) { - return Optional.of(Locale.LanguageRange.parse(acceptLanguageList.get(0))); + try { + return Optional.of(Locale.LanguageRange.parse(acceptLanguageList.get(0))); + } catch (IllegalArgumentException e) { + // this can happen when parsing malformed locale range string + if (log.isDebugEnabled()) { + log.debug("Unable to parse the \"Accept-Language\" header. \"" + acceptLanguageList.get(0) + + "\" is not a valid language range string.", e); + } + } } } diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLLocaleResolver.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLLocaleResolver.java index f4a1f658dc0dc8..d34dc2d8678bd9 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLLocaleResolver.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLLocaleResolver.java @@ -9,6 +9,7 @@ import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; import org.hibernate.validator.spi.messageinterpolation.LocaleResolverContext; +import org.jboss.logging.Logger; import graphql.schema.DataFetchingEnvironment; import io.smallrye.graphql.execution.context.SmallRyeContext; @@ -20,12 +21,13 @@ @Singleton public class SmallRyeGraphQLLocaleResolver implements LocaleResolver { + private static final Logger log = Logger.getLogger(SmallRyeGraphQLLocaleResolver.class); private static final String ACCEPT_HEADER = "Accept-Language"; @Override public Locale resolve(LocaleResolverContext context) { Optional> localePriorities = getAcceptableLanguages(); - if (!localePriorities.isPresent()) { + if (localePriorities.isEmpty()) { return null; } List resolvedLocales = Locale.filter(localePriorities.get(), context.getSupportedLocales()); @@ -41,7 +43,15 @@ private Optional> getAcceptableLanguages() { if (httpHeaders != null) { List acceptLanguageList = httpHeaders.get(ACCEPT_HEADER); if (acceptLanguageList != null && !acceptLanguageList.isEmpty()) { - return Optional.of(Locale.LanguageRange.parse(acceptLanguageList.get(0))); + try { + return Optional.of(Locale.LanguageRange.parse(acceptLanguageList.get(0))); + } catch (IllegalArgumentException e) { + // this can happen when parsing malformed locale range string + if (log.isDebugEnabled()) { + log.debug("Unable to parse the \"Accept-Language\" header. \"" + acceptLanguageList.get(0) + + "\" is not a valid language range string.", e); + } + } } } return Optional.empty(); diff --git a/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java b/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java index e8ffd244f8475d..880841201ec359 100644 --- a/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java +++ b/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java @@ -367,6 +367,16 @@ public void testValidationMessageLocale() { .body(containsString("Vrijednost ne zadovoljava uzorak")); } + @Test + public void testValidationMessageInvalidAcceptLanguageHeaderLeadsToDefaultLocaleBeingUsed() { + RestAssured.given() + .header("Accept-Language", "invalid string") + .when() + .get("/hibernate-validator/test/test-validation-message-locale/1") + .then() + .body(containsString("Value is not in line with the pattern")); + } + @Test public void testValidationMessageDefaultLocale() { RestAssured.given() From 0e313b6df5bd6f47e91230e1524e4f10e17f4a1f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 May 2023 19:35:29 +0300 Subject: [PATCH 259/333] Introduce an async variant of RestMulti Closes: #26523 --- docs/src/main/asciidoc/resteasy-reactive.adoc | 53 +++- .../test/streams/StreamResource.java | 47 ++- .../test/streams/StreamTestCase.java | 14 +- .../test/headers/ResponseHeaderTest.java | 62 +++- .../test/status/ResponseStatusTest.java | 49 ++- .../jboss/resteasy/reactive/RestMulti.java | 280 +++++++++++++++--- .../handlers/PublisherResponseHandler.java | 80 ++--- 7 files changed, 503 insertions(+), 82 deletions(-) diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index 85bc78ead1d7e0..2c468c5113f149 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -933,8 +933,7 @@ impression that you can set headers or HTTP status codes, which is not true afte response. Exception mappers are also not invoked because part of the response may already have been written. -[TIP] -==== +==== Customizing headers and status If you need to set custom HTTP headers and / or the HTTP response, then you can return `org.jboss.resteasy.reactive.RestMulti` instead, like this: [source,java] @@ -959,11 +958,52 @@ public class Endpoint { @GET public Multi streamLogs() { - return RestMulti.from(logs).status(222).header("foo", "bar").build(); + return RestMulti.fromMultiData(logs).status(222).header("foo", "bar").build(); + } +} +---- + +In more advanced cases where the headers and / or status can only be obtained from the results of an async call, the `RestMulti.fromUniResponse` needs to be used. +Here is an example of such a use case: + +[source,java] +---- +package org.acme.rest; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import java.util.List;import java.util.Map;import org.eclipse.microprofile.reactive.messaging.Channel; + +import io.smallrye.mutiny.Multi; +import org.jboss.resteasy.reactive.RestMulti; + +@Path("logs") +public class Endpoint { + + interface SomeService { + Uni get(); + } + + interface SomeResponse { + Multi data; + + String myHeader(); + } + + private final SomeService someService; + + public Endpoint(SomeService someService) { + this.someService = someService; + } + + @GET + public Multi streamLogs() { + return RestMulti.fromUniResponse(someService.get(), SomeResponse::data, (r -> Map.of("MyHeader", List.of(r.myHeader())))); } } ---- -==== === Server-Sent Event (SSE) support @@ -1054,6 +1094,11 @@ public class Endpoint { <3> Set the event name, i.e. the value of the `event` field of a SSE message. <4> Set the data, i.e. the value of the `data` field of a SSE message. +[WARNING] +==== +Manipulation of the returned HTTP headers and status code is not possible via `RestMulti.fromUniResponse` because when returning SSE responses the headers and status code cannot be delayed until the response becomes available. +==== + === Controlling HTTP Caching features RESTEasy Reactive provides the link:{resteasy-reactive-common-api}/org/jboss/resteasy/reactive/Cache.html[`@Cache`] diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java index facd204287c1bf..dfc905597b3384 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamResource.java @@ -1,8 +1,10 @@ package io.quarkus.resteasy.reactive.jackson.deployment.test.streams; import java.io.IOException; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import jakarta.ws.rs.GET; @@ -19,6 +21,7 @@ import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; @Path("streams") public class StreamResource { @@ -98,7 +101,7 @@ public void sseJson2(Sse sse, SseEventSink sink) throws IOException { @GET @RestStreamElementType(MediaType.APPLICATION_JSON) public Multi multiJson() { - return RestMulti.from(Multi.createFrom().items(new Message("hello"), new Message("stef"))) + return RestMulti.fromMultiData(Multi.createFrom().items(new Message("hello"), new Message("stef"))) .header("foo", "bar").build(); } @@ -112,11 +115,24 @@ public Multi multiDefaultElementType() { @Path("ndjson/multi") @GET @Produces(RestMediaType.APPLICATION_NDJSON) - @RestStreamElementType(MediaType.APPLICATION_JSON) public Multi multiNdJson() { return Multi.createFrom().items(new Message("hello"), new Message("stef")); } + @Path("ndjson/multi2") + @GET + @Produces(RestMediaType.APPLICATION_NDJSON) + public Multi multiNdJson2() { + + return RestMulti.fromUniResponse( + Uni.createFrom().item( + () -> new Wrapper(Multi.createFrom().items(new Message("hello"), new Message("stef")), + new AbstractMap.SimpleEntry<>("foo", "bar"), 222)), + Wrapper::getData, + Wrapper::getHeaders, + Wrapper::getStatus); + } + @Path("stream-json/multi") @GET @Produces(RestMediaType.APPLICATION_STREAM_JSON) @@ -137,9 +153,34 @@ public Multi multiStreamJsonFast() { for (int i = 0; i < 5000; i++) { ids.add(UUID.randomUUID()); } - return RestMulti.from(Multi.createFrom().items(ids::stream) + return RestMulti.fromMultiData(Multi.createFrom().items(ids::stream) .onItem().transform(id -> new Message(id.toString())) .onOverflow().buffer(81920)).header("foo", "bar").build(); } + private static final class Wrapper { + public final Multi data; + + public final Map> headers; + private final Integer status; + + public Wrapper(Multi data, Map.Entry header, Integer status) { + this.data = data; + this.status = status; + this.headers = Map.of(header.getKey(), List.of(header.getValue())); + } + + public Multi getData() { + return data; + } + + public Map> getHeaders() { + return headers; + } + + public Integer getStatus() { + return status; + } + } + } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java index 7414d57504e3ae..8c7704d24c472f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java @@ -128,6 +128,18 @@ public void testNdJsonMultiFromMulti() { .header(HttpHeaders.CONTENT_TYPE, containsString(RestMediaType.APPLICATION_NDJSON)); } + @Test + public void testNdJsonMultiFromMulti2() { + when().get(uri.toString() + "streams/ndjson/multi2") + .then().statusCode(222) + // @formatter:off + .body(is("{\"name\":\"hello\"}\n" + + "{\"name\":\"stef\"}\n")) + // @formatter:on + .header(HttpHeaders.CONTENT_TYPE, containsString(RestMediaType.APPLICATION_NDJSON)) + .header("foo", "bar"); + } + @Test public void testStreamJsonMultiFromMulti() { when().get(uri.toString() + "streams/stream-json/multi") @@ -141,7 +153,7 @@ public void testStreamJsonMultiFromMulti() { private void testJsonMulti(String path) { Client client = ClientBuilder.newBuilder().register(new JacksonBasicMessageBodyReader(new ObjectMapper())).build(); - ; + WebTarget target = client.target(uri.toString() + path); Multi multi = target.request().rx(MultiInvoker.class).get(Message.class); List list = multi.collect().asList().await().atMost(Duration.ofSeconds(30)); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java index 41e19c703830de..6b9774f5c16716 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java @@ -1,8 +1,10 @@ package io.quarkus.resteasy.reactive.server.test.headers; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -10,6 +12,7 @@ import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import org.jboss.resteasy.reactive.ResponseHeader; import org.jboss.resteasy.reactive.ResponseStatus; @@ -155,6 +158,27 @@ public void testReturnRestMulti2() { "Keep-Alive", "dummy")); } + @Test + public void testReturnRestMulti3() { + RestAssured + .given() + .get("/test/rest-multi3") + .then() + .statusCode(200) + .headers(Map.of( + "header1", "foo", + "header2", "bar")); + + RestAssured + .given() + .get("/test/rest-multi3?h1=h1&h2=h2") + .then() + .statusCode(200) + .headers(Map.of( + "header1", "h1", + "header2", "h2")); + } + @Path("/test") public static class TestResource { @@ -232,21 +256,53 @@ public String throwExceptionPlain() { @GET @Path("/rest-multi") public RestMulti getTestRestMulti() { - return RestMulti.from(Multi.createFrom().item("test")).header("Access-Control-Allow-Origin", "foo") + return RestMulti.fromMultiData(Multi.createFrom().item("test")).header("Access-Control-Allow-Origin", "foo") .header("Keep-Alive", "bar").build(); } @GET @Path("/rest-multi2") public RestMulti getTestRestMulti2(@DefaultValue("bar") @RestQuery String keepAlive) { - return RestMulti.from(Multi.createFrom().item("test")).header("Access-Control-Allow-Origin", "foo") + return RestMulti.fromMultiData(Multi.createFrom().item("test")).header("Access-Control-Allow-Origin", "foo") .header("Keep-Alive", keepAlive).build(); } + @GET + @Path("/rest-multi3") + @Produces("application/octet-stream") + public RestMulti getTestRestMulti3(@DefaultValue("foo") @RestQuery("h1") String header1, + @DefaultValue("bar") @RestQuery("h2") String header2) { + return RestMulti.fromUniResponse(getWrapper(header1, header2), Wrapper::getData, Wrapper::getHeaders); + } + private IllegalArgumentException createException() { IllegalArgumentException result = new IllegalArgumentException(); result.setStackTrace(EMPTY_STACK_TRACE); return result; } + + private Uni getWrapper(String header1, String header2) { + return Uni.createFrom().item( + () -> new Wrapper(Multi.createFrom().item("test".getBytes(StandardCharsets.UTF_8)), header1, header2)); + } + + private static final class Wrapper { + public final Multi data; + + public final Map> headers; + + public Wrapper(Multi data, String header1, String header2) { + this.data = data; + this.headers = Map.of("header1", List.of(header1), "header2", List.of(header2)); + } + + public Multi getData() { + return data; + } + + public Map> getHeaders() { + return headers; + } + } } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java index 6321d4a7598bb1..d41dbd0aea057a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/status/ResponseStatusTest.java @@ -3,11 +3,13 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import org.jboss.resteasy.reactive.ResponseStatus; import org.jboss.resteasy.reactive.RestMulti; +import org.jboss.resteasy.reactive.RestQuery; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -112,6 +114,36 @@ public void testReturnRestMulti2() { .statusCode(211); } + @Test + public void testReturnRestMulti3() { + RestAssured + .given() + .get("/test/rest-multi3") + .then() + .statusCode(200); + + RestAssured + .given() + .get("/test/rest-multi3?status=212") + .then() + .statusCode(212); + } + + @Test + public void testReturnRestMulti4() { + RestAssured + .given() + .get("/test/rest-multi4") + .then() + .statusCode(200); + + RestAssured + .given() + .get("/test/rest-multi4") + .then() + .statusCode(200); + } + @Path("/test") public static class TestResource { @@ -177,13 +209,26 @@ public String throwExceptionPlain() { @GET @Path("/rest-multi") public RestMulti getTestRestMulti() { - return RestMulti.from(Multi.createFrom().item("test")).status(210).build(); + return RestMulti.fromMultiData(Multi.createFrom().item("test")).status(210).build(); } @GET @Path("/rest-multi2") public RestMulti getTestRestMulti2() { - return RestMulti.from(Multi.createFrom().item("test")).status(211).build(); + return RestMulti.fromMultiData(Multi.createFrom().item("test")).status(211).build(); + } + + @GET + @Path("/rest-multi3") + public RestMulti getTestRestMulti3(@DefaultValue("200") @RestQuery Integer status) { + return RestMulti.fromUniResponse(Uni.createFrom().item("unused"), s -> Multi.createFrom().item("test"), null, + s -> status); + } + + @GET + @Path("/rest-multi4") + public RestMulti getTestRestMulti4() { + return RestMulti.fromUniResponse(Uni.createFrom().item("unused"), s -> Multi.createFrom().item("test")); } private IllegalArgumentException createException() { diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java index 34108bf1db7505..b9fc7ed4228c0c 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestMulti.java @@ -1,77 +1,291 @@ package org.jboss.resteasy.reactive; +import static io.smallrye.mutiny.helpers.ParameterValidation.MAPPER_RETURNED_NULL; +import static io.smallrye.mutiny.helpers.ParameterValidation.nonNull; + +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import org.jboss.resteasy.reactive.common.util.CaseInsensitiveMap; import org.jboss.resteasy.reactive.common.util.MultivaluedTreeMap; +import io.smallrye.mutiny.Context; import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.helpers.EmptyUniSubscription; +import io.smallrye.mutiny.helpers.Subscriptions; import io.smallrye.mutiny.infrastructure.Infrastructure; import io.smallrye.mutiny.operators.AbstractMulti; +import io.smallrye.mutiny.operators.AbstractUni; +import io.smallrye.mutiny.subscription.ContextSupport; import io.smallrye.mutiny.subscription.MultiSubscriber; +import io.smallrye.mutiny.subscription.UniSubscriber; +import io.smallrye.mutiny.subscription.UniSubscription; /** * A wrapper around {@link Multi} that gives resource methods a way to specify the HTTP status code and HTTP headers * when streaming a result. */ -public class RestMulti extends AbstractMulti { +@SuppressWarnings("ReactiveStreamsUnusedPublisher") +public abstract class RestMulti extends AbstractMulti { - private final Multi multi; - private final Integer status; - private final MultivaluedTreeMap headers; + public abstract Integer getStatus(); - public static RestMulti.Builder from(Multi multi) { - return new RestMulti.Builder<>(multi); - } + public abstract Map> getHeaders(); - private RestMulti(Builder builder) { - this.multi = builder.multi; - this.status = builder.status; - this.headers = builder.headers; + public static RestMulti.SyncRestMulti.Builder fromMultiData(Multi multi) { + return new RestMulti.SyncRestMulti.Builder<>(multi); } - @Override - public void subscribe(MultiSubscriber subscriber) { - multi.subscribe(Infrastructure.onMultiSubscription(multi, subscriber)); + public static RestMulti fromUniResponse(Uni uni, + Function> dataExtractor) { + return fromUniResponse(uni, dataExtractor, null, null); } - public Integer getStatus() { - return status; + public static RestMulti fromUniResponse(Uni uni, + Function> dataExtractor, + Function>> headersExtractor) { + return fromUniResponse(uni, dataExtractor, headersExtractor, null); } - public Map> getHeaders() { - return headers; + public static RestMulti fromUniResponse(Uni uni, + Function> dataExtractor, + Function>> headersExtractor, + Function statusExtractor) { + Function> actualDataExtractor = Infrastructure + .decorate(nonNull(dataExtractor, "dataExtractor")); + return (RestMulti) Infrastructure.onMultiCreation(new AsyncRestMulti<>(uni, actualDataExtractor, + headersExtractor, statusExtractor)); } - public static class Builder { + public static class SyncRestMulti extends RestMulti { + private final Multi multi; + private final Integer status; + private final MultivaluedTreeMap headers; - private Integer status; + @Override + public void subscribe(MultiSubscriber subscriber) { + multi.subscribe(Infrastructure.onMultiSubscription(multi, subscriber)); + } - private final MultivaluedTreeMap headers = new CaseInsensitiveMap<>(); + private SyncRestMulti(Builder builder) { + this.multi = builder.multi; + this.status = builder.status; + this.headers = builder.headers; + } - private Builder(Multi multi) { - this.multi = Objects.requireNonNull(multi, "multi cannot be null"); + @Override + public Integer getStatus() { + return status; } - public Builder status(int status) { - this.status = status; - return this; + @Override + public Map> getHeaders() { + return headers; } - public Builder header(String name, String value) { - if (value == null) { - headers.remove(name); + public static class Builder { + private final Multi multi; + + private Integer status; + + private final MultivaluedTreeMap headers = new CaseInsensitiveMap<>(); + + private Builder(Multi multi) { + this.multi = Objects.requireNonNull(multi, "multi cannot be null"); + } + + public Builder status(int status) { + this.status = status; return this; } - headers.add(name, value); - return this; + + public Builder header(String name, String value) { + if (value == null) { + headers.remove(name); + return this; + } + headers.add(name, value); + return this; + } + + public RestMulti build() { + return new SyncRestMulti<>(this); + } + } + } + + // Copied from: io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni while adding header and status extraction + public static class AsyncRestMulti extends RestMulti { + + private final Function> dataExtractor; + private final Function statusExtractor; + private final Function>> headersExtractor; + private final AtomicReference status; + private final AtomicReference>> headers; + private final Uni upstream; + + public AsyncRestMulti(Uni upstream, + Function> dataExtractor, + Function>> headersExtractor, + Function statusExtractor) { + this.upstream = upstream; + this.dataExtractor = dataExtractor; + this.statusExtractor = statusExtractor; + this.headersExtractor = headersExtractor; + this.status = new AtomicReference<>(null); + this.headers = new AtomicReference<>(Collections.emptyMap()); + } + + @Override + public void subscribe(MultiSubscriber subscriber) { + if (subscriber == null) { + throw new NullPointerException("The subscriber must not be `null`"); + } + AbstractUni.subscribe(upstream, new FlatMapPublisherSubscriber<>(subscriber, dataExtractor, statusExtractor, status, + headersExtractor, headers)); + } + + @Override + public Integer getStatus() { + return status.get(); + } + + @Override + public Map> getHeaders() { + return headers.get(); } - public RestMulti build() { - return new RestMulti<>(this); + static final class FlatMapPublisherSubscriber + implements Flow.Subscriber, UniSubscriber, Flow.Subscription, ContextSupport { + + private final AtomicReference secondUpstream; + private final AtomicReference firstUpstream; + private final Flow.Subscriber downstream; + private final Function> dataExtractor; + private final Function statusExtractor; + private final AtomicReference status; + private final Function>> headersExtractor; + private final AtomicReference>> headers; + private final AtomicLong requested = new AtomicLong(); + + public FlatMapPublisherSubscriber(Flow.Subscriber downstream, + Function> dataExtractor, + Function statusExtractor, + AtomicReference status, + Function>> headersExtractor, + AtomicReference>> headers) { + this.downstream = downstream; + this.dataExtractor = dataExtractor; + this.statusExtractor = statusExtractor; + this.status = status; + this.headersExtractor = headersExtractor; + this.headers = headers; + this.firstUpstream = new AtomicReference<>(); + this.secondUpstream = new AtomicReference<>(); + } + + @Override + public void onNext(O item) { + downstream.onNext(item); + } + + @Override + public void onError(Throwable failure) { + downstream.onError(failure); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + Subscriptions.requestIfNotNullOrAccumulate(secondUpstream, requested, n); + } + + @Override + public void cancel() { + UniSubscription subscription = firstUpstream.getAndSet(EmptyUniSubscription.CANCELLED); + if (subscription != null && subscription != EmptyUniSubscription.CANCELLED) { + subscription.cancel(); + } + Subscriptions.cancel(secondUpstream); + } + + @Override + public Context context() { + if (downstream instanceof ContextSupport) { + return ((ContextSupport) downstream).context(); + } else { + return Context.empty(); + } + } + + /** + * Called when we get the subscription from the upstream UNI + * + * @param subscription the subscription allowing to cancel the computation. + */ + @Override + public void onSubscribe(UniSubscription subscription) { + if (firstUpstream.compareAndSet(null, subscription)) { + downstream.onSubscribe(this); + } + } + + /** + * Called after we produced the {@link Flow.Publisher} and subscribe on it. + * + * @param subscription the subscription from the produced {@link Flow.Publisher} + */ + @Override + public void onSubscribe(Flow.Subscription subscription) { + if (secondUpstream.compareAndSet(null, subscription)) { + long r = requested.getAndSet(0L); + if (r != 0L) { + subscription.request(r); + } + } + } + + @Override + public void onItem(I item) { + Multi publisher; + + try { + publisher = dataExtractor.apply(item); + if (publisher == null) { + throw new NullPointerException(MAPPER_RETURNED_NULL); + } + if (headersExtractor != null) { + headers.set(headersExtractor.apply(item)); + } + if (statusExtractor != null) { + status.set(statusExtractor.apply(item)); + } + } catch (Throwable ex) { + downstream.onError(ex); + return; + } + + publisher.subscribe(this); + } + + @Override + public void onFailure(Throwable failure) { + downstream.onError(failure); + } } + } + } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java index 98aefaf1c2f9ba..1791a388da2751 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java @@ -48,8 +48,8 @@ public void setStreamingResponseCustomizers(List st private static class SseMultiSubscriber extends AbstractMultiSubscriber { - SseMultiSubscriber(ResteasyReactiveRequestContext requestContext, List customizers) { - super(requestContext, customizers); + SseMultiSubscriber(ResteasyReactiveRequestContext requestContext, List staticCustomizers) { + super(requestContext, staticCustomizers); } @Override @@ -60,7 +60,7 @@ public void onNext(Object item) { } else { event = new OutboundSseEventImpl.BuilderImpl().data(item).build(); } - SseUtil.send(requestContext, event, customizers).whenComplete(new BiConsumer() { + SseUtil.send(requestContext, event, staticCustomizers).whenComplete(new BiConsumer() { @Override public void accept(Object v, Throwable t) { if (t != null) { @@ -83,8 +83,8 @@ private static class ChunkedStreamingMultiSubscriber extends StreamingMultiSubsc private boolean isFirstItem = true; ChunkedStreamingMultiSubscriber(ResteasyReactiveRequestContext requestContext, - List customizers, boolean json) { - super(requestContext, customizers, json); + List staticCustomizers, Publisher publisher, boolean json) { + super(requestContext, staticCustomizers, publisher, json); } @Override @@ -112,9 +112,13 @@ private static class StreamingMultiSubscriber extends AbstractMultiSubscriber { private String nextJsonPrefix; private boolean hadItem; - StreamingMultiSubscriber(ResteasyReactiveRequestContext requestContext, List customizers, + private final Publisher publisher; + + StreamingMultiSubscriber(ResteasyReactiveRequestContext requestContext, + List staticCustomizers, Publisher publisher, boolean json) { - super(requestContext, customizers); + super(requestContext, staticCustomizers); + this.publisher = publisher; this.json = json; this.nextJsonPrefix = "["; this.hadItem = false; @@ -122,6 +126,7 @@ private static class StreamingMultiSubscriber extends AbstractMultiSubscriber { @Override public void onNext(Object item) { + List customizers = determineCustomizers(!hadItem); hadItem = true; StreamingUtil.send(requestContext, customizers, item, messagePrefix()) .handle(new BiFunction() { @@ -146,10 +151,34 @@ public Object apply(Object v, Throwable t) { }); } + private List determineCustomizers(boolean isFirst) { + // we only need to obtain the customizers from the Publisher if it's the first time we are sending data and the Publisher has customizable data + // at this point no matter the type of RestMulti we can safely obtain the headers and status + if (isFirst && (publisher instanceof RestMulti)) { + RestMulti restMulti = (RestMulti) publisher; + Map> headers = restMulti.getHeaders(); + Integer status = restMulti.getStatus(); + if (headers.isEmpty() && (status == null)) { + return staticCustomizers; + } + List result = new ArrayList<>(staticCustomizers.size() + 2); + result.addAll(staticCustomizers); // these are added first so that the result specific values will take precedence if there are conflicts + if (!headers.isEmpty()) { + result.add(new StreamingResponseCustomizer.AddHeadersCustomizer(headers)); + } + if (status != null) { + result.add(new StreamingResponseCustomizer.StatusCustomizer(status)); + } + return result; + } + + return staticCustomizers; + } + @Override public void onComplete() { if (!hadItem) { - StreamingUtil.setHeaders(requestContext, requestContext.serverResponse(), customizers); + StreamingUtil.setHeaders(requestContext, requestContext.serverResponse(), staticCustomizers); } if (json) { String postfix = onCompleteText(); @@ -161,6 +190,7 @@ public void onComplete() { } else { super.onComplete(); } + } protected String onCompleteText() { @@ -184,12 +214,13 @@ protected String messagePrefix() { static abstract class AbstractMultiSubscriber implements Subscriber { protected Subscription subscription; protected ResteasyReactiveRequestContext requestContext; - protected List customizers; + protected List staticCustomizers; private boolean weClosed = false; - AbstractMultiSubscriber(ResteasyReactiveRequestContext requestContext, List customizers) { + AbstractMultiSubscriber(ResteasyReactiveRequestContext requestContext, + List staticCustomizers) { this.requestContext = requestContext; - this.customizers = customizers; + this.staticCustomizers = staticCustomizers; // let's make sure we never restart by accident, also make sure we're not marked as completed requestContext.restart(AWOL, true); requestContext.serverResponse().addCloseHandler(() -> { @@ -296,37 +327,14 @@ private boolean requiresChunkedStream(MediaType mediaType) { } private void handleChunkedStreaming(ResteasyReactiveRequestContext requestContext, Publisher result, boolean json) { - result.subscribe(new ChunkedStreamingMultiSubscriber(requestContext, determineCustomizers(result), json)); - } - - private List determineCustomizers(Publisher publisher) { - if (publisher instanceof RestMulti) { - RestMulti restMulti = (RestMulti) publisher; - Map> headers = restMulti.getHeaders(); - Integer status = restMulti.getStatus(); - if (headers.isEmpty() && (status == null)) { - return streamingResponseCustomizers; - } - List result = new ArrayList<>(streamingResponseCustomizers.size() + 2); - result.addAll(streamingResponseCustomizers); // these are added first so that the result specific values will take precedence if there are conflicts - if (!headers.isEmpty()) { - result.add(new StreamingResponseCustomizer.AddHeadersCustomizer(headers)); - } - if (status != null) { - result.add(new StreamingResponseCustomizer.StatusCustomizer(status)); - } - return result; - } - - return streamingResponseCustomizers; + result.subscribe(new ChunkedStreamingMultiSubscriber(requestContext, streamingResponseCustomizers, result, json)); } private void handleStreaming(ResteasyReactiveRequestContext requestContext, Publisher result, boolean json) { - result.subscribe(new StreamingMultiSubscriber(requestContext, determineCustomizers(result), json)); + result.subscribe(new StreamingMultiSubscriber(requestContext, streamingResponseCustomizers, result, json)); } private void handleSse(ResteasyReactiveRequestContext requestContext, Publisher result) { - List streamingResponseCustomizers = determineCustomizers(result); SseUtil.setHeaders(requestContext, requestContext.serverResponse(), streamingResponseCustomizers); requestContext.suspend(); requestContext.serverResponse().write(EMPTY_BUFFER, new Consumer() { From 704687f5b25b99e370b96b6b6b1891e860390bc4 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 9 May 2023 12:38:50 +0300 Subject: [PATCH 260/333] Fix liquibase quickstart issue in native mode Fixes: #33209 --- .../io/quarkus/liquibase/deployment/LiquibaseProcessor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index cd935d27f0e658..8666bf778b0f0d 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -152,6 +153,9 @@ void nativeImageConfiguration( liquibase.change.ConstraintsConfig.class.getName()) .fields().build()); + // liquibase seems to instantiate these types reflectively... + reflective.produce(ReflectiveClassBuildItem.builder(ConcurrentHashMap.class, ArrayList.class).build()); + // register classes marked with @DatabaseChangeProperty for reflection Set classesMarkedWithDatabaseChangeProperty = new HashSet<>(); for (AnnotationInstance databaseChangePropertyInstance : combinedIndex.getIndex() From c9fe458bf859d35877e954f70fddad3c2d98124d Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Tue, 2 May 2023 19:06:44 +0200 Subject: [PATCH 261/333] Use relative path for JDK 17-based AppCDS dump This makes the AppCDS feature more portable where the build and deployment paths differ, but are otherwise identical (for example s2i use-case). Closes: #33069 --- .../io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java index 7e99084f775380..2c7a26da982302 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java @@ -309,10 +309,10 @@ private Path createAppCDSFromExit(JarBuildItem jarResult, command.addAll(javaArgs); if (isFastJar) { command - .add(jarResult.getLibraryDir().getParent().resolve(JarResultBuildStep.QUARKUS_RUN_JAR).toAbsolutePath() - .toString()); + .add(jarResult.getLibraryDir().getParent().resolve(JarResultBuildStep.QUARKUS_RUN_JAR) + .getFileName().toString()); } else { - command.add(jarResult.getPath().toAbsolutePath().toString()); + command.add(jarResult.getPath().getFileName().toString()); } } From b021cbbaa93989e322d33e421ae2f48ae9fec907 Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Tue, 9 May 2023 06:39:17 -0400 Subject: [PATCH 262/333] Bump smallrye-open-api.version from 3.3.2 to 3.3.4 Signed-off-by: Michael Edgar --- bom/application/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index e77658791d2e04..fe23df7675f5ea 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -59,7 +59,7 @@ 3.2.1 4.0.1 4.0.0 - 3.3.2 + 3.3.4 2.1.3 3.0.3 6.2.2 @@ -3713,7 +3713,7 @@ ${es-module-shims.version} runtime - + biz.paluch.logging From d4dc94018df30d985eaac3fbea501edf990c2ce6 Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Thu, 4 May 2023 14:26:48 -0400 Subject: [PATCH 263/333] Fix broken link so it points to the config-yaml guide --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../main/resources/codestarts/quarkus/config/yaml/codestart.yml | 2 +- .../tools/devtools-testing/src/main/resources/fake-catalog.json | 2 +- ...m-quarkus-platform-descriptor-999-SNAPSHOT-999-SNAPSHOT.json | 2 +- ...bom-quarkus-platform-descriptor-2.0.3.Final-2.0.3.Final.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 4fb7816a51a5d6..5c3ed717f678af 100644 --- a/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,7 +8,7 @@ metadata: categories: - "core" status: "stable" - guide: "https://quarkus.io/guides/config#yaml" + guide: "https://quarkus.io/guides/config-yaml" codestart: name: "config-yaml" languages: diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/config/yaml/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/config/yaml/codestart.yml index cbdd9c9db0eb26..3eb4a9dc824687 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/config/yaml/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/config/yaml/codestart.yml @@ -4,7 +4,7 @@ language: base: shared-data: config: - guide: https://quarkus.io/guides/config#yaml + guide: https://quarkus.io/guides/config-yaml file-name: application.yml dependencies: - io.quarkus:quarkus-config-yaml \ No newline at end of file diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index db1623d48a73cd..f9c1fa44b1edaf 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -88,7 +88,7 @@ "core" ], "status": "stable", - "guide": "https://quarkus.io/guides/config#yaml", + "guide": "https://quarkus.io/guides/config-yaml", "codestart": { "name": "config-yaml", "kind": "core", diff --git a/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-bom-quarkus-platform-descriptor-999-SNAPSHOT-999-SNAPSHOT.json b/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-bom-quarkus-platform-descriptor-999-SNAPSHOT-999-SNAPSHOT.json index b73e223bf6484f..243a5afb3590c6 100644 --- a/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-bom-quarkus-platform-descriptor-999-SNAPSHOT-999-SNAPSHOT.json +++ b/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-bom-quarkus-platform-descriptor-999-SNAPSHOT-999-SNAPSHOT.json @@ -192,7 +192,7 @@ "keywords" : [ "config", "configuration", "yaml" ], "categories" : [ "core" ], "status" : "stable", - "guide" : "https://quarkus.io/guides/config#yaml", + "guide" : "https://quarkus.io/guides/config-yaml", "codestart" : { "name" : "config-yaml", "languages" : [ "java", "kotlin" ], diff --git a/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-universe-bom-quarkus-platform-descriptor-2.0.3.Final-2.0.3.Final.json b/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-universe-bom-quarkus-platform-descriptor-2.0.3.Final-2.0.3.Final.json index 86492b55278e76..01658618638dc8 100644 --- a/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-universe-bom-quarkus-platform-descriptor-2.0.3.Final-2.0.3.Final.json +++ b/independent-projects/tools/registry-client/src/test/resources/catalog-config/quarkus-universe-bom-quarkus-platform-descriptor-2.0.3.Final-2.0.3.Final.json @@ -504,7 +504,7 @@ "keywords" : [ "config", "configuration", "yaml" ], "categories" : [ "core" ], "status" : "stable", - "guide" : "https://quarkus.io/guides/config#yaml", + "guide" : "https://quarkus.io/guides/config-yaml", "codestart" : { "name" : "config-yaml", "languages" : [ "java", "kotlin" ], From bba1561d0b67d03868484b5b5267de5209d7a3b1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 9 May 2023 14:50:26 +0200 Subject: [PATCH 264/333] Upgrade RESTEasy Classic to 6.2.3.Final --- 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 5e9d2ce473e987..931cb8c283ccf0 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -31,7 +31,7 @@ 1.1.1 2.1.1.Final 3.0.2.Final - 6.2.1.Final + 6.2.3.Final 0.33.0 0.2.4 0.1.15 From bff6f0034934fc0d2506003d9bb9ce6bbd92fb06 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 9 May 2023 15:53:31 +0300 Subject: [PATCH 265/333] Allow to inject info extension data as beans Closes: #32994 --- .../info/deployment/InfoProcessor.java | 88 +++++++++++++---- .../info/deployment/EnabledInfoTest.java | 37 +++++++ .../main/java/io/quarkus/info/BuildInfo.java | 14 +++ .../main/java/io/quarkus/info/GitInfo.java | 12 +++ .../main/java/io/quarkus/info/JavaInfo.java | 6 ++ .../src/main/java/io/quarkus/info/OsInfo.java | 10 ++ .../io/quarkus/info/runtime/InfoRecorder.java | 99 +++++++++++++++++++ .../info/runtime/JavaInfoContributor.java | 6 +- .../info/runtime/OsInfoContributor.java | 18 +++- 9 files changed, 266 insertions(+), 24 deletions(-) create mode 100644 extensions/info/runtime/src/main/java/io/quarkus/info/BuildInfo.java create mode 100644 extensions/info/runtime/src/main/java/io/quarkus/info/GitInfo.java create mode 100644 extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java create mode 100644 extensions/info/runtime/src/main/java/io/quarkus/info/OsInfo.java diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java index 302c56043cac68..bcde3d07de3042 100644 --- a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java +++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java @@ -13,6 +13,8 @@ import java.util.TimeZone; import java.util.stream.Collectors; +import jakarta.inject.Singleton; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; @@ -23,13 +25,19 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.jboss.logging.Logger; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.info.BuildInfo; +import io.quarkus.info.GitInfo; +import io.quarkus.info.JavaInfo; +import io.quarkus.info.OsInfo; import io.quarkus.info.deployment.spi.InfoBuildTimeContributorBuildItem; import io.quarkus.info.deployment.spi.InfoBuildTimeValuesBuildItem; import io.quarkus.info.runtime.InfoRecorder; @@ -43,18 +51,22 @@ public class InfoProcessor { private static final Logger log = Logger.getLogger(InfoProcessor.class); @BuildStep(onlyIf = GitInInfoEndpointEnabled.class) - InfoBuildTimeValuesBuildItem gitInfo(InfoBuildTimeConfig config, + @Record(ExecutionTime.RUNTIME_INIT) + void gitInfo(InfoBuildTimeConfig config, CurateOutcomeBuildItem curateOutcomeBuildItem, - OutputTargetBuildItem outputTargetBuildItem) { + OutputTargetBuildItem outputTargetBuildItem, + BuildProducer valuesProducer, + BuildProducer beanProducer, + InfoRecorder recorder) { File projectRoot = highestKnownProjectDirectory(curateOutcomeBuildItem, outputTargetBuildItem); if (projectRoot == null) { log.debug("Unable to determine project directory"); - return null; + return; } RepositoryBuilder repositoryBuilder = new RepositoryBuilder().findGitDir(projectRoot); if (repositoryBuilder.getGitDir() == null) { log.debug("Project is not checked in to git"); - return null; + return; } try (Repository repository = repositoryBuilder.build()) { @@ -65,11 +77,14 @@ InfoBuildTimeValuesBuildItem gitInfo(InfoBuildTimeConfig config, boolean addFullInfo = config.git().mode() == InfoBuildTimeConfig.Git.Mode.FULL; Map data = new LinkedHashMap<>(); - data.put("branch", repository.getBranch()); + String branch = repository.getBranch(); + data.put("branch", branch); Map commit = new LinkedHashMap<>(); - commit.put("id", latestCommit.getName()); - commit.put("time", formatDate(commitDate, commitTimeZone)); + String latestCommitId = latestCommit.getName(); + commit.put("id", latestCommitId); + String latestCommitTime = formatDate(commitDate, commitTimeZone); + commit.put("time", latestCommitTime); if (addFullInfo) { @@ -85,7 +100,7 @@ InfoBuildTimeValuesBuildItem gitInfo(InfoBuildTimeConfig config, commit.put("user", user); Map id = new LinkedHashMap<>(); - id.put("full", latestCommit.getName()); + id.put("full", latestCommitId); id.put("abbrev", latestCommit.abbreviate(13).name()); Map message = new LinkedHashMap<>(); message.put("full", latestCommit.getFullMessage().trim()); @@ -102,10 +117,14 @@ InfoBuildTimeValuesBuildItem gitInfo(InfoBuildTimeConfig config, data.put("build", obtainBuildInfo(curateOutcomeBuildItem, repository)); } - return new InfoBuildTimeValuesBuildItem("git", data); + valuesProducer.produce(new InfoBuildTimeValuesBuildItem("git", data)); + beanProducer.produce(SyntheticBeanBuildItem.configure(GitInfo.class) + .supplier(recorder.gitInfoSupplier(branch, latestCommitId, latestCommitTime)) + .scope(Singleton.class) + .setRuntimeInit() + .done()); } catch (Exception e) { log.debug("Unable to determine git information", e); - return null; } } @@ -185,15 +204,30 @@ private File highestKnownProjectDirectory(CurateOutcomeBuildItem curateOutcomeBu } @BuildStep(onlyIf = BuildInInfoEndpointEnabled.class) - InfoBuildTimeValuesBuildItem buildInfo(CurateOutcomeBuildItem curateOutcomeBuildItem, InfoBuildTimeConfig config) { + @Record(ExecutionTime.RUNTIME_INIT) + void buildInfo(CurateOutcomeBuildItem curateOutcomeBuildItem, + InfoBuildTimeConfig config, + BuildProducer valuesProducer, + BuildProducer beanProducer, + InfoRecorder recorder) { ApplicationModel applicationModel = curateOutcomeBuildItem.getApplicationModel(); ResolvedDependency appArtifact = applicationModel.getAppArtifact(); Map buildData = new LinkedHashMap<>(); - buildData.put("group", appArtifact.getGroupId()); - buildData.put("artifact", appArtifact.getArtifactId()); - buildData.put("version", appArtifact.getVersion()); - buildData.put("time", ISO_OFFSET_DATE_TIME.format(OffsetDateTime.now())); // TODO: what is the proper notion of build time? - return new InfoBuildTimeValuesBuildItem("build", finalBuildData(buildData, config.build())); + String group = appArtifact.getGroupId(); + buildData.put("group", group); + String artifact = appArtifact.getArtifactId(); + buildData.put("artifact", artifact); + String version = appArtifact.getVersion(); + buildData.put("version", version); + String time = ISO_OFFSET_DATE_TIME.format(OffsetDateTime.now()); + buildData.put("time", time); // TODO: what is the proper notion of build time? + Map data = finalBuildData(buildData, config.build()); + valuesProducer.produce(new InfoBuildTimeValuesBuildItem("build", data)); + beanProducer.produce(SyntheticBeanBuildItem.configure(BuildInfo.class) + .supplier(recorder.buildInfoSupplier(group, artifact, version, time)) + .scope(Singleton.class) + .setRuntimeInit() + .done()); } private Map finalBuildData(Map buildData, InfoBuildTimeConfig.Build buildConfig) { @@ -207,14 +241,28 @@ private Map finalBuildData(Map buildData, InfoBu @BuildStep(onlyIf = OsInInfoEndpointEnabled.class) @Record(ExecutionTime.RUNTIME_INIT) - InfoBuildTimeContributorBuildItem osInfo(InfoRecorder recorder) { - return new InfoBuildTimeContributorBuildItem(recorder.osInfoContributor()); + void osInfo(InfoRecorder recorder, + BuildProducer valuesProducer, + BuildProducer beanProducer) { + valuesProducer.produce(new InfoBuildTimeContributorBuildItem(recorder.osInfoContributor())); + beanProducer.produce(SyntheticBeanBuildItem.configure(OsInfo.class) + .supplier(recorder.osInfoSupplier()) + .scope(Singleton.class) + .setRuntimeInit() + .done()); } @BuildStep(onlyIf = JavaInInfoEndpointEnabled.class) @Record(ExecutionTime.RUNTIME_INIT) - InfoBuildTimeContributorBuildItem javaInfo(InfoRecorder recorder) { - return new InfoBuildTimeContributorBuildItem(recorder.javaInfoContributor()); + void javaInfo(InfoRecorder recorder, + BuildProducer valuesProducer, + BuildProducer beanProducer) { + valuesProducer.produce(new InfoBuildTimeContributorBuildItem(recorder.javaInfoContributor())); + beanProducer.produce(SyntheticBeanBuildItem.configure(JavaInfo.class) + .supplier(recorder.javaInfoSupplier()) + .scope(Singleton.class) + .setRuntimeInit() + .done()); } @BuildStep(onlyIf = InfoEndpointEnabled.class) diff --git a/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java b/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java index 14a3de62956e22..8fd26b1993923d 100644 --- a/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java +++ b/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java @@ -4,10 +4,17 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.info.BuildInfo; +import io.quarkus.info.GitInfo; +import io.quarkus.info.JavaInfo; +import io.quarkus.info.OsInfo; import io.quarkus.test.QuarkusUnitTest; public class EnabledInfoTest { @@ -16,6 +23,18 @@ public class EnabledInfoTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication(); + @Inject + GitInfo gitInfo; + + @Inject + BuildInfo buildInfo; + + @Inject + OsInfo osInfo; + + @Inject + JavaInfo javaInfo; + @Test public void test() { when().get("/q/info") @@ -31,5 +50,23 @@ public void test() { .body("git.branch", is(notNullValue())) .body("git.build", is(nullValue())); + assertNotNull(buildInfo); + assertNotNull(buildInfo.group()); + assertNotNull(buildInfo.artifact()); + assertNotNull(buildInfo.version()); + assertNotNull(buildInfo.time()); + + assertNotNull(gitInfo); + assertNotNull(gitInfo.branch()); + assertNotNull(gitInfo.latestCommitId()); + assertNotNull(gitInfo.commitTime()); + + assertNotNull(osInfo); + assertNotNull(osInfo.name()); + assertNotNull(osInfo.version()); + assertNotNull(osInfo.architecture()); + + assertNotNull(javaInfo); + assertNotNull(javaInfo.version()); } } diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/BuildInfo.java b/extensions/info/runtime/src/main/java/io/quarkus/info/BuildInfo.java new file mode 100644 index 00000000000000..99336d85cb6a6a --- /dev/null +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/BuildInfo.java @@ -0,0 +1,14 @@ +package io.quarkus.info; + +import java.time.OffsetDateTime; + +public interface BuildInfo { + + String group(); + + String artifact(); + + String version(); + + OffsetDateTime time(); +} diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/GitInfo.java b/extensions/info/runtime/src/main/java/io/quarkus/info/GitInfo.java new file mode 100644 index 00000000000000..5cd42a00bbadda --- /dev/null +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/GitInfo.java @@ -0,0 +1,12 @@ +package io.quarkus.info; + +import java.time.OffsetDateTime; + +public interface GitInfo { + + String branch(); + + String latestCommitId(); + + OffsetDateTime commitTime(); +} diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java b/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java new file mode 100644 index 00000000000000..e7e4ed3c7fa90f --- /dev/null +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java @@ -0,0 +1,6 @@ +package io.quarkus.info; + +public interface JavaInfo { + + String version(); +} diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/OsInfo.java b/extensions/info/runtime/src/main/java/io/quarkus/info/OsInfo.java new file mode 100644 index 00000000000000..e37c39af60a361 --- /dev/null +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/OsInfo.java @@ -0,0 +1,10 @@ +package io.quarkus.info; + +public interface OsInfo { + + String name(); + + String version(); + + String architecture(); +} diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java index 2ed391f0cf5a73..765d19af398478 100644 --- a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java @@ -1,9 +1,17 @@ package io.quarkus.info.runtime; +import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME; + +import java.time.OffsetDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; +import io.quarkus.info.BuildInfo; +import io.quarkus.info.GitInfo; +import io.quarkus.info.JavaInfo; +import io.quarkus.info.OsInfo; import io.quarkus.info.runtime.spi.InfoContributor; import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; @@ -20,14 +28,105 @@ public Handler handler(Map buildTimeInfo, List gitInfoSupplier(String branch, String latestCommitId, String latestCommitTime) { + return new Supplier() { + @Override + public GitInfo get() { + return new GitInfo() { + @Override + public String branch() { + return branch; + } + + @Override + public String latestCommitId() { + return latestCommitId; + } + + @Override + public OffsetDateTime commitTime() { + return OffsetDateTime.parse(latestCommitTime, ISO_OFFSET_DATE_TIME); + } + }; + } + }; + } + + public Supplier buildInfoSupplier(String group, String artifact, String version, String time) { + return new Supplier() { + @Override + public BuildInfo get() { + return new BuildInfo() { + @Override + public String group() { + return group; + } + + @Override + public String artifact() { + return artifact; + } + + @Override + public String version() { + return version; + } + + @Override + public OffsetDateTime time() { + return OffsetDateTime.parse(time, ISO_OFFSET_DATE_TIME); + } + }; + } + }; + } + public OsInfoContributor osInfoContributor() { return new OsInfoContributor(); } + public Supplier osInfoSupplier() { + return new Supplier() { + @Override + public OsInfo get() { + return new OsInfo() { + @Override + public String name() { + return OsInfoContributor.getName(); + } + + @Override + public String version() { + return OsInfoContributor.getVersion(); + } + + @Override + public String architecture() { + return OsInfoContributor.getArchitecture(); + } + }; + } + }; + } + public JavaInfoContributor javaInfoContributor() { return new JavaInfoContributor(); } + public Supplier javaInfoSupplier() { + return new Supplier() { + @Override + public JavaInfo get() { + return new JavaInfo() { + @Override + public String version() { + return JavaInfoContributor.getVersion(); + } + }; + } + }; + } + private static class InfoHandler implements Handler { private final Map finalBuildInfo; diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java index 3bf404d78eaac2..11a88a612afbd9 100644 --- a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java @@ -16,7 +16,11 @@ public String name() { public Map data() { //TODO: should we add more information like 'java.runtime.*' and 'java.vm.*' ? Map result = new LinkedHashMap<>(); - result.put("version", System.getProperty("java.version")); + result.put("version", getVersion()); return result; } + + static String getVersion() { + return System.getProperty("java.version"); + } } diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/OsInfoContributor.java b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/OsInfoContributor.java index 1dbe6c79e807f9..8adab7e752a7a8 100644 --- a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/OsInfoContributor.java +++ b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/OsInfoContributor.java @@ -14,9 +14,21 @@ public String name() { @Override public Map data() { Map result = new LinkedHashMap<>(); - result.put("name", System.getProperty("os.name")); - result.put("version", System.getProperty("os.version")); - result.put("arch", System.getProperty("os.arch")); + result.put("name", getName()); + result.put("version", getVersion()); + result.put("arch", getArchitecture()); return result; } + + static String getName() { + return System.getProperty("os.name"); + } + + static String getVersion() { + return System.getProperty("os.version"); + } + + static String getArchitecture() { + return System.getProperty("os.arch"); + } } From 12c413e1123b91e0babff5e0aeb4704bb7663e76 Mon Sep 17 00:00:00 2001 From: Michelle Purcell Date: Tue, 9 May 2023 16:27:10 +0100 Subject: [PATCH 266/333] Fix new vale error on 'source:' attribute fix bad line break --- docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml | 2 +- docs/.vale/styles/Quarkus/ConsciousLanguage.yml | 2 +- docs/.vale/styles/Quarkus/Ellipses.yml | 2 +- docs/.vale/styles/Quarkus/Fluff.yml | 2 +- docs/.vale/styles/Quarkus/HeadingPunctuation.yml | 2 +- docs/.vale/styles/Quarkus/Headings.yml | 2 +- docs/.vale/styles/Quarkus/OxfordComma.yml | 2 +- docs/.vale/styles/Quarkus/RepeatedWords.yml | 3 +-- docs/.vale/styles/Quarkus/SentenceLength.yml | 2 +- docs/.vale/styles/Quarkus/Spacing.yml | 2 +- docs/.vale/styles/Quarkus/Spelling.yml | 4 +--- docs/.vale/styles/Quarkus/TermsErrors.yml | 2 +- docs/.vale/styles/Quarkus/TermsSuggestions.yml | 2 +- docs/.vale/styles/Quarkus/TermsWarnings.yml | 2 +- 14 files changed, 14 insertions(+), 17 deletions(-) diff --git a/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml b/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml index 5f4c7e48d2973b..a3c3b78cf2b7dc 100644 --- a/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml +++ b/docs/.vale/styles/Quarkus/CaseSensitiveTerms.yml @@ -4,7 +4,7 @@ ignorecase: false level: suggestion link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: Use '%s' rather than '%s'. -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: replace swap: diff --git a/docs/.vale/styles/Quarkus/ConsciousLanguage.yml b/docs/.vale/styles/Quarkus/ConsciousLanguage.yml index 62664fb31d7ff0..18ad68cbdb22af 100644 --- a/docs/.vale/styles/Quarkus/ConsciousLanguage.yml +++ b/docs/.vale/styles/Quarkus/ConsciousLanguage.yml @@ -4,7 +4,7 @@ ignorecase: true level: warning link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: Use '%s' rather than '%s.' -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: replace swap: diff --git a/docs/.vale/styles/Quarkus/Ellipses.yml b/docs/.vale/styles/Quarkus/Ellipses.yml index f96f96185a0c94..dff40c5d17adbc 100644 --- a/docs/.vale/styles/Quarkus/Ellipses.yml +++ b/docs/.vale/styles/Quarkus/Ellipses.yml @@ -4,7 +4,7 @@ level: suggestion link: https://developers.google.com/style/ellipses?hl=en message: "Avoid the ellipsis (...) except to indicate omitted words. Insert a space before and after an ellipsis." nonword: true -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: remove tokens: diff --git a/docs/.vale/styles/Quarkus/Fluff.yml b/docs/.vale/styles/Quarkus/Fluff.yml index 2bee7776d88897..b93a78f7cf84b0 100644 --- a/docs/.vale/styles/Quarkus/Fluff.yml +++ b/docs/.vale/styles/Quarkus/Fluff.yml @@ -4,7 +4,7 @@ ignorecase: true level: suggestion link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Depending on the context, consider using '%s' rather than '%s'." -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: replace swap: diff --git a/docs/.vale/styles/Quarkus/HeadingPunctuation.yml b/docs/.vale/styles/Quarkus/HeadingPunctuation.yml index c7a7d22e6ed1c4..0084fcf8a5fd83 100644 --- a/docs/.vale/styles/Quarkus/HeadingPunctuation.yml +++ b/docs/.vale/styles/Quarkus/HeadingPunctuation.yml @@ -5,7 +5,7 @@ link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc- message: "Do not use end punctuation in headings." nonword: true scope: heading -source: "Quarkus style guide on titles and headings" +# source: "Quarkus style guide on titles and headings" action: name: edit params: diff --git a/docs/.vale/styles/Quarkus/Headings.yml b/docs/.vale/styles/Quarkus/Headings.yml index a3218d3af84d41..e22bac9787e2fd 100644 --- a/docs/.vale/styles/Quarkus/Headings.yml +++ b/docs/.vale/styles/Quarkus/Headings.yml @@ -5,7 +5,7 @@ link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc- match: $sentence message: "Use sentence-style capitalization in '%s'." scope: heading -source: Quarkus contributor guide +# source: Quarkus contributor guide indicators: - ":" exceptions: diff --git a/docs/.vale/styles/Quarkus/OxfordComma.yml b/docs/.vale/styles/Quarkus/OxfordComma.yml index 1fe4fc6921847b..c525c39d741802 100644 --- a/docs/.vale/styles/Quarkus/OxfordComma.yml +++ b/docs/.vale/styles/Quarkus/OxfordComma.yml @@ -3,6 +3,6 @@ extends: existence level: suggestion link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Use the Oxford comma in '%s'." -source: Quarkus contributor guide +# source: Quarkus contributor guide tokens: - '(?:[^,]+,){1,}\s\w+\sand' diff --git a/docs/.vale/styles/Quarkus/RepeatedWords.yml b/docs/.vale/styles/Quarkus/RepeatedWords.yml index b4d9add08b3b8a..d3a3c8a1bd1db0 100644 --- a/docs/.vale/styles/Quarkus/RepeatedWords.yml +++ b/docs/.vale/styles/Quarkus/RepeatedWords.yml @@ -3,9 +3,8 @@ extends: repetition link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "'%s' is repeated!" level: error -source: Quarkus contributor guide +# source: Quarkus contributor guide alpha: true tokens: - '[^\s\.]+' - '[^\s]+' - \ No newline at end of file diff --git a/docs/.vale/styles/Quarkus/SentenceLength.yml b/docs/.vale/styles/Quarkus/SentenceLength.yml index 8ae2a285ba76b1..a4ff95a7ae30ce 100644 --- a/docs/.vale/styles/Quarkus/SentenceLength.yml +++ b/docs/.vale/styles/Quarkus/SentenceLength.yml @@ -4,6 +4,6 @@ level: suggestion link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Try to keep sentences to an average of 32 words or fewer." scope: sentence -source: Quarkus contributor guide +# source: Quarkus contributor guide max: 32 token: \b(\w+)\b diff --git a/docs/.vale/styles/Quarkus/Spacing.yml b/docs/.vale/styles/Quarkus/Spacing.yml index 5570e41a79c16a..3a17c5e435cf68 100644 --- a/docs/.vale/styles/Quarkus/Spacing.yml +++ b/docs/.vale/styles/Quarkus/Spacing.yml @@ -4,7 +4,7 @@ level: error link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Keep one space between words in '%s'." nonword: true -source: Quarkus contributor guide +# source: Quarkus contributor guide tokens: - "[a-z][.?!] {2,}[A-Z]" - "[a-z][.?!][A-Z]" diff --git a/docs/.vale/styles/Quarkus/Spelling.yml b/docs/.vale/styles/Quarkus/Spelling.yml index 89ae92fca24a6c..4bac6f4cc4be84 100644 --- a/docs/.vale/styles/Quarkus/Spelling.yml +++ b/docs/.vale/styles/Quarkus/Spelling.yml @@ -3,7 +3,7 @@ extends: spelling level: warning link: https://redhat-documentation.github.io/vale-at-red-hat/docs/reference-guide/spelling/ message: "Use correct American English spelling. Did you really mean '%s'?" -source: Quarkus contributor guide +# source: Quarkus contributor guide # A "filter" is a case-sensitive regular expression specifying words to ignore during spell checking. # Spelling rule applies to individual words @@ -50,8 +50,6 @@ filters: - "[gG]etter" - "[gG]it" - "[gG]radle" - - - "[gG]rafana" - "[hH]ardcoding" - "[hH]eatmap" diff --git a/docs/.vale/styles/Quarkus/TermsErrors.yml b/docs/.vale/styles/Quarkus/TermsErrors.yml index d377cd6d2c1c7f..ebedeebb4e3234 100644 --- a/docs/.vale/styles/Quarkus/TermsErrors.yml +++ b/docs/.vale/styles/Quarkus/TermsErrors.yml @@ -4,7 +4,7 @@ ignorecase: true level: error link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Use '%s' rather than '%s'." -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: replace # swap maps tokens in form of bad: good diff --git a/docs/.vale/styles/Quarkus/TermsSuggestions.yml b/docs/.vale/styles/Quarkus/TermsSuggestions.yml index 2c9b21d45deafe..366f69e90be9b1 100644 --- a/docs/.vale/styles/Quarkus/TermsSuggestions.yml +++ b/docs/.vale/styles/Quarkus/TermsSuggestions.yml @@ -4,7 +4,7 @@ ignorecase: false level: suggestion link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Depending on the context, consider using '%s' rather than '%s'." -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: replace swap: diff --git a/docs/.vale/styles/Quarkus/TermsWarnings.yml b/docs/.vale/styles/Quarkus/TermsWarnings.yml index 20381161552d06..36eebc9a393d55 100644 --- a/docs/.vale/styles/Quarkus/TermsWarnings.yml +++ b/docs/.vale/styles/Quarkus/TermsWarnings.yml @@ -4,7 +4,7 @@ ignorecase: true level: warning link: https://github.com/quarkusio/quarkus/blob/main/docs/src/main/asciidoc/doc-reference.adoc message: "Consider using '%s' rather than '%s' unless updating existing content that uses it." -source: Quarkus contributor guide +# source: Quarkus contributor guide action: name: replace swap: From 63334096a929d24155f04875843c421b7995787e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 9 May 2023 20:04:38 +0200 Subject: [PATCH 267/333] Keep query params for extension page deeplinks --- .../dev-ui/controller/router-controller.js | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js index 0b17656c7e4fa0..7b609d8b562700 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/router-controller.js @@ -156,31 +156,52 @@ export class RouterController { } var currentSelection = window.location.pathname; - var currentQueryString = window.location.search; + const search = this.getQueryParamsWithoutFrom(); var relocationRequest = this.getQueryParameter("from"); if (relocationRequest) { // We know and already loaded the requested location if (relocationRequest === path) { - Router.go({pathname: path}); + Router.go({pathname: path, search}); } } else { // We know and already loaded the requested location if (currentSelection === path) { - Router.go({pathname: path}); + Router.go({pathname: path, search}); // The default naked route } else if (!RouterController.router.location.route && defaultRoute && currentSelection.endsWith('/dev-ui/')) { - Router.go({pathname: path}); + Router.go({pathname: path, search}); // We do not know and have not yet loaded the requested location } else if (!RouterController.router.location.route && defaultRoute) { + + // pass original query param + const currentQueryString = window.location.search; + const origSearch = currentQueryString?.length > 0 ? '&' + currentQueryString : ''; + Router.go({ pathname: path, - search: '?from=' + currentSelection, + search: '?from=' + currentSelection + origSearch, }); } } } + getQueryParamsWithoutFrom() { + const params = new URLSearchParams(window.location.search); + if (params) { + const paramsWithoutFrom = []; + params.forEach((value, key) => { + if (key !== 'from') { + paramsWithoutFrom.push(key + '=' + value) + } + }); + if (paramsWithoutFrom.length > 0) { + return paramsWithoutFrom.join('&'); + } + } + return ''; + } + getQueryParameters() { const params = new Proxy(new URLSearchParams(window.location.search), { get: (searchParams, prop) => searchParams.get(prop), From 164e93c72ca10777e68ab641fc79bd9fc7e0e1a6 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 9 May 2023 20:47:35 +0200 Subject: [PATCH 268/333] Update Stork to version 2.2.0 --- 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 520893270db570..7e0433c25ee033 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -69,7 +69,7 @@ 3.0.0 3.3.0 4.5.0 - 2.1.0 + 2.2.0 2.1.2 2.1.1 4.0.1 From ad63d0639fde269c9f1882cea03c5507d4f5688f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 19:56:36 +0000 Subject: [PATCH 269/333] Bump com.gradle.enterprise from 3.13.1 to 3.13.2 in /devtools/gradle Bumps com.gradle.enterprise from 3.13.1 to 3.13.2. --- updated-dependencies: - dependency-name: com.gradle.enterprise dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- devtools/gradle/settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/settings.gradle.kts b/devtools/gradle/settings.gradle.kts index 2495d30fe406ab..2edcd661600af2 100644 --- a/devtools/gradle/settings.gradle.kts +++ b/devtools/gradle/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.enterprise") version "3.13.1" + id("com.gradle.enterprise") version "3.13.2" } gradleEnterprise { From 27d0c437df05931671f76cfe803cf10646809f5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 22:27:43 +0000 Subject: [PATCH 270/333] Bump grpc.version from 1.54.1 to 1.55.1 Bumps `grpc.version` from 1.54.1 to 1.55.1. Updates `grpc-bom` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) Updates `protoc-gen-grpc-java` from 1.54.1 to 1.55.1 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.54.1...v1.55.1) --- updated-dependencies: - dependency-name: io.grpc:grpc-bom dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:linux-aarch_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:linux-x86_32 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:linux-x86_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:osx-x86_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:osx-aarch_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:windows-x86_32 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:windows-x86_64 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1dc3c22e4deed1..e59b2eb4533580 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 6.6.0 - 1.54.1 + 1.55.1 1.2.1 3.22.0 ${protoc.version} From c5e308752692ef6a00530ac787c3447ee5b4ece1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 22:33:40 +0000 Subject: [PATCH 271/333] Bump groovy from 4.0.11 to 4.0.12 Bumps [groovy](https://github.com/apache/groovy) from 4.0.11 to 4.0.12. - [Commits](https://github.com/apache/groovy/commits) --- updated-dependencies: - dependency-name: org.apache.groovy:groovy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build-parent/pom.xml | 2 +- independent-projects/enforcer-rules/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index f59aef296d173d..b21004cd9add8a 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -686,7 +686,7 @@ org.apache.groovy groovy - 4.0.11 + 4.0.12 diff --git a/independent-projects/enforcer-rules/pom.xml b/independent-projects/enforcer-rules/pom.xml index 10f7c57218e77b..8a47259e602901 100644 --- a/independent-projects/enforcer-rules/pom.xml +++ b/independent-projects/enforcer-rules/pom.xml @@ -109,7 +109,7 @@ org.apache.groovy groovy - 4.0.11 + 4.0.12 From db32c5d066ee577cdb1ba33212b54e0f0d87cf29 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 10 May 2023 08:12:37 +0300 Subject: [PATCH 272/333] Reduce the number of dev mode tests run in Elytron JDBC extension The mixture of all types of Quarkus tests doesn't allow ClassLoaders to be reclaimed leading to OOM exceptions --- .../elytron/security/jdbc/CustomRoleDecoderDevModeTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java b/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java index 772ebd49aeecf4..42234846a6232e 100644 --- a/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java +++ b/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java @@ -1,5 +1,7 @@ package io.quarkus.elytron.security.jdbc; +import static io.quarkus.elytron.security.jdbc.JdbcSecurityRealmTest.testClasses; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -19,7 +21,7 @@ import io.restassured.RestAssured; //see https://github.com/quarkusio/quarkus/issues/9296 -public class CustomRoleDecoderDevModeTest extends JdbcSecurityRealmTest { +public class CustomRoleDecoderDevModeTest { static Class[] testClassesWithCustomRoleDecoder = Stream.concat( Arrays.stream(testClasses), From 158d4c6e39731b81b66c9553fe4b967c6bfadaf3 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 10 May 2023 08:49:10 +0300 Subject: [PATCH 273/333] Fix Resource Class reflection registration when custom Writer is used Originally reported at: https://quarkusio.zulipchat.com/#narrow/stream/306418-resteasy-reactive/topic/CloudEvents.20lib.20doesn't.20work.20in.20native.20mode/near/357052399 --- .../QuarkusServerEndpointIndexer.java | 49 +++++++++++++++-- .../java/io/quarkus/it/envers/Message2.java | 21 ++++++++ .../quarkus/it/envers/Message2Provider.java | 54 +++++++++++++++++++ .../io/quarkus/it/envers/Output2Resource.java | 16 ++++++ .../io/quarkus/it/envers/OutputResource.java | 7 --- .../quarkus/it/envers/OutputResourceTest.java | 2 +- 6 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2.java create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2Provider.java create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Output2Resource.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java index b7cfc7052729fa..fb78eb42bf2047 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java @@ -1,6 +1,7 @@ package io.quarkus.resteasy.reactive.server.deployment; import static org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames.SERVER_MESSAGE_BODY_READER; +import static org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames.SERVER_MESSAGE_BODY_WRITER; import java.lang.annotation.Annotation; import java.util.List; @@ -156,7 +157,7 @@ protected void handleAdditionalMethodProcessing(ServerResourceMethod method, Cla @Override public boolean additionalRegisterClassForReflectionCheck(ResourceMethodCallbackEntry entry) { - return checkBodyParameterMessageBodyReader(entry); + return checkBodyParameterMessageBodyReader(entry) || checkReturnTypeMessageBodyWriter(entry); } /** @@ -194,12 +195,48 @@ private boolean checkBodyParameterMessageBodyReader(ResourceMethodCallbackEntry return false; } + /** + * Check whether the Resource Method has a return type for which there exists a matching + * {@link jakarta.ws.rs.ext.MessageBodyWriter} + * that is not a {@link org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter}. + * In this case the Resource Class needs to be registered for reflection because the + * {@link jakarta.ws.rs.ext.MessageBodyWriter#isWriteable(Class, java.lang.reflect.Type, Annotation[], MediaType)} + * method expects to be passed the method annotations. + */ + private boolean checkReturnTypeMessageBodyWriter(ResourceMethodCallbackEntry entry) { + Type returnType = entry.getMethodInfo().returnType(); + String returnTypeName; + switch (returnType.kind()) { + case CLASS: + returnTypeName = returnType.asClassType().name().toString(); + break; + case PARAMETERIZED_TYPE: + returnTypeName = returnType.asParameterizedType().name().toString(); + break; + default: + returnTypeName = null; + } + if (returnTypeName == null) { + return false; + } + + List writers = getSerializerScanningResult().getWriters(); + + for (ScannedSerializer writer : writers) { + if (isSubclassOf(returnTypeName, writer.getHandledClassName()) + && !isServerMessageBodyWriter(writer.getClassInfo())) { + return true; + } + } + return false; + } + private boolean isSubclassOf(String className, String parentName) { if (className.equals(parentName)) { return true; } ClassInfo classByName = index.getClassByName(className); - if (classByName == null) { + if ((classByName == null) || (classByName.superName() == null)) { return false; } try { @@ -210,8 +247,12 @@ private boolean isSubclassOf(String className, String parentName) { } } - private boolean isServerMessageBodyReader(ClassInfo readerClassInfo) { - return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_READER).contains(readerClassInfo); + private boolean isServerMessageBodyReader(ClassInfo classInfo) { + return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_READER).contains(classInfo); + } + + private boolean isServerMessageBodyWriter(ClassInfo classInfo) { + return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_WRITER).contains(classInfo); } private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, MethodInfo info) { diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2.java new file mode 100644 index 00000000000000..d169529071ac1f --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2.java @@ -0,0 +1,21 @@ +package io.quarkus.it.envers; + +public class Message2 { + + private String data; + + public Message2() { + } + + public Message2(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2Provider.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2Provider.java new file mode 100644 index 00000000000000..8a83299533e1d1 --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Message2Provider.java @@ -0,0 +1,54 @@ +package io.quarkus.it.envers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; + +@Provider +@Consumes(MediaType.WILDCARD) +@Produces(MediaType.WILDCARD) +public class Message2Provider implements MessageBodyReader, MessageBodyWriter { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Message2.class.isAssignableFrom(type); + } + + @Override + public Message2 readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + return new Message2("in"); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Message2.class.isAssignableFrom(type); + } + + @Override + public void writeTo(Message2 event, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { + String data = "out"; + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(CustomOutput.class)) { + data = ((CustomOutput) annotation).value(); + break; + } + } + } + entityStream.write(String.format("{\"data\": \"%s\"}", data).getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Output2Resource.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Output2Resource.java new file mode 100644 index 00000000000000..fd397cc5d2e50a --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/Output2Resource.java @@ -0,0 +1,16 @@ +package io.quarkus.it.envers; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("output2") +public class Output2Resource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Message2 out() { + return new Message2("test"); + } +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java index 4376c1378611f2..874f0f0bd2691b 100644 --- a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java @@ -4,7 +4,6 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import org.jboss.resteasy.reactive.RestStreamElementType; @@ -14,12 +13,6 @@ @Path("output") public class OutputResource { - @GET - @Produces(MediaType.APPLICATION_JSON) - public Message out() { - return new Message("test"); - } - @GET @RestStreamElementType(MediaType.APPLICATION_JSON) @CustomOutput("dummy") diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java index 74b6aa4ffa9bbe..af038a6fecab4f 100644 --- a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java @@ -38,7 +38,7 @@ class OutputResourceTest { void test() { given().accept(ContentType.JSON) .when() - .get(RESOURCE_PATH) + .get(RESOURCE_PATH + "2") .then() .statusCode(200) .body("data", equalTo("out")); From 885a3cf640837cbe57a9808d8d0ed42ec21efbe4 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 10 May 2023 10:49:16 +0200 Subject: [PATCH 274/333] Fix NPE when force restarting with kubernetes dev service running Fix https://github.com/quarkusio/quarkus/issues/33006 --- .../client/deployment/DevServicesKubernetesProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java index 54ca2c89d411aa..b4ffb1df00dac5 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java @@ -148,7 +148,7 @@ public DevServicesResultBuildItem setupKubernetesDevService( } private void shutdownCluster() { - if (devService != null) { + if (devService != null && devService.isOwner()) { try { devService.close(); } catch (Throwable e) { From 1fbb639b015efd5ffea34c5d5d1f46f1e1b47f75 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 May 2023 08:24:08 +0300 Subject: [PATCH 275/333] Ensure that ObjectMapperCustomizer impls are used in runtime init Fixes: #32969 --- .../ResteasyReactiveJacksonProcessor.java | 6 +++++ .../RestClientReactiveJacksonProcessor.java | 6 +++++ .../ReinitializeVertxJsonBuildItem.java | 9 +++++++ .../vertx/deployment/VertxJsonProcessor.java | 27 +++++++++++++++++++ .../resteasy-reactive-kotlin/standard/pom.xml | 6 +++++ .../reactive/kotlin/GreetingResource.kt | 11 ++++++++ .../kotlin/RegisterCustomModuleCustomizer.kt | 15 +++++++++++ .../src/main/resources/application.properties | 2 ++ .../reactive/kotlin/GreetingResourceTest.kt | 9 +++++++ 9 files changed, 91 insertions(+) create mode 100644 extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/ReinitializeVertxJsonBuildItem.java create mode 100644 extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxJsonProcessor.java create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index a2187966147cf4..a92e88117b6c8e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -66,6 +66,7 @@ import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem; +import io.quarkus.vertx.deployment.ReinitializeVertxJsonBuildItem; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -100,6 +101,11 @@ ResteasyReactiveJacksonProviderDefinedBuildItem jacksonRegistered() { return new ResteasyReactiveJacksonProviderDefinedBuildItem(); } + @BuildStep + ReinitializeVertxJsonBuildItem vertxJson() { + return new ReinitializeVertxJsonBuildItem(); + } + @BuildStep ExceptionMapperBuildItem exceptionMappers() { return new ExceptionMapperBuildItem(DefaultMismatchedInputException.class.getName(), diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java index 12fe28143cc6e7..68a80e1f0e0a55 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java @@ -23,6 +23,7 @@ import io.quarkus.resteasy.reactive.jackson.runtime.serialisers.vertx.VertxJsonObjectBasicMessageBodyWriter; import io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem; +import io.quarkus.vertx.deployment.ReinitializeVertxJsonBuildItem; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -37,6 +38,11 @@ void feature(BuildProducer features) { features.produce(new FeatureBuildItem(REST_CLIENT_REACTIVE_JACKSON)); } + @BuildStep + ReinitializeVertxJsonBuildItem vertxJson() { + return new ReinitializeVertxJsonBuildItem(); + } + @BuildStep void additionalProviders( List jacksonProviderDefined, diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/ReinitializeVertxJsonBuildItem.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/ReinitializeVertxJsonBuildItem.java new file mode 100644 index 00000000000000..b6b4c697ce7d49 --- /dev/null +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/ReinitializeVertxJsonBuildItem.java @@ -0,0 +1,9 @@ +package io.quarkus.vertx.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Marker build item used to force the re-initialization of Vert.x JSON handling in native-image + */ +public final class ReinitializeVertxJsonBuildItem extends MultiBuildItem { +} diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxJsonProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxJsonProcessor.java new file mode 100644 index 00000000000000..cbecc141c135ee --- /dev/null +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxJsonProcessor.java @@ -0,0 +1,27 @@ +package io.quarkus.vertx.deployment; + +import java.util.List; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.vertx.core.spi.JsonFactory; + +public class VertxJsonProcessor { + + @BuildStep + void nativeSupport(List reinitializeVertxJson, + BuildProducer runtimeReinitializedClassProducer, + BuildProducer serviceProviderBuildItemBuildProducer) { + if (reinitializeVertxJson.isEmpty()) { + return; + } + runtimeReinitializedClassProducer + .produce(new RuntimeReinitializedClassBuildItem(io.vertx.core.json.Json.class.getName())); + runtimeReinitializedClassProducer + .produce(new RuntimeReinitializedClassBuildItem("io.quarkus.vertx.runtime.jackson.QuarkusJacksonJsonCodec")); + serviceProviderBuildItemBuildProducer + .produce(ServiceProviderBuildItem.allProvidersFromClassPath(JsonFactory.class.getName())); + } +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/pom.xml b/integration-tests/resteasy-reactive-kotlin/standard/pom.xml index bf8df26dd43540..758d622b759c33 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/pom.xml +++ b/integration-tests/resteasy-reactive-kotlin/standard/pom.xml @@ -275,12 +275,18 @@ maven-surefire-plugin false + + prod + maven-failsafe-plugin false + + prod + diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResource.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResource.kt index 5adad745db57c7..c5fd5403b6aabf 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResource.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResource.kt @@ -8,11 +8,16 @@ import jakarta.ws.rs.core.Context import jakarta.ws.rs.core.HttpHeaders import jakarta.ws.rs.core.Response import jakarta.ws.rs.core.UriInfo +import java.util.concurrent.atomic.AtomicReference import org.jboss.resteasy.reactive.RestHeader @Path("/greeting") class GreetingResource(val headers: HttpHeaders) { + companion object { + val MY_PROPERTY = AtomicReference("unset") + } + @GET suspend fun testSuspend(@RestHeader("firstName") firstName: String): Greeting { val lastName = headers.getHeaderString("lastName") @@ -28,6 +33,12 @@ class GreetingResource(val headers: HttpHeaders) { greeting: Greeting, @Context uriInfo: UriInfo ) = Response.ok(greeting).build() + + @GET + @Path("prop") + fun testProp(): String? { + return MY_PROPERTY.get() + } } data class Greeting(val message: String) diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt new file mode 100644 index 00000000000000..1600498fb6ad6e --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt @@ -0,0 +1,15 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import com.fasterxml.jackson.databind.ObjectMapper +import io.quarkus.jackson.ObjectMapperCustomizer +import jakarta.inject.Singleton +import org.eclipse.microprofile.config.inject.ConfigProperty + +@Singleton +class RegisterCustomModuleCustomizer : ObjectMapperCustomizer { + @ConfigProperty(name = "test.prop") lateinit var testProp: String + + override fun customize(objectMapper: ObjectMapper) { + GreetingResource.MY_PROPERTY.set(testProp) + } +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties b/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties index dbd2604ffa9a1c..5e5d8b8138f0ca 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties @@ -29,3 +29,5 @@ mp.messaging.incoming.countries-t2-in.auto.offset.reset=earliest mp.messaging.incoming.countries-t2-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer quarkus.package.quiltflower.enabled=true + +test.prop=test diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResourceTest.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResourceTest.kt index 226058ffe4da68..2541a7a53f9083 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResourceTest.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/GreetingResourceTest.kt @@ -39,4 +39,13 @@ class GreetingResourceTest { fun testNoopCoroutine() { When { get("/greeting/noop") } Then { statusCode(204) } } + + @Test + fun testProp() { + When { get("/greeting/prop") } Then + { + statusCode(200) + body(CoreMatchers.`is`("prod")) + } + } } From 10ae12a0d8e5d169177b8263bbd315dad374e9c3 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 10 May 2023 15:57:15 +0300 Subject: [PATCH 276/333] Properly support extracting fields from entities into projections Fixes: #31774 --- .../generate/DerivedMethodsAdder.java | 19 +++++++++++++++++-- .../it/spring/data/jpa/AbstractPost.java | 18 ++++++++++++++++++ .../io/quarkus/it/spring/data/jpa/Post.java | 13 +------------ 3 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/AbstractPost.java diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/DerivedMethodsAdder.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/DerivedMethodsAdder.java index 8f2e2555e9c5da..f4c5168580ffeb 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/DerivedMethodsAdder.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/DerivedMethodsAdder.java @@ -340,7 +340,7 @@ private void generateCustomResultTypes(DotName interfaceName, DotName implName, throw new IllegalArgumentException("Method " + method.name() + " of interface " + interfaceName + " is not a getter method since it returns void"); } - DotName fieldTypeName = getPrimitiveTypeName(returnType.name()); + DotName fieldTypeName = returnType.name(); FieldDescriptor field = implClassCreator.getFieldCreator(propertyName, fieldTypeName.toString()) .getFieldDescriptor(); @@ -363,7 +363,7 @@ private void generateCustomResultTypes(DotName interfaceName, DotName implName, ResultHandle newObject = convert.newInstance(MethodDescriptor.ofConstructor(implName.toString())); ResultHandle entity = convert.getMethodParam(0); - final List availableMethods = entityClassInfo.methods(); + final List availableMethods = availableMethods(entityClassInfo, index); for (Map.Entry field : fields.entrySet()) { if (!getterExists(availableMethods, field.getKey())) { throw new IllegalArgumentException(field.getKey() + " method does not exists in " @@ -381,6 +381,21 @@ private void generateCustomResultTypes(DotName interfaceName, DotName implName, } } + private static List availableMethods(ClassInfo entityClassInfo, IndexView index) { + List result = new ArrayList<>(entityClassInfo.methods().size()); + while (true) { + result.addAll(entityClassInfo.methods()); + if (entityClassInfo.superName() == null) { + break; + } + entityClassInfo = index.getClassByName(entityClassInfo.superName()); + if ((entityClassInfo == null) || DotNames.OBJECT.equals(entityClassInfo.name())) { + break; + } + } + return result; + } + private boolean getterExists(List methods, String getterName) { for (MethodInfo method : methods) { if (method.name().equals(getterName)) { diff --git a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/AbstractPost.java b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/AbstractPost.java new file mode 100644 index 00000000000000..294787480a33e7 --- /dev/null +++ b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/AbstractPost.java @@ -0,0 +1,18 @@ +package io.quarkus.it.spring.data.jpa; + +import java.time.ZonedDateTime; + +import jakarta.persistence.MappedSuperclass; + +@MappedSuperclass +public abstract class AbstractPost { + private ZonedDateTime posted; + + public ZonedDateTime getPosted() { + return posted; + } + + public void setPosted(ZonedDateTime postedAt) { + this.posted = postedAt; + } +} diff --git a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/Post.java b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/Post.java index 8bae2cbac9c67a..8915ccd44267f7 100644 --- a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/Post.java +++ b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/Post.java @@ -1,6 +1,5 @@ package io.quarkus.it.spring.data.jpa; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -18,7 +17,7 @@ @Entity(name = "Post") @Table(name = "post") -public class Post implements ByPassHolder { +public class Post extends AbstractPost implements ByPassHolder { @Id @SequenceGenerator(name = "postSeqGen", sequenceName = "postSeq", initialValue = 100, allocationSize = 1) @@ -35,8 +34,6 @@ public class Post implements ByPassHolder { private boolean bypass; - private ZonedDateTime posted; - private String organization; public Long getId() { @@ -71,14 +68,6 @@ public void setBypass(boolean bypass) { this.bypass = bypass; } - public ZonedDateTime getPosted() { - return posted; - } - - public void setPosted(ZonedDateTime postedAt) { - this.posted = postedAt; - } - public String getOrganization() { return organization; } From 8f43b43e7a6bb294e257694a0772ad0ac142447e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 9 May 2023 18:02:17 +0200 Subject: [PATCH 277/333] Mailer - Add the ability to configure a list of approved recipients Fixes #32688 --- .../mailer/ApproveListNoEmailTest.java | 48 +++++++++ .../io/quarkus/mailer/ApproveListTest.java | 52 +++++++++ .../mailer/runtime/MailerRuntimeConfig.java | 25 +++++ .../io/quarkus/mailer/runtime/Mailers.java | 11 +- .../mailer/runtime/MutinyMailerImpl.java | 101 +++++++++++++++++- .../runtime/TrimmedPatternConverter.java | 26 +++++ ....eclipse.microprofile.config.spi.Converter | 1 + .../mailer/runtime/MailerImplTest.java | 2 +- .../runtime/MailerWithMultipartImplTest.java | 2 +- .../mailer/runtime/MockMailerImplTest.java | 2 +- 10 files changed, 262 insertions(+), 8 deletions(-) create mode 100644 extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListNoEmailTest.java create mode 100644 extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListTest.java create mode 100644 extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/TrimmedPatternConverter.java create mode 100644 extensions/mailer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter diff --git a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListNoEmailTest.java b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListNoEmailTest.java new file mode 100644 index 00000000000000..3e9a847d5eea2d --- /dev/null +++ b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListNoEmailTest.java @@ -0,0 +1,48 @@ +package io.quarkus.mailer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.logging.Level; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ApproveListNoEmailTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset( + "quarkus.mailer.approved-recipients=.*@approved1.com\nquarkus.mailer.log-rejected-recipients=true"), + "application.properties")) + .setLogRecordPredicate(record -> record.getLevel().equals(Level.WARNING)) + .assertLogRecords(lrs -> { + assertTrue(lrs.stream().anyMatch(lr -> lr.getMessage().equals( + "Email 'A subject' was not sent because all recipients were rejected by the configuration: [email1@rejected.com, email2@rejected.com, email3@rejected.com, email4@rejected.com, email5@rejected.com]"))); + }); + + @Inject + Mailer mailer; + + @Inject + MockMailbox mockMailbox; + + @Test + public void testApproveList() { + mailer.send(Mail.withText("email1@rejected.com", "A subject", "") + .addCc("email2@rejected.com", "email3@rejected.com") + .addBcc("email4@rejected.com", "email5@rejected.com")); + + assertEquals(0, mockMailbox.getMailMessagesSentTo("email1@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email2@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email3@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email4@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email5@rejected.com").size()); + } +} diff --git a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListTest.java b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListTest.java new file mode 100644 index 00000000000000..cb134a6f949db5 --- /dev/null +++ b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/ApproveListTest.java @@ -0,0 +1,52 @@ +package io.quarkus.mailer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.logging.Level; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ApproveListTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset( + "quarkus.mailer.approved-recipients=.*@approved1.com,.*@approved2.com,.*@approved3.com\nquarkus.mailer.log-rejected-recipients=true"), + "application.properties")) + .setLogRecordPredicate(record -> record.getLevel().equals(Level.WARNING)) + .assertLogRecords(lrs -> { + assertTrue(lrs.stream().anyMatch(lr -> lr.getMessage().equals( + "Email 'A subject' was not sent to the following recipients as they were rejected by the configuration: [email1@rejected.com, email2@rejected.com, email3@rejected.com, email4@rejected.com, email5@rejected.com]"))); + }); + + @Inject + Mailer mailer; + + @Inject + MockMailbox mockMailbox; + + @Test + public void testApproveList() { + mailer.send(Mail.withText("email@approved1.com", "A subject", "") + .addTo("email1@rejected.com") + .addCc("email@approved2.com", "email2@rejected.com", "email3@rejected.com") + .addBcc("email@approved3.com", "email4@rejected.com", "email5@rejected.com")); + + assertEquals(1, mockMailbox.getMailMessagesSentTo("email@approved1.com").size()); + assertEquals(1, mockMailbox.getMailMessagesSentTo("email@approved2.com").size()); + assertEquals(1, mockMailbox.getMailMessagesSentTo("email@approved3.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email1@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email2@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email3@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email4@rejected.com").size()); + assertEquals(0, mockMailbox.getMailMessagesSentTo("email5@rejected.com").size()); + } +} diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailerRuntimeConfig.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailerRuntimeConfig.java index 1aefe876ed099f..4e9926e28985b7 100644 --- a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailerRuntimeConfig.java +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailerRuntimeConfig.java @@ -1,11 +1,14 @@ package io.quarkus.mailer.runtime; import java.time.Duration; +import java.util.List; import java.util.Optional; import java.util.OptionalInt; +import java.util.regex.Pattern; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConvertWith; @ConfigGroup public class MailerRuntimeConfig { @@ -210,4 +213,26 @@ public class MailerRuntimeConfig { @ConfigItem public NtlmConfig ntlm = new NtlmConfig(); + /** + * Allows sending emails to these recipients only. + *

    + * Approved recipients are compiled to a {@code Pattern} and must be a valid regular expression. + * The created {@code Pattern} is case-insensitive as emails are case insensitive. + * Provided patterns are trimmed before being compiled. + * + * @see {@link #logRejectedRecipients} + */ + @ConfigItem + @ConvertWith(TrimmedPatternConverter.class) + public Optional> approvedRecipients = Optional.empty(); + + /** + * Log rejected recipients as warnings. + *

    + * If false, the rejected recipients will be logged at the DEBUG level. + * + * @see {@link #approvedRecipients} + */ + @ConfigItem(defaultValue = "false") + public boolean logRejectedRecipients = false; } diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/Mailers.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/Mailers.java index 472778ab49b534..4239dd46abaac5 100644 --- a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/Mailers.java +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/Mailers.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import jakarta.annotation.PreDestroy; import jakarta.inject.Singleton; @@ -65,7 +66,10 @@ public Mailers(Vertx vertx, io.vertx.mutiny.core.Vertx mutinyVertx, MailersRunti new MutinyMailerImpl(mutinyVertx, mutinyMailClient, mockMailbox, mailersRuntimeConfig.defaultMailer.from.orElse(null), mailersRuntimeConfig.defaultMailer.bounceAddress.orElse(null), - mailersRuntimeConfig.defaultMailer.mock.orElse(launchMode.isDevOrTest()))); + mailersRuntimeConfig.defaultMailer.mock.orElse(launchMode.isDevOrTest()), + mailersRuntimeConfig.defaultMailer.approvedRecipients.orElse(List.of()).stream() + .filter(p -> p != null).collect(Collectors.toList()), + mailersRuntimeConfig.defaultMailer.logRejectedRecipients)); } for (String name : mailerSupport.namedMailers) { @@ -83,7 +87,10 @@ public Mailers(Vertx vertx, io.vertx.mutiny.core.Vertx mutinyVertx, MailersRunti new MutinyMailerImpl(mutinyVertx, namedMutinyMailClient, namedMockMailbox, namedMailerRuntimeConfig.from.orElse(null), namedMailerRuntimeConfig.bounceAddress.orElse(null), - namedMailerRuntimeConfig.mock.orElse(false))); + namedMailerRuntimeConfig.mock.orElse(false), + namedMailerRuntimeConfig.approvedRecipients.orElse(List.of()).stream() + .filter(p -> p != null).collect(Collectors.toList()), + namedMailerRuntimeConfig.logRejectedRecipients)); } this.clients = Collections.unmodifiableMap(localClients); diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java index c39f5391301252..b6baa1a56b75ef 100644 --- a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MutinyMailerImpl.java @@ -3,11 +3,14 @@ import static java.util.Arrays.stream; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Flow.Publisher; import java.util.function.Function; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jboss.logging.Logger; @@ -40,16 +43,23 @@ public class MutinyMailerImpl implements ReactiveMailer { private final String bounceAddress; - private boolean mock; + private final boolean mock; + + private final List approvedRecipients; + + private boolean logRejectedRecipients; MutinyMailerImpl(Vertx vertx, MailClient client, MockMailboxImpl mockMailbox, - String from, String bounceAddress, boolean mock) { + String from, String bounceAddress, boolean mock, List approvedRecipients, + boolean logRejectedRecipients) { this.vertx = vertx; this.client = client; this.mockMailbox = mockMailbox; this.from = from; this.bounceAddress = bounceAddress; this.mock = mock; + this.approvedRecipients = approvedRecipients; + this.logRejectedRecipients = logRejectedRecipients; } @Override @@ -77,9 +87,41 @@ public Uni apply(MailMessage mailMessage) { } private Uni send(Mail mail, MailMessage message) { + if (!approvedRecipients.isEmpty()) { + Recipients to = filterApprovedRecipients(message.getTo()); + Recipients cc = filterApprovedRecipients(message.getCc()); + Recipients bcc = filterApprovedRecipients(message.getBcc()); + + if (to.approved.isEmpty() && cc.approved.isEmpty() && bcc.approved.isEmpty()) { + logRejectedRecipients("Email '%s' was not sent because all recipients were rejected by the configuration: %s", + message.getSubject(), to.rejected, cc.rejected, bcc.rejected); + return Uni.createFrom().voidItem(); + } + + if (!to.rejected.isEmpty() || !cc.rejected.isEmpty() || !bcc.rejected.isEmpty()) { + logRejectedRecipients( + "Email '%s' was not sent to the following recipients as they were rejected by the configuration: %s", + message.getSubject(), to.rejected, cc.rejected, bcc.rejected); + } + + if (!to.rejected.isEmpty()) { + mail.setTo(to.approved); + message.setTo(to.approved); + } + if (!cc.rejected.isEmpty()) { + mail.setCc(cc.approved); + message.setCc(cc.approved); + } + if (!bcc.rejected.isEmpty()) { + mail.setBcc(bcc.approved); + message.setBcc(bcc.approved); + } + } + if (mock) { - LOGGER.infof("Sending email %s from %s to %s, text body: \n%s\nhtml body: \n%s", + LOGGER.infof("Sending email %s from %s to %s (cc: %s, bcc: %s), text body: \n%s\nhtml body: \n%s", message.getSubject(), message.getFrom(), message.getTo(), + message.getCc(), message.getBcc(), message.getText() == null ? "" : message.getText(), message.getHtml() == null ? "" : message.getHtml()); return mockMailbox.send(mail, message); @@ -103,6 +145,7 @@ private Uni toMailMessage(Mail mail) { } else { message.setFrom(from); } + message.setTo(mail.getTo()); message.setCc(mail.getCc()); message.setBcc(mail.getBcc()); @@ -166,6 +209,47 @@ private Uni toMailAttachment(Attachment attachment) { .onItem().transform(attach::setData); } + private Recipients filterApprovedRecipients(List emails) { + if (approvedRecipients.isEmpty()) { + return new Recipients(emails, List.of()); + } + + List allowedRecipients = new ArrayList<>(); + List rejectedRecipients = new ArrayList<>(); + + emailLoop: for (String email : emails) { + for (Pattern approvedRecipient : approvedRecipients) { + if (approvedRecipient.matcher(email).matches()) { + allowedRecipients.add(email); + continue emailLoop; + } + } + + rejectedRecipients.add(email); + } + + return new Recipients(allowedRecipients, rejectedRecipients); + } + + @SafeVarargs + private void logRejectedRecipients(String logMessage, String subject, List... rejectedRecipientLists) { + if (logRejectedRecipients) { + Set allRejectedRecipients = new LinkedHashSet<>(); + for (List rejectedRecipients : rejectedRecipientLists) { + allRejectedRecipients.addAll(rejectedRecipients); + } + + LOGGER.warn(String.format(logMessage, subject, allRejectedRecipients)); + } else if (LOGGER.isDebugEnabled()) { + List allRejectedRecipients = new ArrayList<>(); + for (List rejectedRecipients : rejectedRecipientLists) { + allRejectedRecipients.addAll(rejectedRecipients); + } + + LOGGER.warn(String.format(logMessage, subject, allRejectedRecipients)); + } + } + public static Uni getAttachmentStream(Vertx vertx, Attachment attachment) { if (attachment.getFile() != null) { Uni open = vertx.fileSystem().open(attachment.getFile().getAbsolutePath(), @@ -183,4 +267,15 @@ public static Uni getAttachmentStream(Vertx vertx, Attachment attachment return Uni.createFrom().failure(new IllegalArgumentException("Attachment has no data")); } } + + private static class Recipients { + + private final List approved; + private final List rejected; + + Recipients(List approved, List rejected) { + this.approved = approved; + this.rejected = rejected; + } + } } diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/TrimmedPatternConverter.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/TrimmedPatternConverter.java new file mode 100644 index 00000000000000..97e5b1649ffdbb --- /dev/null +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/TrimmedPatternConverter.java @@ -0,0 +1,26 @@ +package io.quarkus.mailer.runtime; + +import java.util.regex.Pattern; + +import org.eclipse.microprofile.config.spi.Converter; + +public class TrimmedPatternConverter implements Converter { + + public TrimmedPatternConverter() { + } + + @Override + public Pattern convert(String s) { + if (s == null) { + return null; + } + + String trimmedString = s.trim().toLowerCase(); + + if (trimmedString.isEmpty()) { + return null; + } + + return Pattern.compile(trimmedString, Pattern.CASE_INSENSITIVE); + } +} diff --git a/extensions/mailer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter b/extensions/mailer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter new file mode 100644 index 00000000000000..14605a1850a868 --- /dev/null +++ b/extensions/mailer/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter @@ -0,0 +1 @@ +io.quarkus.mailer.runtime.TrimmedPatternConverter \ No newline at end of file diff --git a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java index af8ae580a65536..85e4b4dbeedb34 100644 --- a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java +++ b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java @@ -55,7 +55,7 @@ void init() { mailer = new MutinyMailerImpl(vertx, MailClient.createShared(vertx, new MailConfig().setPort(wiser.getServer().getPort())), - null, FROM, null, false); + null, FROM, null, false, List.of(), false); wiser.getMessages().clear(); } diff --git a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerWithMultipartImplTest.java b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerWithMultipartImplTest.java index 9e930a3e709e4a..9800244d536888 100644 --- a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerWithMultipartImplTest.java +++ b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerWithMultipartImplTest.java @@ -62,7 +62,7 @@ static void stopWiser() { void init() { mailer = new MutinyMailerImpl(vertx, MailClient.createShared(vertx, new MailConfig().setPort(wiser.getServer().getPort()).setMultiPartOnly(true)), null, - FROM, null, false); + FROM, null, false, List.of(), false); wiser.getMessages().clear(); } diff --git a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java index 381c832dab9e9f..453460c5407200 100644 --- a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java +++ b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java @@ -35,7 +35,7 @@ static void stop() { @BeforeEach void init() { mockMailbox = new MockMailboxImpl(); - mailer = new MutinyMailerImpl(vertx, null, mockMailbox, FROM, null, true); + mailer = new MutinyMailerImpl(vertx, null, mockMailbox, FROM, null, true, List.of(), false); } @Test From 57a58d3d084ff895911352ad169990acad173cae Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 8 May 2023 19:32:14 +0100 Subject: [PATCH 278/333] Verify OIDC ID token audience by default --- .../io/quarkus/oidc/OidcTenantConfig.java | 8 +++++ .../runtime/CodeAuthenticationMechanism.java | 24 +++++++++------ .../oidc/runtime/OidcIdentityProvider.java | 30 +++++++++++-------- .../io/quarkus/oidc/runtime/OidcProvider.java | 29 ++++++++++++------ .../keycloak/CustomTenantConfigResolver.java | 5 +++- .../io/quarkus/it/keycloak/OidcResource.java | 23 ++++++++------ .../BearerTokenAuthorizationTest.java | 4 +-- .../src/main/resources/application.properties | 5 +++- .../keycloak/CodeFlowAuthorizationTest.java | 6 +++- 9 files changed, 90 insertions(+), 44 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index dffe360fb5e0a3..33d0454d4fa527 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -1112,6 +1112,14 @@ public static Token fromAudience(String... audience) { /** * Expected audience 'aud' claim value which may be a string or an array of strings. + * + * Note the audience claim will be verified for ID tokens by default. + * ID token audience must be equal to the value of `quarkus.oidc.client-id` property. + * Use this property to override the expected value if your OpenID Connect provider + * sets a different audience claim value in ID tokens. Set it to `any` if your provider + * does not set ID token audience` claim. + * + * Audience verification for access tokens will only be done if this property is configured. */ @ConfigItem public Optional> audience = Optional.empty(); 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 5f423c81f904a5..9a0a692305c4e4 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 @@ -317,7 +317,7 @@ public Uni apply(Throwable t) { .hasErrorCode(ErrorCodes.EXPIRED); if (!expired) { - LOG.errorf("ID token verification failure: %s", t.getCause()); + LOG.errorf("ID token verification failure: %s", errorMessage(t)); return removeSessionCookie(context, configContext.oidcConfig) .replaceWith(Uni.createFrom() .failure(t @@ -667,14 +667,17 @@ public Uni apply(final AuthorizationCodeTokens tokens, final T return Uni.createFrom().failure(new AuthenticationCompletionException(tOuter)); } - boolean internalIdToken = !isIdTokenRequired(configContext); + final boolean internalIdToken; if (tokens.getIdToken() == null) { - if (!internalIdToken) { + if (isIdTokenRequired(configContext)) { LOG.errorf("ID token is not available in the authorization code grant response"); return Uni.createFrom().failure(new AuthenticationCompletionException()); } else { tokens.setIdToken(generateInternalIdToken(configContext.oidcConfig, null, null)); + internalIdToken = true; } + } else { + internalIdToken = false; } context.put(NEW_AUTHENTICATION, Boolean.TRUE); @@ -739,16 +742,18 @@ public Throwable apply(Throwable tInner) { LOG.debugf("Starting the final redirect"); return tInner; } - String message = tInner.getCause() != null ? tInner.getCause().getMessage() - : tInner.getMessage(); - LOG.errorf("ID token verification has failed: %s", message); + + LOG.errorf("ID token verification has failed: %s", errorMessage(tInner)); return new AuthenticationCompletionException(tInner); } }); } }); + } + private static Object errorMessage(Throwable t) { + return t.getCause() != null ? t.getCause().getMessage() : t.getMessage(); } private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedStateCookieValue, @@ -794,6 +799,7 @@ private String generateInternalIdToken(OidcTenantConfig oidcConfig, UserInfo use if (oidcConfig.authentication.internalIdTokenLifespan.isPresent()) { builder.expiresIn(oidcConfig.authentication.internalIdTokenLifespan.get().getSeconds()); } + builder.audience(oidcConfig.getClientId().get()); return builder.jws().header(INTERNAL_IDTOKEN_HEADER, true) .sign(KeyUtils.createSecretKeyFromSecret(OidcCommonUtils.clientSecret(oidcConfig.credentials))); } @@ -1025,7 +1031,7 @@ private Uni refreshSecurityIdentity(TenantConfigContext config @Override public Uni apply(final AuthorizationCodeTokens tokens, final Throwable t) { if (t != null) { - LOG.debugf("ID token refresh has failed: %s", t.getMessage()); + LOG.debugf("ID token refresh has failed: %s", errorMessage(t)); if (autoRefresh && fallback != null) { LOG.debug("Using the current SecurityIdentity since the ID token is still valid"); return Uni.createFrom().item(fallback); @@ -1064,7 +1070,7 @@ public SecurityIdentity apply(SecurityIdentity identity) { }).onFailure().transform(new Function() { @Override public Throwable apply(Throwable tInner) { - LOG.debugf("Verifying the refreshed ID token failed %s", tInner.getMessage()); + LOG.debugf("Verifying the refreshed ID token failed %s", errorMessage(tInner)); return new AuthenticationFailedException(tInner); } }); @@ -1085,7 +1091,7 @@ public AuthorizationCodeTokens apply(AuthorizationCodeTokens tokens) { } if (tokens.getIdToken() == null) { - if (isIdTokenRequired(configContext)) { + if (isIdTokenRequired(configContext) || !isInternalIdToken(currentIdToken, configContext)) { if (!autoRefresh) { LOG.debugf( "ID token is not returned in the refresh token grant response, re-authentication is required"); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 7595cf1a04a195..31411775d3341e 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -216,7 +216,7 @@ private Uni createSecurityIdentityWithOidcServer(RoutingContex tokenUni = verifySelfSignedTokenUni(resolvedContext, request.getToken().getToken()); } } else { - tokenUni = verifyTokenUni(resolvedContext, request.getToken().getToken(), userInfo); + tokenUni = verifyTokenUni(resolvedContext, request.getToken().getToken(), isIdToken(request), userInfo); } return tokenUni.onItemOrFailure() @@ -246,7 +246,7 @@ public Uni apply(TokenVerificationResult result, Throwable t) resolvedContext, tokenJson, rolesJson, userInfo, result.introspectionResult); // If the primary token is a bearer access token then there's no point of checking if // it should be refreshed as RT is only available for the code flow tokens - if (tokenCred instanceof IdTokenCredential + if (isIdToken(request) && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { return Uni.createFrom().failure(new TokenAutoRefreshException(securityIdentity)); } else { @@ -255,7 +255,7 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { } catch (Throwable ex) { return Uni.createFrom().failure(new AuthenticationFailedException(ex)); } - } else if (tokenCred instanceof IdTokenCredential + } else if (isIdToken(request) || tokenCred instanceof AccessTokenCredential && !((AccessTokenCredential) tokenCred).isOpaque()) { return Uni.createFrom() @@ -314,7 +314,7 @@ public String getName() { SecurityIdentity identity = builder.build(); // If the primary token is a bearer access token then there's no point of checking if // it should be refreshed as RT is only available for the code flow tokens - if (tokenCred instanceof IdTokenCredential + if (isIdToken(request) && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { return Uni.createFrom().failure(new TokenAutoRefreshException(identity)); } @@ -325,7 +325,11 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { } private static boolean isInternalIdToken(TokenAuthenticationRequest request) { - return (request.getToken() instanceof IdTokenCredential) && ((IdTokenCredential) request.getToken()).isInternal(); + return isIdToken(request) && ((IdTokenCredential) request.getToken()).isInternal(); + } + + private static boolean isIdToken(TokenAuthenticationRequest request) { + return request.getToken() instanceof IdTokenCredential; } private static boolean tokenAutoRefreshPrepared(TokenVerificationResult result, RoutingContext vertxContext, @@ -377,13 +381,14 @@ private Uni verifyCodeFlowAccessTokenUni(RoutingContext && (resolvedContext.oidcConfig.authentication.verifyAccessToken || resolvedContext.oidcConfig.roles.source.orElse(null) == Source.accesstoken)) { final String codeAccessToken = (String) vertxContext.get(OidcConstants.ACCESS_TOKEN_VALUE); - return verifyTokenUni(resolvedContext, codeAccessToken, userInfo); + return verifyTokenUni(resolvedContext, codeAccessToken, false, userInfo); } else { return NULL_CODE_ACCESS_TOKEN_UNI; } } - private Uni verifyTokenUni(TenantConfigContext resolvedContext, String token, UserInfo userInfo) { + private Uni verifyTokenUni(TenantConfigContext resolvedContext, + String token, boolean enforceAudienceVerification, UserInfo userInfo) { if (OidcUtils.isOpaqueToken(token)) { if (!resolvedContext.oidcConfig.token.allowOpaqueTokenIntrospection) { LOG.debug("Token is opaque but the opaque token introspection is not allowed"); @@ -411,11 +416,11 @@ private Uni verifyTokenUni(TenantConfigContext resolved // Verify JWT token with the local JWK keys with a possible remote introspection fallback try { LOG.debug("Verifying the JWT token with the local JWK keys"); - return Uni.createFrom().item(resolvedContext.provider.verifyJwtToken(token)); + return Uni.createFrom().item(resolvedContext.provider.verifyJwtToken(token, enforceAudienceVerification)); } catch (Throwable t) { if (t.getCause() instanceof UnresolvableKeyException) { LOG.debug("No matching JWK key is found, refreshing and repeating the verification"); - return refreshJwksAndVerifyTokenUni(resolvedContext, token); + return refreshJwksAndVerifyTokenUni(resolvedContext, token, enforceAudienceVerification); } else { LOG.debugf("Token verification has failed: %s", t.getMessage()); return Uni.createFrom().failure(t); @@ -432,8 +437,9 @@ private Uni verifySelfSignedTokenUni(TenantConfigContex } } - private Uni refreshJwksAndVerifyTokenUni(TenantConfigContext resolvedContext, String token) { - return resolvedContext.provider.refreshJwksAndVerifyJwtToken(token) + private Uni refreshJwksAndVerifyTokenUni(TenantConfigContext resolvedContext, String token, + boolean enforceAudienceVerification) { + return resolvedContext.provider.refreshJwksAndVerifyJwtToken(token, enforceAudienceVerification) .onFailure(f -> f.getCause() instanceof UnresolvableKeyException && resolvedContext.oidcConfig.token.allowJwtIntrospection) .recoverWithUni(f -> introspectTokenUni(resolvedContext, token)); @@ -473,7 +479,7 @@ private static Uni validateTokenWithoutOidcServer(TokenAuthent TenantConfigContext resolvedContext) { try { - TokenVerificationResult result = resolvedContext.provider.verifyJwtToken(request.getToken().getToken()); + TokenVerificationResult result = resolvedContext.provider.verifyJwtToken(request.getToken().getToken(), false); return Uni.createFrom() .item(validateAndCreateIdentity(null, request.getToken(), resolvedContext, result.localVerificationResult, result.localVerificationResult, null, null)); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java index 8c40298b1ff294..8dc087466d770d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java @@ -41,6 +41,7 @@ public class OidcProvider implements Closeable { private static final Logger LOG = Logger.getLogger(OidcProvider.class); private static final String ANY_ISSUER = "any"; + private static final String ANY_AUDIENCE = "any"; private static final String[] ASYMMETRIC_SUPPORTED_ALGORITHMS = new String[] { SignatureAlgorithm.RS256.getAlgorithm(), SignatureAlgorithm.RS384.getAlgorithm(), SignatureAlgorithm.RS512.getAlgorithm(), @@ -103,16 +104,19 @@ private Map checkRequiredClaimsProp() { } public TokenVerificationResult verifySelfSignedJwtToken(String token) throws InvalidJwtException { - return verifyJwtTokenInternal(token, SYMMETRIC_ALGORITHM_CONSTRAINTS, new SymmetricKeyResolver(), true); + return verifyJwtTokenInternal(token, true, SYMMETRIC_ALGORITHM_CONSTRAINTS, new SymmetricKeyResolver(), true); } - public TokenVerificationResult verifyJwtToken(String token) throws InvalidJwtException { - return verifyJwtTokenInternal(token, ASYMMETRIC_ALGORITHM_CONSTRAINTS, asymmetricKeyResolver, true); + public TokenVerificationResult verifyJwtToken(String token, boolean enforceAudienceVerification) + throws InvalidJwtException { + return verifyJwtTokenInternal(token, enforceAudienceVerification, ASYMMETRIC_ALGORITHM_CONSTRAINTS, + asymmetricKeyResolver, true); } public TokenVerificationResult verifyLogoutJwtToken(String token) throws InvalidJwtException { final boolean enforceExpReq = !oidcConfig.token.age.isPresent(); - TokenVerificationResult result = verifyJwtTokenInternal(token, ASYMMETRIC_ALGORITHM_CONSTRAINTS, asymmetricKeyResolver, + TokenVerificationResult result = verifyJwtTokenInternal(token, true, ASYMMETRIC_ALGORITHM_CONSTRAINTS, + asymmetricKeyResolver, enforceExpReq); if (!enforceExpReq) { // Expiry check was skipped during the initial verification but if the logout token contains the exp claim @@ -126,7 +130,8 @@ public TokenVerificationResult verifyLogoutJwtToken(String token) throws Invalid return result; } - private TokenVerificationResult verifyJwtTokenInternal(String token, AlgorithmConstraints algConstraints, + private TokenVerificationResult verifyJwtTokenInternal(String token, boolean enforceAudienceVerification, + AlgorithmConstraints algConstraints, VerificationKeyResolver verificationKeyResolver, boolean enforceExpReq) throws InvalidJwtException { JwtConsumerBuilder builder = new JwtConsumerBuilder(); @@ -143,11 +148,17 @@ private TokenVerificationResult verifyJwtTokenInternal(String token, AlgorithmCo builder.setExpectedIssuer(issuer); } if (audience != null) { - builder.setExpectedAudience(audience); + if (audience.length == 1 && audience[0].equals(ANY_AUDIENCE)) { + builder.setSkipDefaultAudienceValidation(); + } else { + builder.setExpectedAudience(audience); + } + } else if (enforceAudienceVerification) { + builder.setExpectedAudience(oidcConfig.clientId.get()); } else { builder.setSkipDefaultAudienceValidation(); } - if (requiredClaims != null) { + if (requiredClaims != null && !requiredClaims.isEmpty()) { builder.registerValidator(new CustomClaimsValidator(requiredClaims)); } @@ -193,14 +204,14 @@ private void verifyTokenAge(Long iat) throws InvalidJwtException { } } - public Uni refreshJwksAndVerifyJwtToken(String token) { + public Uni refreshJwksAndVerifyJwtToken(String token, boolean enforceAudienceVerification) { return asymmetricKeyResolver.refresh().onItem() .transformToUni(new Function>() { @Override public Uni apply(Void v) { try { - return Uni.createFrom().item(verifyJwtToken(token)); + return Uni.createFrom().item(verifyJwtToken(token, enforceAudienceVerification)); } catch (Throwable t) { return Uni.createFrom().failure(t); } diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java index 2d2ee30dea0913..ed0d4015fb0fcf 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java @@ -13,6 +13,7 @@ import io.quarkus.oidc.OidcTenantConfig.Roles.Source; import io.quarkus.oidc.TenantConfigResolver; import io.quarkus.oidc.common.runtime.OidcCommonConfig.Credentials; +import io.quarkus.oidc.common.runtime.OidcCommonConfig.Credentials.Secret.Method; import io.smallrye.jwt.algorithm.SignatureAlgorithm; import io.smallrye.mutiny.Uni; import io.vertx.ext.web.RoutingContext; @@ -126,10 +127,12 @@ public OidcTenantConfig get() { config.setTenantId("tenant-web-app-refresh"); config.setApplicationType(ApplicationType.WEB_APP); config.getToken().setRefreshExpired(true); + config.getToken().setRefreshTokenTimeSkew(Duration.ofSeconds(3)); config.setAuthServerUrl(getIssuerUrl() + "/realms/quarkus-webapp"); config.setClientId("quarkus-app-webapp"); - config.getCredentials().setSecret( + config.getCredentials().getClientSecret().setValue( "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); + config.getCredentials().getClientSecret().setMethod(Method.POST); // Let Keycloak issue a login challenge but use the test token endpoint String uri = context.request().absoluteURI(); diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java index 8a1721396fe3c3..4dd17153b0e284 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java @@ -26,6 +26,7 @@ import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.smallrye.jwt.auth.principal.JWTParser; import io.smallrye.jwt.build.Jwt; +import io.smallrye.jwt.build.JwtClaimsBuilder; import io.smallrye.jwt.util.KeyUtils; @Path("oidc") @@ -196,10 +197,10 @@ public String userinfo() { @POST @Path("token") @Produces("application/json") - public String token(@FormParam("grant_type") String grantType) { + public String token(@FormParam("grant_type") String grantType, @FormParam("client_id") String clientId) { if ("authorization_code".equals(grantType)) { - return "{\"id_token\": \"" + jwt("1") + "\"," + - "\"access_token\": \"" + jwt("1") + "\"," + + return "{\"id_token\": \"" + jwt(clientId, "1") + "\"," + + "\"access_token\": \"" + jwt(clientId, "1") + "\"," + " \"token_type\": \"Bearer\"," + " \"refresh_token\": \"123456789\"," + " \"expires_in\": 300 }"; @@ -209,7 +210,7 @@ public String token(@FormParam("grant_type") String grantType) { if (refreshEndpointCallCount++ == 0) { // first refresh token request, check the original ID token is used - return "{\"access_token\": \"" + jwt("1") + "\"," + + return "{\"access_token\": \"" + jwt(clientId, "1") + "\"," + " \"token_type\": \"Bearer\"," + " \"expires_in\": 300 }"; } else { @@ -227,7 +228,7 @@ public String token(@FormParam("grant_type") String grantType) { @Path("accesstoken") @Produces("application/json") public String testAccessToken(@QueryParam("kid") String kid) { - return "{\"access_token\": \"" + jwt(kid) + "\"," + + return "{\"access_token\": \"" + jwt(null, kid) + "\"," + " \"token_type\": \"Bearer\"," + " \"refresh_token\": \"123456789\"," + " \"expires_in\": 300 }"; @@ -285,14 +286,18 @@ public boolean disableRotate() { return rotate; } - private String jwt(String kid) { - return Jwt.claims() + private String jwt(String audience, String kid) { + JwtClaimsBuilder builder = Jwt.claims() .claim("typ", "Bearer") .upn("alice") .preferredUserName("alice") .groups("user") - .expiresIn(Duration.ofSeconds(4)) - .jws().keyId(kid) + .expiresIn(Duration.ofSeconds(4)); + if (audience != null) { + builder.audience(audience); + } + + return builder.jws().keyId(kid) .sign(key.getPrivateKey()); } } diff --git a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index 606ac82267089b..151454eeb2d71e 100644 --- a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -126,7 +126,7 @@ public void testCodeFlowRefreshTokens() throws IOException, InterruptedException // id and access tokens should have new values, refresh token value should remain the same. // No new sign-in process is required. //await().atLeast(6, TimeUnit.SECONDS); - Thread.sleep(6 * 1000); + Thread.sleep(2 * 1000); webClient.getOptions().setRedirectEnabled(false); WebResponse webResponse = webClient @@ -138,7 +138,7 @@ public void testCodeFlowRefreshTokens() throws IOException, InterruptedException Cookie sessionCookie2 = getSessionCookie(webClient, "tenant-web-app-refresh"); assertNotNull(sessionCookie2); - assertNotEquals(sessionCookie2.getValue(), sessionCookie.getValue()); + assertEquals(sessionCookie2.getValue(), sessionCookie.getValue()); assertNotNull(getSessionAtCookie(webClient, "tenant-web-app-refresh")); Cookie rtCookie2 = getSessionRtCookie(webClient, "tenant-web-app-refresh"); assertNotNull(rtCookie2); diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index a549bdae45d0d9..827cbefb174d81 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -19,6 +19,7 @@ quarkus.oidc.code-flow.logout.post-logout-uri-param=returnTo quarkus.oidc.code-flow.logout.extra-params.client_id=${quarkus.oidc.code-flow.client-id} quarkus.oidc.code-flow.credentials.secret=secret quarkus.oidc.code-flow.application-type=web-app +quarkus.oidc.code-flow.token.audience=https://server.example.com quarkus.oidc.code-flow-encrypted-id-token-jwk.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.code-flow-encrypted-id-token-jwk.client-id=quarkus-web-app @@ -26,6 +27,7 @@ quarkus.oidc.code-flow-encrypted-id-token-jwk.credentials.secret=secret quarkus.oidc.code-flow-encrypted-id-token-jwk.application-type=web-app quarkus.oidc.code-flow-encrypted-id-token-jwk.token-path=${keycloak.url}/realms/quarkus/encrypted-id-token quarkus.oidc.code-flow-encrypted-id-token-jwk.token.decryption-key-location=privateKeyEncryptedIdToken.jwk +quarkus.oidc.code-flow-encrypted-id-token-jwk.token.audience=https://server.example.com quarkus.oidc.code-flow-encrypted-id-token-pem.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.code-flow-encrypted-id-token-pem.client-id=quarkus-web-app @@ -33,6 +35,7 @@ quarkus.oidc.code-flow-encrypted-id-token-pem.credentials.secret=secret quarkus.oidc.code-flow-encrypted-id-token-pem.application-type=web-app quarkus.oidc.code-flow-encrypted-id-token-pem.token-path=encrypted-id-token quarkus.oidc.code-flow-encrypted-id-token-pem.token.decryption-key-location=privateKey.pem +quarkus.oidc.code-flow-encrypted-id-token-pem.token.audience=any quarkus.oidc.code-flow-form-post.auth-server-url=${keycloak.url}/realms/quarkus-form-post/ quarkus.oidc.code-flow-form-post.client-id=quarkus-web-app @@ -48,7 +51,7 @@ quarkus.oidc.code-flow-form-post.token-path=${keycloak.url}/realms/quarkus/token quarkus.oidc.code-flow-form-post.jwks-path=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs quarkus.oidc.code-flow-form-post.logout.backchannel.path=/back-channel-logout quarkus.oidc.code-flow-form-post.logout.frontchannel.path=/code-flow-form-post/front-channel-logout - +quarkus.oidc.code-flow-form-post.token.audience=https://server.example.com quarkus.oidc.code-flow-user-info-only.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.code-flow-user-info-only.discovery-enabled=false diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java index ac8c97a55304d9..0a3536eeb0db0a 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java @@ -85,8 +85,12 @@ public void testCodeFlow() throws IOException { } @Test - public void testCodeFlowEncryptedIdToken() throws IOException { + public void testCodeFlowEncryptedIdTokenJwk() throws IOException { doTestCodeFlowEncryptedIdToken("code-flow-encrypted-id-token-jwk"); + } + + @Test + public void testCodeFlowEncryptedIdTokenPem() throws IOException { doTestCodeFlowEncryptedIdToken("code-flow-encrypted-id-token-pem"); } From 9829270c9e8795034aa348880096fc8562e14ff9 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 10 May 2023 19:47:44 +0200 Subject: [PATCH 279/333] Bump dekorate to 3.6.0 Release changes: https://github.com/dekorateio/dekorate/releases/tag/3.6.0 --- 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 5c2d7f2a6643a2..fda3f509d69ecd 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -160,7 +160,7 @@ 1.8.21 1.6.4 1.5.0 - 3.5.5 + 3.6.0 3.2.0 4.2.0 1.1.1 From b8b5de54a3adb14acff3213228decdf1ccca795b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Thu, 11 May 2023 00:37:05 +0200 Subject: [PATCH 280/333] Test: Correct OpenTelemetry Baggage bean injection assertion --- .../io/quarkus/it/opentelemetry/util/InjectionResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java index 43cc37f05bbe7a..f991a4f0b5ba08 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/util/InjectionResource.java @@ -48,7 +48,7 @@ private void verifyInjections() { Assertions.assertNotNull(openTelemetry, "OpenTelemetry cannot be injected"); Assertions.assertNotNull(tracer, "Tracer cannot be injected"); Assertions.assertNotNull(span, "Span cannot be injected"); - Assertions.assertNotNull(openTelemetry, "Baggage cannot be injected"); + Assertions.assertNotNull(baggage, "Baggage cannot be injected"); // GlobalOpenTelemetry.get() returns an Obfuscated OpenTelemetry instance that // is not equal to the injected one but contains the same objects. From 61ba8218a932502f1637366be8ec03cbf4fc035c Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 10 May 2023 22:13:11 +1000 Subject: [PATCH 281/333] Dev UI: Allow runtime links in external page Signed-off-by: Phillip Kruger --- .../dev-ui/qwc/qwc-extension-link.js | 60 +++++++++++++------ .../resources/dev-ui/qwc/qwc-extensions.js | 1 + .../resources/dev-ui/qwc/qwc-external-page.js | 16 ++++- .../devui/spi/page/ExternalPageBuilder.java | 10 ++++ 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js index b7f8b7ee8834bf..acc17a62ef1406 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js @@ -7,7 +7,7 @@ import 'qui-badge'; * This component adds a custom link on the Extension card */ export class QwcExtensionLink extends QwcHotReloadElement { - + static styles = css` .extensionLink { display: flex; @@ -36,6 +36,7 @@ export class QwcExtensionLink extends QwcHotReloadElement { `; static properties = { + namespace: {type: String}, extensionName: {type: String}, iconName: {type: String}, displayName: {type: String}, @@ -46,13 +47,16 @@ export class QwcExtensionLink extends QwcHotReloadElement { webcomponent: {type: String}, embed: {type: Boolean}, externalUrl: {type: String}, + dynamicUrlMethodName: {type: String}, _effectiveLabel: {state: true}, + _effectiveExternalUrl: {state: true}, _observer: {state: false}, }; _staticLabel = null; _dynamicLabel = null; _streamingLabel = null; + _effectiveExternalUrl = null; set staticLabel(val) { if(!this._staticLabel || (this._staticLabel && this._staticLabel != val)){ @@ -102,6 +106,17 @@ export class QwcExtensionLink extends QwcHotReloadElement { if(this._observer){ this._observer.cancel(); } + + if(this.dynamicUrlMethodName){ + let jrpc = new JsonRpc(this.namespace); + jrpc[this.dynamicUrlMethodName]().then(jsonRpcResponse => { + this._effectiveExternalUrl = jsonRpcResponse.result; + this.requestUpdate(); + }); + }else { + this._effectiveExternalUrl = this.externalUrl; + } + this._effectiveLabel = null; if(this.streamingLabel){ this.jsonRpc = new JsonRpc(this); @@ -153,27 +168,36 @@ export class QwcExtensionLink extends QwcHotReloadElement { render() { if(this.path){ - let routerIgnore = false; - - let p = this.path; - let t = "_self"; - if(!this.embed){ - routerIgnore = true; - p = this.externalUrl; - t = "_blank"; + if(!this.embed) { + return html`${this.renderLink(this._effectiveExternalUrl, true, "_blank")}`; + }else{ + return html`${this.renderLink(this.path, false, "_self")}`; } - return html` - - - - ${this.displayName} - - ${this._renderBadge()} - - `; } } + renderLink(linkRef, routerIgnore, target){ + if(linkRef){ + return html` + + + + ${this.displayName} + + ${this._renderBadge()} + + `; + }else{ + return html` + + + loading ... + + ${this._renderBadge()} + `; + } + } + _renderBadge() { if (this._effectiveLabel) { return html`${this._effectiveLabel}`; diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js index e1710c6b3ce2be..bbe779ac6069d2 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js @@ -136,6 +136,7 @@ export class QwcExtensions extends observeState(LitElement) { path="${page.id}" ?embed=${page.embed} externalUrl="${page.metadata.externalUrl}" + dynamicUrlMethodName="${page.metadata.dynamicUrlMethodName}" webcomponent="${page.componentLink}" > `)}`; diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js index a6765e54b04639..c0f99a61d27546 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-external-page.js @@ -1,5 +1,6 @@ import { LitElement, html, css} from 'lit'; import { RouterController } from 'router-controller'; +import { JsonRpc } from 'jsonrpc'; import '@vaadin/icon'; import 'qui-code-block'; import '@vaadin/progress-bar'; @@ -35,7 +36,20 @@ export class QwcExternalPage extends LitElement { connectedCallback() { super.connectedCallback(); var metadata = this.routerController.getCurrentMetaData(); - if(metadata){ + if(metadata && metadata.dynamicUrlMethodName){ + let ns = this.routerController.getCurrentNamespace(); + this.jsonRpc = new JsonRpc(ns); + this.jsonRpc[metadata.dynamicUrlMethodName]().then(jsonRpcResponse => { + this._externalUrl = jsonRpcResponse.result; + + if(metadata.mimeType){ + this._mimeType = metadata.mimeType; + this._deriveModeFromMimeType(this._mimeType); + }else{ + this._autoDetectMimeType(); + } + }); + } else if (metadata && metadata.externalUrl){ this._externalUrl = metadata.externalUrl; if(metadata.mimeType){ this._mimeType = metadata.mimeType; diff --git a/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/ExternalPageBuilder.java b/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/ExternalPageBuilder.java index 8747fca2f93ce3..5736a46757260d 100644 --- a/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/ExternalPageBuilder.java +++ b/extensions/vertx-http/dev-ui-spi/src/main/java/io/quarkus/devui/spi/page/ExternalPageBuilder.java @@ -5,6 +5,7 @@ public class ExternalPageBuilder extends PageBuilder { private static final String QWC_EXTERNAL_PAGE_JS = "qwc-external-page.js"; private static final String EXTERNAL_URL = "externalUrl"; + private static final String DYNAMIC_URL = "dynamicUrlMethodName"; private static final String MIME_TYPE = "mimeType"; public static final String MIME_TYPE_HTML = "text/html"; @@ -27,6 +28,15 @@ public ExternalPageBuilder url(String url) { return this; } + @SuppressWarnings("unchecked") + public ExternalPageBuilder dynamicUrlJsonRPCMethodName(String methodName) { + if (methodName == null || methodName.isEmpty()) { + throw new RuntimeException("Invalid dynamic URL Method name, can not be empty"); + } + super.metadata.put(DYNAMIC_URL, methodName); + return this; + } + public ExternalPageBuilder isHtmlContent() { return mimeType(MIME_TYPE_HTML); } From 285e0172b30a44175807cc8d0d8ed7391d60d2cd Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 10 May 2023 23:15:47 +1000 Subject: [PATCH 282/333] Fix raw built time data page Signed-off-by: Phillip Kruger --- .../resources/dev-ui/qwc/qwc-data-raw-page.js | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-data-raw-page.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-data-raw-page.js index e1a7768c73a74b..24539f51e31cd5 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-data-raw-page.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-data-raw-page.js @@ -1,14 +1,11 @@ import { LitElement, html, css} from 'lit'; import { RouterController } from 'router-controller'; -import { observeState } from 'lit-element-state'; -import { themeState } from 'theme-state'; -import '@vanillawc/wc-codemirror'; -import '@vanillawc/wc-codemirror/mode/javascript/javascript.js'; +import 'qui-code-block'; /** * This component renders build time data in raw json format */ -export class QwcDataRawPage extends observeState(LitElement) { +export class QwcDataRawPage extends LitElement { routerController = new RouterController(this); static styles = css` @@ -57,15 +54,10 @@ export class QwcDataRawPage extends observeState(LitElement) { var json = JSON.stringify(this._buildTimeData, null, '\t'); return html`

    - - - - + +
    `; } From d640fcad2c1ed3c24366989c04fcbf46ff2d8167 Mon Sep 17 00:00:00 2001 From: Michael Hamburger Date: Sun, 7 May 2023 14:32:49 +0200 Subject: [PATCH 283/333] During cors preflight response should always return all configured http methods which are allowed. If none are configured allow everything. --- .../vertx/http/runtime/cors/CORSFilter.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java index 25a4244fb84252..09e20cd7703ee1 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java @@ -142,23 +142,17 @@ private void processMethods(HttpServerResponse response, String allowMethodsValu if (isConfiguredWithWildcard(corsConfig.methods)) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethodsValue); } else { - String[] allowedMethodsParts = COMMA_SEPARATED_SPLIT_REGEX.split(allowMethodsValue); - List requestedMethods = new ArrayList<>(allowedMethodsParts.length); - for (String requestedMethod : allowedMethodsParts) { - requestedMethods.add(requestedMethod.toLowerCase()); - } StringBuilder allowMethods = new StringBuilder(); boolean isFirst = true; for (HttpMethod configMethod : configuredHttpMethods) { - if (requestedMethods.contains(configMethod.name().toLowerCase())) { - if (isFirst) { - isFirst = false; - } else { - allowMethods.append(','); - } - allowMethods.append(configMethod.name()); + if (isFirst) { + isFirst = false; + } else { + allowMethods.append(','); } + allowMethods.append(configMethod.name()); + } if (allowMethods.length() != 0) { @@ -178,8 +172,12 @@ public void handle(RoutingContext event) { } else { final String requestedMethods = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); + boolean allowsMethod = true; if (requestedMethods != null) { processMethods(response, requestedMethods); + if (!corsConfig.methods.isEmpty()) { + allowsMethod = corsConfig.methods.get().contains(requestedMethods); + } } final String requestedHeaders = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS); @@ -216,10 +214,10 @@ public void handle(RoutingContext event) { String.join(",", exposedHeaders.orElse(Collections.emptyList()))); } - if (!allowsOrigin) { - LOG.debug("Origin is not allowed"); + if (!allowsOrigin || !allowsMethod) { + LOG.debug("Origin or method is not allowed"); response.setStatusCode(403); - response.setStatusMessage("CORS Rejected - Invalid origin"); + response.setStatusMessage("CORS Rejected - Invalid origin or method"); response.end(); } else if (request.method().equals(HttpMethod.OPTIONS) && (requestedHeaders != null || requestedMethods != null)) { LOG.debug("Preflight request has completed"); From e96779cf24831dd9d182f57ac10d276f61f7e0a8 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 8 May 2023 15:19:20 +0100 Subject: [PATCH 284/333] Fix CORS tests --- .../cors/CORSFullConfigHandlerTestCase.java | 12 ++--- .../vertx/web/cors/CORSHandlerTestCase.java | 8 ++-- .../cors/CORSFullConfigHandlerTestCase.java | 12 ++--- .../vertx/http/cors/CORSHandlerTestCase.java | 12 ++--- .../CORSHandlerTestWildcardOriginCase.java | 15 ++++--- .../vertx/http/cors/CORSSecurityTestCase.java | 45 +++++++++++-------- .../vertx/http/runtime/cors/CORSFilter.java | 17 ++++--- 7 files changed, 70 insertions(+), 51 deletions(-) diff --git a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java index d5652e506ae163..d1eef2fe8282dc 100644 --- a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java +++ b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java @@ -27,18 +27,18 @@ public void corsFullConfigTestServlet() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", "http://custom.origin.quarkus") - .header("Access-Control-Allow-Methods", "GET") + .header("Access-Control-Allow-Methods", "GET,PUT,POST") .header("Access-Control-Expose-Headers", "Content-Disposition") .header("Access-Control-Allow-Headers", "X-Custom") .header("Access-Control-Max-Age", "86400"); given().header("Origin", "http://www.quarkus.io") - .header("Access-Control-Request-Method", "PUT,POST") + .header("Access-Control-Request-Method", "PUT") .when() .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", "http://www.quarkus.io") - .header("Access-Control-Allow-Methods", "PUT,POST") + .header("Access-Control-Allow-Methods", "GET,PUT,POST") .header("Access-Control-Expose-Headers", "Content-Disposition"); } @@ -46,14 +46,14 @@ public void corsFullConfigTestServlet() { @DisplayName("Returns only allowed headers and methods") public void corsPartialMethodsTestServlet() { given().header("Origin", "http://custom.origin.quarkus") - .header("Access-Control-Request-Method", "GET,DELETE") + .header("Access-Control-Request-Method", "DELETE") .header("Access-Control-Request-Headers", "X-Custom,X-Custom2") .when() .options("/test").then() - .statusCode(200) + .statusCode(403) .log().headers() .header("Access-Control-Allow-Origin", "http://custom.origin.quarkus") - .header("Access-Control-Allow-Methods", "GET") // Should not return DELETE + .header("Access-Control-Allow-Methods", "GET,PUT,POST") // Should not return DELETE .header("Access-Control-Expose-Headers", "Content-Disposition") .header("Access-Control-Allow-Headers", "X-Custom");// Should not return X-Custom2 } diff --git a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java index 0ae195073d8293..97beecdc94275c 100644 --- a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java +++ b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java @@ -22,7 +22,7 @@ public class CORSHandlerTestCase { @DisplayName("Handles a preflight CORS request correctly") public void corsPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "GET"; String headers = "X-Custom"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -31,7 +31,7 @@ public void corsPreflightTestServlet() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); } @@ -39,7 +39,7 @@ public void corsPreflightTestServlet() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "POST"; String headers = "X-Custom"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -49,7 +49,7 @@ public void corsNoPreflightTestServlet() { .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers) .body(is("test route")); } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java index 026cdf6d0bb2ce..6bc84281bec38c 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java @@ -26,19 +26,19 @@ public void corsFullConfigTestServlet() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", "http://custom.origin.quarkus") - .header("Access-Control-Allow-Methods", "GET") + .header("Access-Control-Allow-Methods", "GET,PUT,POST") .header("Access-Control-Expose-Headers", "Content-Disposition") .header("Access-Control-Allow-Headers", "X-Custom") .header("Access-Control-Allow-Credentials", "false") .header("Access-Control-Max-Age", "86400"); given().header("Origin", "http://www.quarkus.io") - .header("Access-Control-Request-Method", "PUT,POST") + .header("Access-Control-Request-Method", "PUT") .when() .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", "http://www.quarkus.io") - .header("Access-Control-Allow-Methods", "PUT,POST") + .header("Access-Control-Allow-Methods", "GET,PUT,POST") .header("Access-Control-Allow-Credentials", "false") .header("Access-Control-Expose-Headers", "Content-Disposition"); } @@ -47,13 +47,13 @@ public void corsFullConfigTestServlet() { @DisplayName("Returns only allowed headers and methods") public void corsPartialMethodsTestServlet() { given().header("Origin", "http://custom.origin.quarkus") - .header("Access-Control-Request-Method", "GET,DELETE") + .header("Access-Control-Request-Method", "DELETE") .header("Access-Control-Request-Headers", "X-Custom,X-Custom2") .when() .options("/test").then() - .statusCode(200) + .statusCode(403) .header("Access-Control-Allow-Origin", "http://custom.origin.quarkus") - .header("Access-Control-Allow-Methods", "GET") // Should not return DELETE + .header("Access-Control-Allow-Methods", "GET,PUT,POST") // Should not return DELETE .header("Access-Control-Expose-Headers", "Content-Disposition") .header("Access-Control-Allow-Credentials", "false") .header("Access-Control-Allow-Headers", "X-Custom");// Should not return X-Custom2 diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java index 404627d4a32c36..36ce4654edb26f 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java @@ -22,7 +22,7 @@ public class CORSHandlerTestCase { @DisplayName("Handles a preflight CORS request correctly") public void corsPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "POST"; String headers = "X-Custom,content-type"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -31,7 +31,7 @@ public void corsPreflightTestServlet() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Credentials", "true") .header("Access-Control-Allow-Headers", headers); } @@ -39,7 +39,7 @@ public void corsPreflightTestServlet() { @Test public void corsPreflightTestUnmatchedHeader() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "GET"; String headers = "X-Customs,content-types"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -48,7 +48,7 @@ public void corsPreflightTestUnmatchedHeader() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Credentials", "true") .header("Access-Control-Allow-Headers", nullValue()); } @@ -57,7 +57,7 @@ public void corsPreflightTestUnmatchedHeader() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "POST"; String headers = "x-custom,CONTENT-TYPE"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -66,7 +66,7 @@ public void corsNoPreflightTestServlet() { .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers) .header("Access-Control-Allow-Credentials", "true") .body(is("test route")); diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java index c1852e10dd84db..23381e3b15fdff 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java @@ -21,7 +21,7 @@ class CORSHandlerTestWildcardOriginCase { @DisplayName("Returns true 'Access-Control-Allow-Credentials' header on matching origin") void corsMatchingOrigin() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "GET"; String headers = "X-Custom"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -30,14 +30,15 @@ void corsMatchingOrigin() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Credentials", "true"); + .header("Access-Control-Allow-Credentials", "true") + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); } @Test @DisplayName("Returns false 'Access-Control-Allow-Credentials' header on matching origin") void corsNotMatchingOrigin() { String origin = "http://non.matching.origin.quarkus"; - String methods = "GET,POST"; + String methods = "POST"; String headers = "X-Custom"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -46,7 +47,8 @@ void corsNotMatchingOrigin() { .options("/test").then() .statusCode(403) .header("Access-Control-Allow-Origin", nullValue()) - .header("Access-Control-Allow-Credentials", "false"); + .header("Access-Control-Allow-Credentials", "false") + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); } @Test @@ -107,7 +109,7 @@ void corsInvalidSameOriginRequest5() { @DisplayName("Returns false 'Access-Control-Allow-Credentials' header on matching origin '*'") void corsMatchingOriginWithWildcard() { String origin = "*"; - String methods = "GET,POST"; + String methods = "GET"; String headers = "X-Custom"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -116,6 +118,7 @@ void corsMatchingOriginWithWildcard() { .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Credentials", "false"); + .header("Access-Control-Allow-Credentials", "false") + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java index 8117bf00dbd573..840cd4248d9c5a 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java @@ -48,49 +48,59 @@ public static void setup() { @DisplayName("Handles a preflight CORS request correctly") public void corsPreflightTest() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; String headers = "X-Custom"; given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "GET") .header("Access-Control-Request-Headers", headers) .when() .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "POST") .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "test") .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "GET") .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "wrongpassword") .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "POST") .header("Access-Control-Request-Headers", headers) .when() .auth().basic("user", "user") .options("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") + .header("Access-Control-Allow-Headers", headers); + + given().header("Origin", origin) + .header("Access-Control-Request-Method", "PUT") + .header("Access-Control-Request-Headers", headers) + .when() + .auth().basic("user", "user") + .options("/test").then() + .statusCode(403) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); } @@ -98,50 +108,49 @@ public void corsPreflightTest() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTest() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; String headers = "X-Custom"; given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "GET") .header("Access-Control-Request-Headers", headers) .when() .get("/test").then() .statusCode(401) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "POST") .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "test") .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers) .body(Matchers.equalTo("test:/test")); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "GET") .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "wrongpassword") .get("/test").then() .statusCode(401) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Method", "POST") .header("Access-Control-Request-Headers", headers) .when() .auth().basic("user", "user") .get("/test").then() .statusCode(403) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java index 09e20cd7703ee1..fe07578a89e53f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java @@ -29,12 +29,14 @@ public class CORSFilter implements Handler { final CORSConfig corsConfig; private final boolean wildcardOrigin; + private final boolean wildcardMethod; private final List allowedOriginsRegex; private final List configuredHttpMethods; public CORSFilter(CORSConfig corsConfig) { this.corsConfig = corsConfig; this.wildcardOrigin = isOriginConfiguredWithWildcard(this.corsConfig.origins); + this.wildcardMethod = isConfiguredWithWildcard(corsConfig.methods); this.allowedOriginsRegex = this.wildcardOrigin ? List.of() : parseAllowedOriginsRegex(this.corsConfig.origins); this.configuredHttpMethods = createConfiguredHttpMethods(this.corsConfig.methods); } @@ -139,7 +141,7 @@ private void processRequestedHeaders(HttpServerResponse response, String allowHe } private void processMethods(HttpServerResponse response, String allowMethodsValue) { - if (isConfiguredWithWildcard(corsConfig.methods)) { + if (wildcardMethod) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethodsValue); } else { @@ -175,7 +177,7 @@ public void handle(RoutingContext event) { boolean allowsMethod = true; if (requestedMethods != null) { processMethods(response, requestedMethods); - if (!corsConfig.methods.isEmpty()) { + if (!wildcardMethod && !corsConfig.methods.isEmpty()) { allowsMethod = corsConfig.methods.get().contains(requestedMethods); } } @@ -214,10 +216,15 @@ public void handle(RoutingContext event) { String.join(",", exposedHeaders.orElse(Collections.emptyList()))); } - if (!allowsOrigin || !allowsMethod) { - LOG.debug("Origin or method is not allowed"); + if (!allowsOrigin) { + LOG.debug("Origin is not allowed"); + response.setStatusCode(403); + response.setStatusMessage("CORS Rejected - Invalid origin"); + response.end(); + } else if (!allowsMethod) { + LOG.debug("Method is not allowed"); response.setStatusCode(403); - response.setStatusMessage("CORS Rejected - Invalid origin or method"); + response.setStatusMessage("CORS Rejected - Invalid method"); response.end(); } else if (request.method().equals(HttpMethod.OPTIONS) && (requestedHeaders != null || requestedMethods != null)) { LOG.debug("Preflight request has completed"); From bd4ae0b20f5d3b0a8d2707a41c9ffe94e027c79f Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 9 May 2023 11:52:46 +1000 Subject: [PATCH 285/333] Refactor CORS support This refactoring make it clear the different betweeen preflight and normal requests. It also adds access control to normal requests such that they will be blocked if an invalid header or method is present. --- .../cors/CORSFullConfigHandlerTestCase.java | 2 +- .../vertx/web/cors/CORSHandlerTestCase.java | 5 - .../resources/conf/cors-config.properties | 2 +- .../resteasy/test/RestEasyCORSTestCase.java | 6 - .../cors/CORSFullConfigHandlerTestCase.java | 4 +- .../vertx/http/cors/CORSHandlerTestCase.java | 7 +- .../CORSHandlerTestWildcardOriginCase.java | 20 +- .../vertx/http/cors/CORSSecurityTestCase.java | 23 +- .../cors/CORSWildcardSecurityTestCase.java | 24 +- .../CORSWildcardStarSecurityTestCase.java | 25 +- .../conf/cors-config-full.properties | 2 +- .../resources/conf/cors-config.properties | 2 +- .../vertx/http/runtime/cors/CORSConfig.java | 2 +- .../vertx/http/runtime/cors/CORSFilter.java | 230 ++++++++++-------- .../io/quarkus/it/vertx/CorsTestCase.java | 2 +- 15 files changed, 148 insertions(+), 208 deletions(-) diff --git a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java index d1eef2fe8282dc..445fe9adc05b63 100644 --- a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java +++ b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSFullConfigHandlerTestCase.java @@ -50,7 +50,7 @@ public void corsPartialMethodsTestServlet() { .header("Access-Control-Request-Headers", "X-Custom,X-Custom2") .when() .options("/test").then() - .statusCode(403) + .statusCode(200) .log().headers() .header("Access-Control-Allow-Origin", "http://custom.origin.quarkus") .header("Access-Control-Allow-Methods", "GET,PUT,POST") // Should not return DELETE diff --git a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java index 97beecdc94275c..ad09acf0586f07 100644 --- a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java +++ b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/cors/CORSHandlerTestCase.java @@ -39,18 +39,13 @@ public void corsPreflightTestServlet() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; - String methods = "POST"; - String headers = "X-Custom"; given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .log().headers() .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") - .header("Access-Control-Allow-Headers", headers) .body(is("test route")); } diff --git a/extensions/reactive-routes/deployment/src/test/resources/conf/cors-config.properties b/extensions/reactive-routes/deployment/src/test/resources/conf/cors-config.properties index 10c86a915bd04c..d70386687b2666 100644 --- a/extensions/reactive-routes/deployment/src/test/resources/conf/cors-config.properties +++ b/extensions/reactive-routes/deployment/src/test/resources/conf/cors-config.properties @@ -1,4 +1,4 @@ quarkus.http.cors=true quarkus.http.cors.origins=* # whitespaces added to test that they are not taken into account config is parsed -quarkus.http.cors.methods=GET, OPTIONS, POST +quarkus.http.cors.methods=GET,OPTIONS,POST diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RestEasyCORSTestCase.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RestEasyCORSTestCase.java index 335629911df6fa..02422fc929b3dc 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RestEasyCORSTestCase.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RestEasyCORSTestCase.java @@ -33,16 +33,10 @@ public void testCORSPreflightRootResource() { @Test public void testCORSRootResource() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; - String headers = "X-Custom"; RestAssured.given() .header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when().get("/").then() .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers) .body(Matchers.is("Root Resource")); } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java index 6bc84281bec38c..24b606f475574e 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSFullConfigHandlerTestCase.java @@ -48,10 +48,10 @@ public void corsFullConfigTestServlet() { public void corsPartialMethodsTestServlet() { given().header("Origin", "http://custom.origin.quarkus") .header("Access-Control-Request-Method", "DELETE") - .header("Access-Control-Request-Headers", "X-Custom,X-Custom2") + .header("Access-Control-Request-Headers", "X-Custom, X-Custom2") .when() .options("/test").then() - .statusCode(403) + .statusCode(200) .header("Access-Control-Allow-Origin", "http://custom.origin.quarkus") .header("Access-Control-Allow-Methods", "GET,PUT,POST") // Should not return DELETE .header("Access-Control-Expose-Headers", "Content-Disposition") diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java index 36ce4654edb26f..f6080ccae04b07 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java @@ -1,7 +1,6 @@ package io.quarkus.vertx.http.cors; import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; import org.junit.jupiter.api.DisplayName; @@ -33,7 +32,7 @@ public void corsPreflightTestServlet() { .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Credentials", "true") - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Headers", "x-custom,CONTENT-TYPE"); } @Test @@ -50,7 +49,7 @@ public void corsPreflightTestUnmatchedHeader() { .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Credentials", "true") - .header("Access-Control-Allow-Headers", nullValue()); + .header("Access-Control-Allow-Headers", "x-custom,CONTENT-TYPE"); } @Test @@ -60,8 +59,6 @@ public void corsNoPreflightTestServlet() { String methods = "POST"; String headers = "x-custom,CONTENT-TYPE"; given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .get("/test").then() .statusCode(200) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java index 23381e3b15fdff..041490f5714337 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestWildcardOriginCase.java @@ -46,9 +46,7 @@ void corsNotMatchingOrigin() { .when() .options("/test").then() .statusCode(403) - .header("Access-Control-Allow-Origin", nullValue()) - .header("Access-Control-Allow-Credentials", "false") - .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); + .header("Access-Control-Allow-Origin", nullValue()); } @Test @@ -105,20 +103,4 @@ void corsInvalidSameOriginRequest5() { .header("Access-Control-Allow-Origin", nullValue()); } - @Test - @DisplayName("Returns false 'Access-Control-Allow-Credentials' header on matching origin '*'") - void corsMatchingOriginWithWildcard() { - String origin = "*"; - String methods = "GET"; - String headers = "X-Custom"; - given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) - .when() - .options("/test").then() - .statusCode(200) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Credentials", "false") - .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); - } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java index 840cd4248d9c5a..df90856460716e 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSSecurityTestCase.java @@ -23,7 +23,7 @@ public class CORSSecurityTestCase { private static final String APP_PROPS = "" + "quarkus.http.cors=true\n" + "quarkus.http.cors.origins=*\n" + - "quarkus.http.cors.methods=GET, OPTIONS, POST\n" + + "quarkus.http.cors.methods=GET,OPTIONS,POST\n" + "quarkus.http.auth.basic=true\n" + "quarkus.http.auth.policy.r1.roles-allowed=test\n" + "quarkus.http.auth.permission.roles1.paths=/test\n" + @@ -98,7 +98,7 @@ public void corsPreflightTest() { .when() .auth().basic("user", "user") .options("/test").then() - .statusCode(403) + .statusCode(200) .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") .header("Access-Control-Allow-Headers", headers); @@ -108,49 +108,36 @@ public void corsPreflightTest() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTest() { String origin = "http://custom.origin.quarkus"; - String headers = "X-Custom"; given().header("Origin", origin) - .header("Access-Control-Request-Method", "GET") - .header("Access-Control-Request-Headers", headers) .when() .get("/test").then() .statusCode(401) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); given().header("Origin", origin) - .header("Access-Control-Request-Method", "POST") - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "test") .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") - .header("Access-Control-Allow-Headers", headers) .body(Matchers.equalTo("test:/test")); given().header("Origin", origin) - .header("Access-Control-Request-Method", "GET") - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "wrongpassword") .get("/test").then() .statusCode(401) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); given().header("Origin", origin) - .header("Access-Control-Request-Method", "POST") - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("user", "user") .get("/test").then() .statusCode(403) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST") - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Methods", "GET,OPTIONS,POST"); } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardSecurityTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardSecurityTestCase.java index 0cbda431190ef3..e364866570885a 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardSecurityTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardSecurityTestCase.java @@ -97,50 +97,34 @@ public void corsPreflightTest() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTest() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST"; + String methods = "GET, POST"; String headers = "X-Custom"; given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .get("/test").then() .statusCode(401) - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Origin", origin); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "test") .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers) .body(Matchers.equalTo("test:/test")); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "wrongpassword") .get("/test").then() .statusCode(401) - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Origin", origin); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("user", "user") .get("/test").then() .statusCode(403) - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Origin", origin); } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardStarSecurityTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardStarSecurityTestCase.java index 0464168bca23dc..e4e56b466d8d58 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardStarSecurityTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSWildcardStarSecurityTestCase.java @@ -49,7 +49,7 @@ public static void setup() { @DisplayName("Handles a preflight CORS request correctly") public void corsPreflightTest() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST,OPTIONS,DELETE"; + String methods = "GET, POST, OPTIONS, DELETE"; String headers = "X-Custom,B-Custom,Test-Headers"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) @@ -99,51 +99,34 @@ public void corsPreflightTest() { @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTest() { String origin = "http://custom.origin.quarkus"; - String methods = "GET,POST,OPTIONS,DELETE"; String headers = "X-Custom,B-Custom,Test-Headers"; given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .get("/test").then() .statusCode(401) - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Origin", origin); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "test") .get("/test").then() .statusCode(200) .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers) .body(Matchers.equalTo("test:/test")); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("test", "wrongpassword") .get("/test").then() .statusCode(401) - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Origin", origin); given().header("Origin", origin) - .header("Access-Control-Request-Method", methods) - .header("Access-Control-Request-Headers", headers) .when() .auth().basic("user", "user") .get("/test").then() .statusCode(403) - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Methods", methods) - .header("Access-Control-Allow-Headers", headers); + .header("Access-Control-Allow-Origin", origin); } } diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config-full.properties b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config-full.properties index f335c05c9090d0..32b4c5c268739a 100644 --- a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config-full.properties +++ b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config-full.properties @@ -1,6 +1,6 @@ quarkus.http.cors=true quarkus.http.cors.origins=http://custom.origin.quarkus, http://www.quarkus.io -quarkus.http.cors.methods=GET, PUT, POST +quarkus.http.cors.methods=GET,PUT,POST quarkus.http.cors.headers=X-Custom quarkus.http.cors.exposed-headers=Content-Disposition quarkus.http.cors.access-control-max-age=24H diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties index 6e587318d781dc..f2de5431e4a27d 100644 --- a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties +++ b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties @@ -1,6 +1,6 @@ quarkus.http.cors=true quarkus.http.cors.origins=* # whitespaces added to test that they are not taken into account config is parsed -quarkus.http.cors.methods=GET, OPTIONS, POST +quarkus.http.cors.methods=GET,OPTIONS,POST quarkus.http.cors.access-control-allow-credentials=true quarkus.http.cors.headers=x-custom,CONTENT-TYPE diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java index 4b2a57fd22573a..253ad10984b943 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java @@ -71,7 +71,7 @@ public class CORSConfig { * the request’s credentials mode Request.credentials is “include”. * * The value of this header will default to `true` if `quarkus.http.cors.origins` property is set and - * there is a match with the precise `Origin` header and that header is not '*'. + * there is a match with the precise `Origin` header. */ @ConfigItem public Optional accessControlAllowCredentials = Optional.empty(); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java index fe07578a89e53f..4cd477c951b8d5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java @@ -2,12 +2,11 @@ import java.net.URI; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.regex.Pattern; import org.jboss.logging.Logger; @@ -31,7 +30,13 @@ public class CORSFilter implements Handler { private final boolean wildcardOrigin; private final boolean wildcardMethod; private final List allowedOriginsRegex; - private final List configuredHttpMethods; + private final Set configuredHttpMethods; + + private final String exposedHeaders; + + private final String allowedHeaders; + + private final String allowedMethods; public CORSFilter(CORSConfig corsConfig) { this.corsConfig = corsConfig; @@ -39,14 +44,30 @@ public CORSFilter(CORSConfig corsConfig) { this.wildcardMethod = isConfiguredWithWildcard(corsConfig.methods); this.allowedOriginsRegex = this.wildcardOrigin ? List.of() : parseAllowedOriginsRegex(this.corsConfig.origins); this.configuredHttpMethods = createConfiguredHttpMethods(this.corsConfig.methods); + this.exposedHeaders = createHeaderString(this.corsConfig.exposedHeaders); + this.allowedHeaders = createHeaderString(this.corsConfig.headers); + this.allowedMethods = createHeaderString(this.corsConfig.methods); + } + + private String createHeaderString(Optional> headers) { + if (headers.isEmpty()) { + return null; + } + if (headers.get().isEmpty()) { + return null; + } + if (headers.get().size() == 1 && headers.get().get(0).equals("*")) { + return null; + } + return String.join(",", headers.get()); } - private List createConfiguredHttpMethods(Optional> methods) { + private Set createConfiguredHttpMethods(Optional> methods) { if (methods.isEmpty()) { - return List.of(); + return Set.of(); } List corsConfigMethods = methods.get(); - List result = new ArrayList<>(corsConfigMethods.size()); + LinkedHashSet result = new LinkedHashSet<>(corsConfigMethods.size()); for (String value : corsConfigMethods) { result.add(HttpMethod.valueOf(value)); } @@ -108,61 +129,6 @@ public static boolean isOriginAllowedByRegex(List allowOriginsRegex, St return false; } - private void processRequestedHeaders(HttpServerResponse response, String allowHeadersValue) { - if (isConfiguredWithWildcard(corsConfig.headers)) { - response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeadersValue); - } else { - Map requestedHeaders; - String[] allowedParts = COMMA_SEPARATED_SPLIT_REGEX.split(allowHeadersValue); - requestedHeaders = new HashMap<>(); - for (String requestedHeader : allowedParts) { - requestedHeaders.put(requestedHeader.toLowerCase(), requestedHeader); - } - - List corsConfigHeaders = corsConfig.headers.get(); - StringBuilder allowedHeaders = new StringBuilder(); - boolean isFirst = true; - for (String configHeader : corsConfigHeaders) { - String configHeaderLowerCase = configHeader.toLowerCase(); - if (requestedHeaders.containsKey(configHeaderLowerCase)) { - if (isFirst) { - isFirst = false; - } else { - allowedHeaders.append(','); - } - allowedHeaders.append(requestedHeaders.get(configHeaderLowerCase)); - } - } - - if (allowedHeaders.length() != 0) { - response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowedHeaders.toString()); - } - } - } - - private void processMethods(HttpServerResponse response, String allowMethodsValue) { - if (wildcardMethod) { - response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethodsValue); - } else { - - StringBuilder allowMethods = new StringBuilder(); - boolean isFirst = true; - for (HttpMethod configMethod : configuredHttpMethods) { - if (isFirst) { - isFirst = false; - } else { - allowMethods.append(','); - } - allowMethods.append(configMethod.name()); - - } - - if (allowMethods.length() != 0) { - response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethods.toString()); - } - } - } - @Override public void handle(RoutingContext event) { Objects.requireNonNull(corsConfig, "CORS config is not set"); @@ -172,25 +138,11 @@ public void handle(RoutingContext event) { if (origin == null) { event.next(); } else { - final String requestedMethods = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); - - boolean allowsMethod = true; - if (requestedMethods != null) { - processMethods(response, requestedMethods); - if (!wildcardMethod && !corsConfig.methods.isEmpty()) { - allowsMethod = corsConfig.methods.get().contains(requestedMethods); - } - } - - final String requestedHeaders = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS); - - if (requestedHeaders != null) { - processRequestedHeaders(response, requestedHeaders); - } + //for both normal and preflight requests we need to check the origin boolean allowsOrigin = wildcardOrigin; if (!allowsOrigin) { - if (!corsConfig.origins.isEmpty()) { + if (corsConfig.origins.isPresent()) { allowsOrigin = corsConfig.origins.get().contains(origin) || isOriginAllowedByRegex(allowedOriginsRegex, origin) || isSameOrigin(request, origin); @@ -198,47 +150,78 @@ public void handle(RoutingContext event) { allowsOrigin = isSameOrigin(request, origin); } } - - if (allowsOrigin) { + if (!allowsOrigin) { + response.setStatusCode(403); + response.setStatusMessage("CORS Rejected - Invalid origin"); + } else { + boolean allowCredentials = corsConfig.accessControlAllowCredentials + .orElse(corsConfig.origins.isPresent() && corsConfig.origins.get().contains(origin)); + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, String.valueOf(allowCredentials)); response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); } - boolean allowCredentials = corsConfig.accessControlAllowCredentials - .orElseGet(() -> corsConfig.origins.isPresent() && corsConfig.origins.get().contains(origin) - && !origin.equals("*")); - - response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, String.valueOf(allowCredentials)); - - final Optional> exposedHeaders = corsConfig.exposedHeaders; - - if (!isConfiguredWithWildcard(exposedHeaders)) { - response.headers().set(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, - String.join(",", exposedHeaders.orElse(Collections.emptyList()))); + if (request.method().equals(HttpMethod.OPTIONS)) { + final String requestedMethods = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); + final String requestedHeaders = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS); + //preflight request, handle it specially + if (requestedHeaders != null || requestedMethods != null) { + handlePreflightRequest(event, requestedHeaders, requestedMethods, origin, allowsOrigin); + response.end(); + return; + } } - - if (!allowsOrigin) { - LOG.debug("Origin is not allowed"); - response.setStatusCode(403); - response.setStatusMessage("CORS Rejected - Invalid origin"); - response.end(); - } else if (!allowsMethod) { + if (allowedHeaders != null) { + response.headers().add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowedHeaders); + } + if (allowedMethods != null) { + response.headers().add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowedMethods); + } + //we check that the actual request matches the allowed methods and headers + if (!isMethodAllowed(request.method())) { LOG.debug("Method is not allowed"); response.setStatusCode(403); response.setStatusMessage("CORS Rejected - Invalid method"); response.end(); - } else if (request.method().equals(HttpMethod.OPTIONS) && (requestedHeaders != null || requestedMethods != null)) { - LOG.debug("Preflight request has completed"); - if (corsConfig.accessControlMaxAge.isPresent()) { - response.putHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, - String.valueOf(corsConfig.accessControlMaxAge.get().getSeconds())); - } + return; + } + if (!allowsOrigin) { response.end(); - } else { - event.next(); + return; } + + //all good, it can proceed + event.next(); } } + private void handlePreflightRequest(RoutingContext event, String requestedHeaders, String requestedMethods, String origin, + boolean allowsOrigin) { + //see https://fetch.spec.whatwg.org/#http-cors-protocol + + if (corsConfig.accessControlMaxAge.isPresent()) { + event.response().putHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, + String.valueOf(corsConfig.accessControlMaxAge.get().getSeconds())); + } + var response = event.response(); + if (requestedMethods != null) { + processPreFlightMethods(response, requestedMethods); + } + + if (requestedHeaders != null) { + processPreFlightRequestedHeaders(response, requestedHeaders); + } + + //always set expose headers if present + if (exposedHeaders != null) { + response.headers().add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposedHeaders); + } + + if (!isConfiguredWithWildcard(corsConfig.exposedHeaders)) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, this.exposedHeaders); + } + + } + static boolean isSameOrigin(HttpServerRequest request, String origin) { //fast path check, when everything is the same if (origin.startsWith(request.scheme())) { @@ -323,4 +306,39 @@ static boolean substringMatch(String str, int pos, String substring, boolean req subPos++; } } + + private void processPreFlightRequestedHeaders(HttpServerResponse response, String allowHeadersValue) { + if (isConfiguredWithWildcard(corsConfig.headers)) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeadersValue); + } else { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowedHeaders); + } + } + + private void processPreFlightMethods(HttpServerResponse response, String allowMethodsValue) { + if (wildcardMethod) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethodsValue); + } else { + StringBuilder allowMethods = new StringBuilder(); + boolean isFirst = true; + for (HttpMethod configMethod : configuredHttpMethods) { + if (isFirst) { + isFirst = false; + } else { + allowMethods.append(","); + } + allowMethods.append(configMethod.name()); + + } + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethods.toString()); + } + } + + private boolean isMethodAllowed(HttpMethod method) { + if (wildcardMethod) { + return true; + } else { + return configuredHttpMethods.contains(method); + } + } } diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/CorsTestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/CorsTestCase.java index d6280d5104f77d..dbb0bbc1e13cea 100644 --- a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/CorsTestCase.java +++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/CorsTestCase.java @@ -17,7 +17,7 @@ void basicPreflightTest() { .options("/simple/options") .then() .statusCode(200) - .header("Access-Control-Allow-Methods", is("GET")) + .header("Access-Control-Allow-Methods", is("POST,GET,PUT,OPTIONS,DELETE")) .header("access-control-allow-origin", is("https://example.org/")) .header("access-control-allow-credentials", is("false")) .header("content-length", is("0")) From aaa8b8c0b3195e7e0884f0aa18e343b63aca2a63 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 11 May 2023 09:05:09 +0200 Subject: [PATCH 286/333] Support Providers in REST Client Reactive from context Fix https://github.com/quarkusio/quarkus/issues/26003 --- .../main/asciidoc/rest-client-reactive.adoc | 73 +++++++++++++ .../reactive/ProvidersFromContextTest.java | 102 ++++++++++++++++++ .../client/impl/ClientRequestContextImpl.java | 8 ++ .../reactive/client/impl/ProvidersImpl.java | 60 +++++++++++ .../ResteasyReactiveClientRequestContext.java | 6 ++ .../ResteasyReactiveClientRequestFilter.java | 14 +++ .../ResteasyReactiveClientResponseFilter.java | 17 +++ .../common/jaxrs/ConfigurationImpl.java | 14 +++ 8 files changed, 294 insertions(+) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ProvidersFromContextTest.java create mode 100644 independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ProvidersImpl.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index 83a97652fc6864..9dbf627dd273e0 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -953,6 +953,79 @@ public interface ExtensionsService { org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,Proxy-Authorization ---- +== Customizing the request + +The REST Client Reactive supports further customization of the final request to be sent to the server via filters. The filters must implement either the interface `ClientRequestFilter` or `ResteasyReactiveClientRequestFilter`. + +A simple example of customizing the request would be to add a custom header: + +[source, java] +---- +@Provider +public class TestClientRequestFilter implements ClientRequestFilter { + + @Override + public void filter(ClientRequestContext requestContext) { + requestContext.getHeaders().add("my_header", "value"); + } +} +---- + +Next, you can register your filter using the `@RegisterProvider` annotation: + +[source, java] +---- +@Path("/extensions") +@RegisterProvider(TestClientRequestFilter.class) +public interface ExtensionsService { + + // ... +} +---- + +Or programmatically using the `.register()` method: + +[source, java] +---- +QuarkusRestClientBuilder.newBuilder() + .register(TestClientRequestFilter.class) + .build(ExtensionsService.class) +---- + +=== Injecting the `jakarta.ws.rs.ext.Providers` instance in filters + +The `jakarta.ws.rs.ext.Providers` is useful when we need to lookup the provider instances of the current client. + +We can get the `Providers` instance in our filters from the request context as follows: + +[source, java] +---- +@Provider +public class TestClientRequestFilter implements ClientRequestFilter { + + @Override + public void filter(ClientRequestContext requestContext) { + Providers providers = ((ResteasyReactiveClientRequestContext) requestContext).getProviders(); + // ... + } +} +---- + +Alternatively, you can implement the `ResteasyReactiveClientRequestFilter` interface instead of the `ClientRequestFilter` interface that will directly provide the `ResteasyReactiveClientRequestContext` context: + +[source, java] +---- +@Provider +public class TestClientRequestFilter implements ResteasyReactiveClientRequestFilter { + + @Override + public void filter(ResteasyReactiveClientRequestFilter requestContext) { + Providers providers = requestContext.getProviders(); + // ... + } +} +---- + == Exception handling The MicroProfile REST Client specification introduces the `org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper` whose purpose is to convert an HTTP response to an exception. diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ProvidersFromContextTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ProvidersFromContextTest.java new file mode 100644 index 00000000000000..eac99ad1181e88 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ProvidersFromContextTest.java @@ -0,0 +1,102 @@ +package io.quarkus.rest.client.reactive; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.URI; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ContextResolver; +import jakarta.ws.rs.ext.Provider; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class ProvidersFromContextTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + + @TestHTTPResource + URI baseUri; + + private Client client; + + @BeforeEach + public void before() { + client = QuarkusRestClientBuilder.newBuilder() + .baseUri(baseUri) + .register(TestClientRequestFilter.class) + .register(MyContextResolver.class) + .build(Client.class); + } + + @Test + public void test() { + Response response = client.get(); + assertEquals(200, response.getStatus()); + } + + @RegisterRestClient + public interface Client { + + @GET + @Path("test") + Response get(); + } + + @Path("test") + public static class Endpoint { + + @GET + public Response get() { + return Response.ok().build(); + } + } + + public static class Person { + public String name; + } + + public static class MyContextResolver implements ContextResolver { + + @Override + public Person getContext(Class aClass) { + return new Person(); + } + } + + @Provider + public static class TestClientRequestFilter implements ResteasyReactiveClientRequestFilter { + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext) { + if (requestContext.getProviders() == null) { + throw new RuntimeException("Providers was not injected"); + } + + var readers = requestContext.getProviders().getMessageBodyReader(String.class, null, null, null); + if (readers == null) { + throw new RuntimeException("No readers were found"); + } + + var writers = requestContext.getProviders().getMessageBodyWriter(String.class, null, null, null); + if (writers == null) { + throw new RuntimeException("No writers were found"); + } + + ContextResolver contextResolver = requestContext.getProviders().getContextResolver(Person.class, null); + if (contextResolver == null) { + throw new RuntimeException("Context resolver was not found"); + } + } + } +} diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java index fcd63ed74d9572..eb653ee5875445 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java @@ -32,6 +32,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Providers; import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; import org.jboss.resteasy.reactive.common.NotImplementedYet; @@ -52,6 +53,7 @@ public class ClientRequestContextImpl implements ResteasyReactiveClientRequestCo private final RestClientRequestContext restClientRequestContext; private final ClientRequestHeadersMap headersMap; private final Context context; + private final Providers providers; public ClientRequestContextImpl(RestClientRequestContext restClientRequestContext, ClientImpl client, ConfigurationImpl configuration) { @@ -59,6 +61,7 @@ public ClientRequestContextImpl(RestClientRequestContext restClientRequestContex this.client = client; this.configuration = configuration; this.headersMap = new ClientRequestHeadersMap(); //restClientRequestContext.requestHeaders.getHeaders() + this.providers = new ProvidersImpl(restClientRequestContext); // TODO This needs to be challenged: // Always create a duplicated context because each REST Client invocation must have its own context @@ -73,6 +76,11 @@ public ClientRequestContextImpl(RestClientRequestContext restClientRequestContex restClientRequestContext.properties.put(VERTX_CONTEXT_PROPERTY, context); } + @Override + public Providers getProviders() { + return providers; + } + @Override public Context getContext() { return context; diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ProvidersImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ProvidersImpl.java new file mode 100644 index 00000000000000..e8169dbaaae72d --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ProvidersImpl.java @@ -0,0 +1,60 @@ +package org.jboss.resteasy.reactive.client.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; + +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.ext.ContextResolver; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Providers; + +public class ProvidersImpl implements Providers { + + private final RestClientRequestContext context; + + public ProvidersImpl(RestClientRequestContext context) { + this.context = context; + } + + @Override + public MessageBodyReader getMessageBodyReader(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + List> readers = context.getRestClient().getClientContext().getSerialisers() + .findReaders(context.getConfiguration(), type, mediaType, RuntimeType.CLIENT); + for (MessageBodyReader reader : readers) { + if (reader.isReadable(type, genericType, annotations, mediaType)) { + return (MessageBodyReader) reader; + } + } + return null; + } + + @Override + public MessageBodyWriter getMessageBodyWriter(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + List> writers = context.getRestClient().getClientContext().getSerialisers() + .findWriters(context.getConfiguration(), type, mediaType, RuntimeType.CLIENT); + for (MessageBodyWriter writer : writers) { + if (writer.isWriteable(type, genericType, annotations, mediaType)) { + return (MessageBodyWriter) writer; + } + } + return null; + } + + @Override + public ExceptionMapper getExceptionMapper(Class type) { + throw new UnsupportedOperationException( + "`jakarta.ws.rs.ext.ExceptionMapper` are not supported in REST Client Reactive"); + } + + @Override + public ContextResolver getContextResolver(Class contextType, MediaType mediaType) { + // TODO: support getting context resolver by mediaType (which is provided using the `@Produces` annotation). + return context.getConfiguration().getContextResolver(contextType); + } +} diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestContext.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestContext.java index b1cbc2e7809e5c..43f8327a9ed64a 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestContext.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestContext.java @@ -2,6 +2,7 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.GenericType; +import jakarta.ws.rs.ext.Providers; import io.smallrye.stork.api.ServiceInstance; import io.vertx.core.Context; @@ -23,6 +24,11 @@ public interface ResteasyReactiveClientRequestContext extends ClientRequestConte void resume(Throwable t); + /** + * @return the context where to lookup all the provider instances of the current client. + */ + Providers getProviders(); + /** * @return the captured or created duplicated context. See {@link #VERTX_CONTEXT_PROPERTY} for details. */ diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestFilter.java index d4ca467842f8b1..dc974a8f182e03 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestFilter.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientRequestFilter.java @@ -5,12 +5,26 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; +/** + * An extension interface implemented by client request filters used by REST Client Reactive. + */ public interface ResteasyReactiveClientRequestFilter extends ClientRequestFilter { + /** + * Filter method called before a request has been dispatched to a client transport layer. + * + * @param requestContext the request context. + * @throws IOException if an I/O exception occurs. + */ @Override default void filter(ClientRequestContext requestContext) throws IOException { filter((ResteasyReactiveClientRequestContext) requestContext); } + /** + * Filter method called before a request has been dispatched to a client transport layer. + * + * @param requestContext the REST Client reactive request context. + */ void filter(ResteasyReactiveClientRequestContext requestContext); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientResponseFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientResponseFilter.java index 112620fd9b588d..139a3461079c9f 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientResponseFilter.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/spi/ResteasyReactiveClientResponseFilter.java @@ -4,11 +4,28 @@ import jakarta.ws.rs.client.ClientResponseContext; import jakarta.ws.rs.client.ClientResponseFilter; +/** + * An extension interface implemented by client response filters used by REST Client Reactive. + */ public interface ResteasyReactiveClientResponseFilter extends ClientResponseFilter { + /** + * Filter method called after a response has been provided for a request (either by a request filter or when the HTTP + * invocation returns). + * + * @param requestContext the request context. + * @param responseContext the response context. + */ default void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) { filter((ResteasyReactiveClientRequestContext) requestContext, responseContext); } + /** + * Filter method called after a response has been provided for a request (either by a request filter or when the HTTP + * invocation returns). + * + * @param requestContext the REST Client reactive request context. + * @param responseContext the response context. + */ void filter(ResteasyReactiveClientRequestContext requestContext, ClientResponseContext responseContext); } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java index 588237450831fb..72a269f62e4194 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java @@ -528,6 +528,20 @@ public RxInvokerProvider getRxInvokerProvider(Class wantedClass) { return null; } + public ContextResolver getContextResolver(Class wantedClass) { + MultivaluedMap> candidates = contextResolvers.get(wantedClass); + if (candidates == null) { + return null; + } + for (List> contextResolvers : candidates.values()) { + if (!contextResolvers.isEmpty()) { + return (ContextResolver) contextResolvers.get(0); + } + } + + return null; + } + public T getFromContext(Class wantedClass) { MultivaluedMap> candidates = contextResolvers.get(wantedClass); if (candidates == null) { From 44ed120e1d2e6036858d81c2f5e920d5172c42b8 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 11 May 2023 10:53:28 +0300 Subject: [PATCH 287/333] No need to exclude javax validation-api anymore This was first introduced with the Big ClassLoader Change in 1.3 but the move to Jakarta this is no longer needed Relates to: https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Does.20Quarkus.203.20remove.20javax.20Dependecy.20like.20javax.2Evalidation.3F --- extensions/hibernate-validator/runtime/pom.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/hibernate-validator/runtime/pom.xml b/extensions/hibernate-validator/runtime/pom.xml index eea1011d4a0518..cd2ec7a75a5eac 100644 --- a/extensions/hibernate-validator/runtime/pom.xml +++ b/extensions/hibernate-validator/runtime/pom.xml @@ -107,9 +107,6 @@ io.quarkus.hibernate.validator - - javax.validation:validation-api - From 9a0744e0ab04f65f0e90f40253a7bef5c8038b51 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 10 May 2023 19:43:40 +0100 Subject: [PATCH 288/333] Make it easier to get well known properties from TokenIntrospection --- .../oidc/common/runtime/OidcConstants.java | 3 + .../io/quarkus/oidc/TokenIntrospection.java | 42 +++++++ .../oidc/runtime/OidcIdentityProvider.java | 22 ++-- .../io/quarkus/oidc/runtime/OidcProvider.java | 2 +- .../oidc/runtime/TokenIntrospectionTest.java | 108 ++++++++++++++++++ 5 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java index 39d66426f92d0d..931070f98e9df0 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java @@ -23,10 +23,13 @@ public final class OidcConstants { public static final String INTROSPECTION_TOKEN_TYPE_HINT = "token_type_hint"; public static final String INTROSPECTION_TOKEN = "token"; public static final String INTROSPECTION_TOKEN_ACTIVE = "active"; + public static final String INTROSPECTION_TOKEN_CLIENT_ID = "client_id"; public static final String INTROSPECTION_TOKEN_EXP = "exp"; public static final String INTROSPECTION_TOKEN_IAT = "iat"; public static final String INTROSPECTION_TOKEN_USERNAME = "username"; public static final String INTROSPECTION_TOKEN_SUB = "sub"; + public static final String INTROSPECTION_TOKEN_AUD = "aud"; + public static final String INTROSPECTION_TOKEN_ISS = "iss"; public static final String REVOCATION_TOKEN = "token"; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java index 137f139f064237..109d675f47652a 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java @@ -1,7 +1,11 @@ package io.quarkus.oidc; +import java.util.HashSet; +import java.util.Set; + import jakarta.json.JsonObject; +import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.runtime.AbstractJsonObjectResponse; /** @@ -21,6 +25,44 @@ public TokenIntrospection(JsonObject json) { super(json); } + public boolean isActive() { + return getBoolean(OidcConstants.INTROSPECTION_TOKEN_ACTIVE); + } + + public String getUsername() { + return getString(OidcConstants.INTROSPECTION_TOKEN_USERNAME); + } + + public String getSubject() { + return getString(OidcConstants.INTROSPECTION_TOKEN_SUB); + } + + public String getAudience() { + return getString(OidcConstants.INTROSPECTION_TOKEN_AUD); + } + + public String getIssuer() { + return getString(OidcConstants.INTROSPECTION_TOKEN_ISS); + } + + public Set getScopes() { + if (this.contains(OidcConstants.TOKEN_SCOPE)) { + String[] scopesArray = getString(OidcConstants.TOKEN_SCOPE).split(" "); + Set scopes = new HashSet<>(scopesArray.length); + for (String scope : scopesArray) { + scopes.add(scope.trim()); + } + return scopes; + } else { + return null; + } + + } + + public String getClientId() { + return getString(OidcConstants.INTROSPECTION_TOKEN_CLIENT_ID); + } + public String getIntrospectionString() { return getNonNullJsonString(); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 7595cf1a04a195..25598779b7eacb 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -3,6 +3,7 @@ import static io.quarkus.oidc.runtime.OidcUtils.validateAndCreateIdentity; import java.security.Principal; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -282,20 +283,15 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { } } else { OidcUtils.setSecurityIdentityIntrospection(builder, result.introspectionResult); - String principalMember = ""; - if (result.introspectionResult.contains(OidcConstants.INTROSPECTION_TOKEN_USERNAME)) { - principalMember = OidcConstants.INTROSPECTION_TOKEN_USERNAME; - } else if (result.introspectionResult.contains(OidcConstants.INTROSPECTION_TOKEN_SUB)) { - // fallback to "sub", if "username" is not present - principalMember = OidcConstants.INTROSPECTION_TOKEN_SUB; + String principalName = result.introspectionResult.getUsername(); + if (principalName == null) { + principalName = result.introspectionResult.getSubject(); } - userName = principalMember.isEmpty() ? "" - : result.introspectionResult.getString(principalMember); - if (result.introspectionResult.contains(OidcConstants.TOKEN_SCOPE)) { - for (String role : result.introspectionResult.getString(OidcConstants.TOKEN_SCOPE) - .split(" ")) { - builder.addRole(role.trim()); - } + userName = principalName != null ? principalName : ""; + + Set scopes = result.introspectionResult.getScopes(); + if (scopes != null) { + builder.addRoles(scopes); } } builder.setPrincipal(new Principal() { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java index 8c40298b1ff294..eb993936834e5e 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java @@ -225,7 +225,7 @@ public TokenIntrospection apply(TokenIntrospection introspectionResult, Throwabl if (t != null) { throw new AuthenticationFailedException(t); } - if (!Boolean.TRUE.equals(introspectionResult.getBoolean(OidcConstants.INTROSPECTION_TOKEN_ACTIVE))) { + if (!introspectionResult.isActive()) { LOG.debugf("Token issued to client %s is not active", oidcConfig.clientId.get()); verifyTokenExpiry(introspectionResult.getLong(OidcConstants.INTROSPECTION_TOKEN_EXP)); throw new AuthenticationFailedException(); diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java new file mode 100644 index 00000000000000..3b7ba097308e52 --- /dev/null +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java @@ -0,0 +1,108 @@ +package io.quarkus.oidc.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Set; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.oidc.TokenIntrospection; + +public class TokenIntrospectionTest { + TokenIntrospection introspection = new TokenIntrospection( + "{" + + "\"active\": true," + + "\"username\": \"alice\"," + + "\"sub\": \"1234567\"," + + "\"aud\": \"http://localhost:8080\"," + + "\"iss\": \"http://keycloak/realm\"," + + "\"client_id\": \"quarkus\"," + + "\"custom\": null," + + "\"id\": 1234," + + "\"permissions\": [\"read\", \"write\"]," + + "\"scope\": \"add divide\"," + + "\"scopes\": {\"scope\": \"see\"}" + + "}"); + + @Test + public void testActive() { + assertTrue(introspection.isActive()); + } + + @Test + public void testGetUsername() { + assertEquals("alice", introspection.getUsername()); + } + + @Test + public void testGetSubject() { + assertEquals("1234567", introspection.getSubject()); + } + + @Test + public void testGetAudience() { + assertEquals("http://localhost:8080", introspection.getAudience()); + } + + @Test + public void testGetIssuer() { + assertEquals("http://keycloak/realm", introspection.getIssuer()); + } + + @Test + public void testGetScopes() { + assertEquals(Set.of("add", "divide"), introspection.getScopes()); + } + + @Test + public void testGetClientId() { + assertEquals("quarkus", introspection.getClientId()); + } + + @Test + public void testGetString() { + assertEquals("alice", introspection.getString("username")); + assertNull(introspection.getString("usernames")); + } + + @Test + public void testGetBoolean() { + assertTrue(introspection.getBoolean("active")); + assertNull(introspection.getBoolean("activate")); + } + + @Test + public void testGetLong() { + assertEquals(1234, introspection.getLong("id")); + assertNull(introspection.getLong("ids")); + } + + @Test + public void testGetArray() { + JsonArray array = introspection.getArray("permissions"); + assertNotNull(array); + assertEquals(2, array.size()); + assertEquals("read", array.getString(0)); + assertEquals("write", array.getString(1)); + assertNull(introspection.getArray("permit")); + } + + @Test + public void testGetObject() { + JsonObject map = introspection.getObject("scopes"); + assertNotNull(map); + assertEquals(1, map.size()); + assertEquals("see", map.getString("scope")); + } + + @Test + public void testGetNullProperty() { + assertNull(introspection.getString("custom")); + } +} From 0fbcda584fc81cc850da1913e85c28424e1c5969 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 11 May 2023 09:55:17 +0200 Subject: [PATCH 289/333] Qute - support optional end tags for sections - resolves #33293 --- docs/src/main/asciidoc/qute-reference.adoc | 49 +++++++++++ .../java/io/quarkus/qute/EngineBuilder.java | 5 ++ .../io/quarkus/qute/IncludeSectionHelper.java | 5 ++ .../src/main/java/io/quarkus/qute/Parser.java | 87 ++++++++++++------- .../java/io/quarkus/qute/ParserError.java | 5 ++ .../io/quarkus/qute/SectionHelperFactory.java | 25 ++++++ .../io/quarkus/qute/SetSectionHelper.java | 5 ++ .../test/java/io/quarkus/qute/EvalTest.java | 2 +- .../java/io/quarkus/qute/IncludeTest.java | 12 +++ .../java/io/quarkus/qute/SetSectionTest.java | 15 ++++ 10 files changed, 179 insertions(+), 31 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 036530d2665a3b..8d6fe3f3195c43 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -150,6 +150,7 @@ Sections:: A <> may contain static text, expressions and nested sections: `{#if foo.active}{foo.name}{/if}`. The name in the closing tag is optional: `{#if active}ACTIVE!{/}`. A section can be empty: `{#myTag image=true /}`. +Some sections support optional end tags, i.e. if the end tag is missing then the section ends where the parent section ends. A section may also declare nested section blocks: `{#if item.valid} Valid. {#else} Invalid. {/if}` and decide which block to render. Unparsed Character Data:: @@ -585,6 +586,54 @@ A section has a start tag that starts with `#`, followed by the name of the sect It may be empty, i.e. the start tag ends with `/`: `{#myEmptySection /}`. Sections usually contain nested expressions and other sections. The end tag starts with `/` and contains the name of the section (optional): `{#if foo}Foo!{/if}` or `{#if foo}Foo!{/}`. +Some sections support optional end tags, i.e. if the end tag is missing then the section ends where the parent section ends. + +.`#let` Optional End Tag Example +[source,html] +---- +{#if item.isActive} + {#let price = item.price} <1> + {price} + // synthetic {/let} added here automatically +{/if} +// {price} cannot be used here! +---- +<1> Defines the local variable that can be used inside the parent `{#if}` section. + +|=== +|Built-in section |Supports Optional End Tag + +|`{#for}` +|❌ + +|`{#if}` +|❌ + +|`{#when}` +|❌ + +|`{#let}` +|✅ + +|`{#with}` +|❌ + +|`{#include}` +|✅ + +|User-defined Tags +|❌ + +|`{#fragment}` +|❌ + +|`{#cached}` +|❌ + +|=== + +[[sections_params]] +==== Parameters A start tag can define parameters with optional names, e.g. `{#if item.isActive}` and `{#let foo=1 bar=false}`. Parameters are separated by one or more spaces. diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java index 713ee3db6abea4..ae7c5dc065e949 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java @@ -332,6 +332,11 @@ public Scope initializeBlock(Scope outerScope, BlockInfo block) { return delegate.initializeBlock(outerScope, block); } + @Override + public MissingEndTagStrategy missingEndTagStrategy() { + return delegate.missingEndTagStrategy(); + } + } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java index a4438a030fae92..8edfcf3df81b31 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java @@ -107,6 +107,11 @@ public ParametersInfo getParameters() { return builder.build(); } + @Override + public MissingEndTagStrategy missingEndTagStrategy() { + return MissingEndTagStrategy.BIND_TO_PARENT; + } + @Override protected boolean ignoreParameterInit(String key, String value) { return key.equals(TEMPLATE) diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index 7f8d58a8bbbd6d..dad999d56c2112 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -28,6 +28,7 @@ import io.quarkus.qute.Expression.Part; import io.quarkus.qute.SectionHelperFactory.BlockInfo; +import io.quarkus.qute.SectionHelperFactory.MissingEndTagStrategy; import io.quarkus.qute.SectionHelperFactory.ParametersInfo; import io.quarkus.qute.SectionHelperFactory.ParserDelegate; import io.quarkus.qute.TemplateNode.Origin; @@ -79,9 +80,6 @@ class Parser implements ParserHelper, ParserDelegate, WithOrigin, ErrorInitializ private final List> contentFilters; private boolean hasLineSeparator; - // The number of param declarations with default values for which a synthetic {#let} section was added - private int paramDeclarationDefaults; - private TemplateImpl template; public Parser(EngineImpl engine, Reader reader, String templateId, String generatedId, Optional variant) { @@ -155,33 +153,40 @@ Template parse() { // Flush the last text segment flushText(); } else { - String reason; - ErrorCode code; + String reason = null; + ErrorCode code = null; if (state == State.TAG_INSIDE_STRING_LITERAL) { reason = "unterminated string literal"; code = ParserError.UNTERMINATED_STRING_LITERAL; } else if (state == State.TAG_INSIDE) { - reason = "unterminated section"; - code = ParserError.UNTERMINATED_SECTION; + // First handle the optional end tags and if an unterminated section is found the then throw an exception + SectionNode.Builder section = sectionStack.peek(); + if (!section.helperName.equals(ROOT_HELPER_NAME)) { + SectionNode.Builder unterminated = handleOptionalEngTags(section, ROOT_HELPER_NAME); + if (unterminated != null) { + reason = "unterminated section"; + code = ParserError.UNTERMINATED_SECTION; + } + } else { + reason = "unterminated expression"; + code = ParserError.UNTERMINATED_EXPRESSION; + } } else { reason = "unexpected state [" + state + "]"; code = ParserError.GENERAL_ERROR; } - throw error(code, - "unexpected non-text buffer at the end of the template - {reason}: {buffer}") - .argument("reason", reason) - .argument("buffer", buffer) - .build(); + if (code != null) { + throw error(code, + "unexpected non-text buffer at the end of the template - {reason}: {buffer}") + .argument("reason", reason) + .argument("buffer", buffer) + .build(); + } } } - // Param declarations with default values - a synthetic {#let} section has no end tag, i.e. {/let} so we need to handle this specially - for (int i = 0; i < paramDeclarationDefaults; i++) { - SectionNode.Builder section = sectionStack.pop(); - sectionStack.peek().currentBlock().addNode(section.build(this::currentTemplate)); - // Remove the last type info map from the stack - scopeStack.pop(); - } + // Note that this also handles the param declarations with default values, i.e. synthetic {#let} sections + handleOptionalEngTags(sectionStack.peek(), ROOT_HELPER_NAME); SectionNode.Builder root = sectionStack.peek(); if (root == null) { @@ -506,7 +511,8 @@ private void sectionEnd(String content, String tag) { SectionNode.Builder section = sectionStack.peek(); SectionBlock.Builder block = section.currentBlock(); String name = content.substring(1, content.length()); - if (block != null && !block.getLabel().equals(SectionHelperFactory.MAIN_BLOCK_NAME) + if (block != null + && !block.getLabel().equals(SectionHelperFactory.MAIN_BLOCK_NAME) && !section.helperName.equals(name)) { // Non-main block end, e.g. {/else} if (!name.isEmpty() && !block.getLabel().equals(name)) { @@ -517,18 +523,23 @@ private void sectionEnd(String content, String tag) { } section.endBlock(); } else { - // Section end, e.g. {/if} + // Section end, e.g. {/if} or {/} if (section.helperName.equals(ROOT_HELPER_NAME)) { throw error(ParserError.SECTION_START_NOT_FOUND, "section start tag found for {tag}") .argument("tag", tag) .build(); } if (!name.isEmpty() && !section.helperName.equals(name)) { - throw error(ParserError.SECTION_END_DOES_NOT_MATCH_START, - "section end tag [{name}] does not match the start tag [{tag}]") - .argument("name", name) - .argument("tag", section.helperName) - .build(); + // The tag name is not empty but does not match the current section + // First handle the optional end tags and if an unterminated section is found the then throw an exception + SectionNode.Builder unterminated = handleOptionalEngTags(section, name); + if (unterminated != null) { + throw error(ParserError.SECTION_END_DOES_NOT_MATCH_START, + "section end tag [{name}] does not match the start tag [{tag}]") + .argument("name", name) + .argument("tag", unterminated.helperName) + .build(); + } } // Pop the section and its main block section = sectionStack.pop(); @@ -539,6 +550,25 @@ private void sectionEnd(String content, String tag) { scopeStack.pop(); } + /** + * + * @param section + * @return an unterminated section or {@code null} if no unterminated section was detected + */ + private SectionNode.Builder handleOptionalEngTags(SectionNode.Builder section, String name) { + while (section != null && !section.helperName.equals(name)) { + if (section.factory.missingEndTagStrategy() == MissingEndTagStrategy.BIND_TO_PARENT) { + section = sectionStack.pop(); + sectionStack.peek().currentBlock().addNode(section.build(this::currentTemplate)); + scopeStack.pop(); + section = sectionStack.peek(); + } else { + return section; + } + } + return null; + } + private void parameterDeclaration(String content, String tag) { Scope currentScope = scopeStack.peek(); @@ -614,14 +644,11 @@ private void parameterDeclaration(String content, String tag) { List.of(key + "?=" + defaultValue).iterator(), sectionNode.currentBlock()); - // Init section block + // Init a synthetic section block currentScope = scopeStack.peek(); Scope newScope = factory.initializeBlock(currentScope, sectionNode.currentBlock()); scopeStack.addFirst(newScope); sectionStack.addFirst(sectionNode); - - // A synthetic {#let} section has no end tag, i.e. {/let} so we need to handle this specially - paramDeclarationDefaults++; } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParserError.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParserError.java index a246ae61399382..b4cd36cbb02f31 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParserError.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParserError.java @@ -59,6 +59,11 @@ public enum ParserError implements ErrorCode { */ UNTERMINATED_SECTION, + /** + * {name + */ + UNTERMINATED_EXPRESSION, + /** * {#if (foo || bar}{/} */ diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java index f2f062e9018f5a..aa165b207d0ea8 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java @@ -118,6 +118,31 @@ default Scope initializeBlock(Scope outerScope, BlockInfo block) { return outerScope; } + /** + * A section end tag may be mandatory or optional. + * + * @return the strategy + */ + default MissingEndTagStrategy missingEndTagStrategy() { + return MissingEndTagStrategy.ERROR; + } + + /** + * This strategy is used when an unterminated section is detected during parsing. + */ + public enum MissingEndTagStrategy { + + /** + * The end tag is mandatory. A missing end tag results in a parser error. + */ + ERROR, + + /** + * The end tag is optional. The section ends where the parent section ends. + */ + BIND_TO_PARENT; + } + interface ParserDelegate extends ErrorInitializer { default TemplateException createParserError(String message) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SetSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SetSectionHelper.java index 5d70b8444e6cd4..7cd5901b980e1f 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SetSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SetSectionHelper.java @@ -115,6 +115,11 @@ public ParametersInfo getParameters() { return ParametersInfo.EMPTY; } + @Override + public MissingEndTagStrategy missingEndTagStrategy() { + return MissingEndTagStrategy.BIND_TO_PARENT; + } + @Override public SetSectionHelper initialize(SectionInitContext context) { Map params = new HashMap<>(); diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java index 7b33224b25a50c..e1ef1b4f59c44f 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EvalTest.java @@ -37,7 +37,7 @@ public void testInvalidTemplateContents() { assertThatExceptionOfType(TemplateException.class) .isThrownBy(() -> Engine.builder().addDefaults().build().parse("{#eval invalid /}").data("invalid", "{foo") .render()) - .withMessageContainingAll("Parser error in the evaluated template", "unterminated section"); + .withMessageContainingAll("Parser error in the evaluated template", "unterminated expression"); } } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/IncludeTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/IncludeTest.java index 1848e70789c8a8..e8b762996d2ad0 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/IncludeTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/IncludeTest.java @@ -222,7 +222,19 @@ public void testInvalidFragment() { assertEquals( "Rendering error in template [bum.html] line 1: invalid fragment identifier [foo-and_bar]", expected.getMessage()); + } + + @Test + public void testOptionalEndTag() { + Engine engine = Engine.builder().addDefaults().build(); + engine.putTemplate("super", engine.parse("{#insert header}default header{/insert}::{#insert}{/}")); + assertEquals("super header:: body", + engine.parse("{#include super}{#header}super header{/header} body").render()); + assertEquals("super header:: 1", + engine.parse("{#let foo = 1}{#include super}{#header}super header{/header} {foo}").render()); + assertEquals("default header:: 1", + engine.parse("{#include super}{#let foo=1} {foo}").render()); } } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java index bc1b70a000c61f..6e45c6c740896c 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java @@ -74,4 +74,19 @@ public void testCompositeParams() { .render()); } + @Test + public void testOptionalEndTag() { + Engine engine = Engine.builder().addDefaults().build(); + assertEquals("true", + engine.parse("{#let foo=true}{foo}").render()); + assertEquals("true ?", + engine.parse("{#let foo=true}{foo} ?").render()); + assertEquals("true::1", + engine.parse("{#let foo=true}{#let bar = 1}{foo}::{bar}").render()); + assertEquals("true", + engine.parse("{#let foo=true}{#if baz}{foo}{/}").data("baz", true).render()); + assertEquals("true::null", + engine.parse("{#if baz}{#let foo=true}{foo}{/if}::{foo ?: 'null'}").data("baz", true).render()); + } + } From 06d116d4e39809bd8794c8f836a6fbca411f33bb Mon Sep 17 00:00:00 2001 From: Marek Skacelik Date: Wed, 5 Apr 2023 13:55:22 +0200 Subject: [PATCH 290/333] Added tests for tracing SmallRye-GraphQL using technology of OpenTelemetry --- bom/application/pom.xml | 2 +- extensions/opentelemetry/deployment/pom.xml | 5 + .../GraphQLOpenTelemetryTest.java | 335 ++++++++++++++++++ .../runtime/QuarkusContextStorage.java | 1 - .../smallrye-graphql/deployment/pom.xml | 12 - .../deployment/SmallRyeGraphQLProcessor.java | 6 +- .../GraphQLTracingDisabledTest.java | 2 +- .../deployment/GraphQLTracingTest.java | 49 --- .../datafetcher/AbstractAsyncDataFetcher.java | 2 + .../QuarkusDefaultDataFetcher.java | 12 +- 10 files changed, 357 insertions(+), 69 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java delete mode 100644 extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingTest.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5c2d7f2a6643a2..77ccc6a38b0326 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -60,7 +60,7 @@ 4.0.1 4.0.0 3.3.4 - 2.1.3 + 2.2.0 3.0.3 6.2.2 4.2.1 diff --git a/extensions/opentelemetry/deployment/pom.xml b/extensions/opentelemetry/deployment/pom.xml index 6bf02358c72a39..88a08f63390cb1 100644 --- a/extensions/opentelemetry/deployment/pom.xml +++ b/extensions/opentelemetry/deployment/pom.xml @@ -106,6 +106,11 @@ quarkus-grpc-deployment test
    + + io.quarkus + quarkus-smallrye-graphql-deployment + test + io.quarkus quarkus-agroal-deployment diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java new file mode 100644 index 00000000000000..8b54abe555d724 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java @@ -0,0 +1,335 @@ +package io.quarkus.opentelemetry.deployment.instrumentation; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; +import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; +import static java.net.HttpURLConnection.HTTP_OK; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.StringReader; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Mutation; +import org.eclipse.microprofile.graphql.Query; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class GraphQLOpenTelemetryTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(HelloResource.class, CustomCDIBean.class, TestSpanExporterProvider.class, + TestSpanExporter.class) + .addAsResource(new StringAsset("smallrye.graphql.allowGet=true"), "application.properties") + .addAsResource(new StringAsset("smallrye.graphql.printDataFetcherException=true"), "application.properties") + .addAsResource(new StringAsset("smallrye.graphql.events.enabled=true"), "application.properties") + .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + @Inject + TestSpanExporter spanExporter; + + @AfterEach + void tearDown() { + spanExporter.reset(); + } + + @Test + void singleResultQueryTraceTest() { + String request = getPayload("query hello {\n" + + " hello\n" + + "}", + null); + + assertSuccessfulRequestContainingMessages(request, "hello xyz"); + + spanExporter.assertSpanCount(3); + + final List spans = Collections.unmodifiableList(spanExporter.getFinishedSpanItems(3)); + assertTimeForSpans(spans); + assertHttpSpan(spans); + + final SpanData operationSpan = spans.get(1); + assertEquals(spans.get(2).getSpanId(), operationSpan.getParentSpanId()); + assertGraphQLSpan(operationSpan, "GraphQL:hello", "QUERY", "hello"); + + final SpanData querySpan = spans.get(0); + assertEquals(operationSpan.getSpanId(), querySpan.getParentSpanId()); + assertEquals("HelloResource.hello", querySpan.getName()); + } + + @Test + void multipleResultQueryTraceTest() { + String request = getPayload("query {\n" + + " hellos\n" + + "}", + null); + + assertSuccessfulRequestContainingMessages(request, "[\"hello, hi, hey\"]"); + + spanExporter.assertSpanCount(3); + + final List spans = Collections.unmodifiableList(spanExporter.getFinishedSpanItems(3)); + assertTimeForSpans(spans); + assertHttpSpan(spans); + + final SpanData operationSpan = spans.get(1); + assertEquals(spans.get(2).getSpanId(), operationSpan.getParentSpanId()); + assertGraphQLSpan(operationSpan, "GraphQL", "QUERY", ""); + + final SpanData querySpan = spans.get(0); + assertEquals(operationSpan.getSpanId(), querySpan.getParentSpanId()); + assertEquals("HelloResource.hellos", querySpan.getName()); + } + + @Test + void nestedCdiBeanInsideQueryTraceTest() throws ExecutionException, InterruptedException { + String request = getPayload("query {\n" + + " helloAfterSecond\n" + + "}", + null); + int iterations = 500; + ExecutorService executor = Executors.newFixedThreadPool(50); + + try { + CompletableFuture[] futures = new CompletableFuture[iterations]; + for (int i = 0; i < iterations; i++) { + futures[i] = CompletableFuture.supplyAsync(() -> assertSuccessfulRequestContainingMessages(request, "hello"), + executor); + } + getNestedCdiBeanTestResult(futures, iterations); + } finally { + executor.shutdown(); + } + } + + private void getNestedCdiBeanTestResult(CompletableFuture[] futures, int iterations) + throws InterruptedException, ExecutionException { + CompletableFuture.allOf(futures).get(); + int numberOfSpansPerGroup = 4; + spanExporter.assertSpanCount(iterations * numberOfSpansPerGroup); + final List spans = Collections + .unmodifiableList(spanExporter.getFinishedSpanItems(iterations * numberOfSpansPerGroup)); + + List> spanCollections = spans.stream() + .collect(Collectors.groupingBy( + SpanData::getTraceId, + LinkedHashMap::new, // use a LinkedHashMap to preserve insertion order + Collectors.toList())) + .values().stream() + .collect(Collectors.toList()); + + assertEquals(spanCollections.size(), iterations); + for (List spanGroup : spanCollections) { + assertTimeForSpans(spanGroup); + assertHttpSpan(spanGroup); + + final SpanData operationSpan = spanGroup.get(2); + assertEquals(spanGroup.get(3).getSpanId(), operationSpan.getParentSpanId()); + assertGraphQLSpan(operationSpan, "GraphQL", "QUERY", ""); + + final SpanData querySpan = spanGroup.get(1); + assertEquals(operationSpan.getSpanId(), querySpan.getParentSpanId()); + assertEquals("HelloResource.helloAfterSecond", querySpan.getName()); + + final SpanData cdiBeanSpan = spanGroup.get(0); + assertEquals(querySpan.getSpanId(), cdiBeanSpan.getParentSpanId()); + assertEquals("CustomCDIBean.waitForSomeTime", cdiBeanSpan.getName()); + } + } + + @Test + void mutationTraceTest() { + String request = getPayload("mutation addHello { createHello }", null); + + assertSuccessfulRequestContainingMessages(request, "hello created"); + + spanExporter.assertSpanCount(3); + + final List spans = Collections.unmodifiableList(spanExporter.getFinishedSpanItems(3)); + assertTimeForSpans(spans); + assertHttpSpan(spans); + + final SpanData operationSpan = spans.get(1); + assertEquals(spans.get(2).getSpanId(), operationSpan.getParentSpanId()); + assertGraphQLSpan(operationSpan, "GraphQL:addHello", "MUTATION", "addHello"); + + final SpanData mutationSpan = spans.get(0); + assertEquals(operationSpan.getSpanId(), mutationSpan.getParentSpanId()); + assertEquals("HelloResource.createHello", mutationSpan.getName()); + } + + @GraphQLApi + public static class HelloResource { + @Inject + CustomCDIBean cdiBean; + + @WithSpan + @Query + public String hello() { + return "hello xyz"; + } + + @WithSpan + @Query + public List hellos() { + return List.of("hello, hi, hey"); + } + + @WithSpan + @Query + public String helloAfterSecond() throws InterruptedException { + cdiBean.waitForSomeTime(); + return "hello"; + } + + // FIXME: error handling in spans + @WithSpan + @Query + public String errorHello() { + throw new RuntimeException("Error"); + } + + @WithSpan + @Mutation + public String createHello() { + return "hello created"; + } + + // TODO: SOURCE field tests (sync) + // TODO: SOURCE field tests (async) + // TODO: nonblocking queries/mutations (reactive) tests + // TODO: subscriptions tests (?) + + } + + @ApplicationScoped + public static class CustomCDIBean { + @WithSpan + public void waitForSomeTime() throws InterruptedException { + Thread.sleep(10); + } + } + + private Void assertSuccessfulRequestContainingMessages(String request, String... messages) { + org.hamcrest.Matcher messageMatcher = Arrays.stream(messages) + .map(CoreMatchers::containsString) + .reduce(Matchers.allOf(), (a, b) -> Matchers.allOf(a, b)); + + RestAssured.given().when() + .accept(MEDIATYPE_JSON) + .contentType(MEDIATYPE_JSON) + .body(request) + .post("/graphql") + .then() + .assertThat() + .statusCode(200) + .and() + .body(messageMatcher); + return null; + } + + private void assertTimeForSpans(List spans) { + if (spans.size() <= 1) { + return; + } + + IntStream.range(0, spans.size() - 1).forEach(i -> { + SpanData current = spans.get(i); + SpanData successor = spans.get(i + 1); + assertTrue(current.getStartEpochNanos() >= successor.getStartEpochNanos()); + assertTrue(current.getEndEpochNanos() <= successor.getEndEpochNanos()); + }); + } + + private void assertHttpSpan(List spans) { + final SpanData server = getSpanByKindAndParentId(spans, SpanKind.SERVER, "0000000000000000"); + assertEquals("POST /graphql", server.getName()); + assertEquals(HTTP_OK, server.getAttributes().get(HTTP_STATUS_CODE)); + assertEquals("POST", server.getAttributes().get(HTTP_METHOD)); + assertEquals("/graphql", server.getAttributes().get(HTTP_ROUTE)); + } + + private void assertGraphQLSpan(SpanData span, String name, String OperationType, String OperationName) { + assertEquals(name, span.getName()); + assertNotNull(span.getAttributes().get(stringKey("graphql.executionId"))); + assertEquals(OperationType, span.getAttributes().get(stringKey("graphql.operationType"))); + assertEquals(OperationName, span.getAttributes().get(stringKey("graphql.operationName"))); + } + + private String getPayload(String query, String variables) { + JsonObject jsonObject = createRequestBody(query, variables); + return jsonObject.toString(); + } + + private JsonObject createRequestBody(String graphQL, String variables) { + JsonObjectBuilder job = Json.createObjectBuilder(); + JsonObject vjo = Json.createObjectBuilder().build(); + + // Extract the operation name from the GraphQL query + String operationName = null; + if (graphQL != null && !graphQL.isEmpty()) { + Matcher matcher = Pattern.compile("(query|mutation|subscription)\\s+([\\w-]+)?").matcher(graphQL); + if (matcher.find()) { + operationName = matcher.group(2); + job.add(QUERY, graphQL); + } + } + + // Parse variables if present + if (variables != null && !variables.isEmpty()) { + try (JsonReader jsonReader = Json.createReader(new StringReader(variables))) { + vjo = jsonReader.readObject(); + } + } + + if (operationName != null) { + job.add(OPERATION_NAME, operationName); + } + + return job.add(VARIABLES, vjo).build(); + } + + private static final String MEDIATYPE_JSON = "application/json"; + private static final String QUERY = "query"; + private static final String VARIABLES = "variables"; + private static final String OPERATION_NAME = "operationName"; + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java index 719c3023b18415..7a30e5bcdae2df 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java @@ -63,7 +63,6 @@ public Scope attach(io.vertx.core.Context vertxContext, Context toAttach) { if (toAttach == beforeAttach) { return Scope.noop(); } - vertxContext.putLocal(OTEL_CONTEXT, toAttach); OpenTelemetryUtil.setMDCData(toAttach, vertxContext); diff --git a/extensions/smallrye-graphql/deployment/pom.xml b/extensions/smallrye-graphql/deployment/pom.xml index 6bc71e823be026..e3319820ed9621 100644 --- a/extensions/smallrye-graphql/deployment/pom.xml +++ b/extensions/smallrye-graphql/deployment/pom.xml @@ -14,7 +14,6 @@ Quarkus - SmallRye GraphQL - Deployment - io.quarkus quarkus-smallrye-graphql @@ -76,11 +75,6 @@ quarkus-smallrye-metrics-deployment test - - io.quarkus - quarkus-smallrye-opentracing-deployment - test - io.quarkus quarkus-resteasy-reactive-deployment @@ -96,12 +90,6 @@ quarkus-elytron-security-properties-file-deployment test - - io.opentracing - opentracing-mock - test - - diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 38881415292048..4ed01cd20b53fd 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -605,9 +605,9 @@ void activateTracing(Capabilities capabilities, BuildProducer unremovableBeans) { boolean activate = shouldActivateService(graphQLConfig.tracingEnabled, - capabilities.isPresent(Capability.OPENTRACING), - "quarkus-smallrye-opentracing", - Capability.OPENTRACING, + capabilities.isPresent(Capability.OPENTELEMETRY_TRACER), + "quarkus-opentelemetry", + Capability.OPENTELEMETRY_TRACER, "quarkus.smallrye-graphql.tracing.enabled", true); if (activate) { diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingDisabledTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingDisabledTest.java index 2e8c7519af9af0..9f503e046c5468 100644 --- a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingDisabledTest.java +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingDisabledTest.java @@ -8,7 +8,7 @@ import io.quarkus.test.QuarkusUnitTest; /** - * Test that the GraphQL extension does not activate OpenTracing capability within SmallRye GraphQL + * Test that the GraphQL extension does not activate OpenTelemetry capability within SmallRye GraphQL * when there is no tracer enabled. */ public class GraphQLTracingDisabledTest extends AbstractGraphQLTest { diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingTest.java deleted file mode 100644 index 6f90e0cf186340..00000000000000 --- a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLTracingTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.quarkus.smallrye.graphql.deployment; - -import java.util.List; -import java.util.stream.Collectors; - -import org.jboss.shrinkwrap.api.asset.EmptyAsset; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.opentracing.mock.MockSpan; -import io.opentracing.mock.MockTracer; -import io.opentracing.util.GlobalTracer; -import io.quarkus.test.QuarkusUnitTest; - -public class GraphQLTracingTest extends AbstractGraphQLTest { - - @RegisterExtension - static QuarkusUnitTest test = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addClasses(TestResource.class, TestPojo.class, TestRandom.class, TestGenericsPojo.class, - BusinessException.class, TestUnion.class, TestUnionMember.class) - .addAsResource(new StringAsset("quarkus.jaeger.disable-tracer-registration=true"), "application.properties") - .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); - - static MockTracer mockTracer = new MockTracer(); - - static { - GlobalTracer.registerIfAbsent(mockTracer); - } - - @BeforeEach - public void before() { - mockTracer.reset(); - } - - @Test - public void testTracing() { - pingTest(); - List spans = mockTracer.finishedSpans() - .stream() - .filter(span -> span.operationName().equals("GraphQL:Query.ping")) - .collect(Collectors.toList()); - Assertions.assertEquals(1, spans.size()); - } - -} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java index 83c07d92cb93d4..56bfe7fdcba339 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java @@ -61,6 +61,8 @@ protected O invokeAndTransform( resultBuilder.data(fieldHelper.transformOrAdaptResponse(result, dfe)); } catch (AbstractDataFetcherException te) { te.appendDataFetcherResult(resultBuilder, dfe); + } finally { + eventEmitter.fireAfterDataFetch(c); } } emitter.complete(resultBuilder.build()); diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java index 311891879e5a40..897efa587f06c2 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java @@ -18,6 +18,7 @@ import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Type; import io.smallrye.graphql.transformation.AbstractDataFetcherException; +import io.smallrye.mutiny.Uni; import io.vertx.core.Context; import io.vertx.core.Promise; import io.vertx.core.Vertx; @@ -90,14 +91,21 @@ private T invokeAndTransformBlocking(final io.smallrye.graphql.api.Context c resultBuilder.clearErrors().data(null).error(new AbortExecutionException(e)); return (T) resultBuilder.build(); } catch (Throwable ex) { - eventEmitter.fireOnDataFetchError(c, ex); throw ex; } }); // Here call blocking with context BlockingHelper.runBlocking(vc, contextualCallable, result); - return (T) result.future().toCompletionStage(); + + return (T) Uni.createFrom().completionStage(result.future().toCompletionStage()).onItemOrFailure() + .invoke((item, error) -> { + if (item != null) { + eventEmitter.fireAfterDataFetch(c); + } else { + eventEmitter.fireOnDataFetchError(c, error); + } + }).subscribeAsCompletionStage(); } @SuppressWarnings("unchecked") From e7ab9cc3a0a73624c3ea590d9742b43b18e09b09 Mon Sep 17 00:00:00 2001 From: Marek Skacelik Date: Wed, 10 May 2023 08:40:29 +0200 Subject: [PATCH 291/333] Added new federation directives indexes --- .../graphql/deployment/SmallRyeGraphQLProcessor.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 4ed01cd20b53fd..df1e43ca7a4e5e 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -74,11 +74,16 @@ import io.smallrye.graphql.api.Deprecated; import io.smallrye.graphql.api.Entry; import io.smallrye.graphql.api.ErrorExtensionProvider; +import io.smallrye.graphql.api.federation.ComposeDirective; import io.smallrye.graphql.api.federation.Extends; import io.smallrye.graphql.api.federation.External; +import io.smallrye.graphql.api.federation.Inaccessible; +import io.smallrye.graphql.api.federation.InterfaceObject; import io.smallrye.graphql.api.federation.Key; import io.smallrye.graphql.api.federation.Provides; import io.smallrye.graphql.api.federation.Requires; +import io.smallrye.graphql.api.federation.Shareable; +import io.smallrye.graphql.api.federation.Tag; import io.smallrye.graphql.cdi.config.ConfigKey; import io.smallrye.graphql.cdi.config.MicroProfileConfig; import io.smallrye.graphql.cdi.producer.GraphQLProducer; @@ -267,6 +272,12 @@ void buildFinalIndex( indexer.indexClass(Provides.class); indexer.indexClass(Requires.class); indexer.indexClass(Deprecated.class); + indexer.indexClass(Shareable.class); + indexer.indexClass(ComposeDirective.class); + indexer.indexClass(InterfaceObject.class); + indexer.indexClass(Inaccessible.class); + indexer.indexClass(io.smallrye.graphql.api.federation.Override.class); + indexer.indexClass(Tag.class); } catch (IOException ex) { LOG.warn("Failure while creating index", ex); } From 94df14b2f2c40a14b7cb1fd71506faca6c66954a Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Wed, 3 May 2023 10:14:35 +0200 Subject: [PATCH 292/333] Update to Graphql-java 20.2 --- 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 77ccc6a38b0326..1eef1d6ab1909e 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -43,7 +43,7 @@ 1.10.6 2.1.12 0.22.0 - 20.1 + 20.2 3.0.3 4.0.1 4.0.1 From a66c8b45884f91e0ee814a6672554f31c22bdef9 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Fri, 5 May 2023 12:03:11 +0200 Subject: [PATCH 293/333] Reflect Bean Validation refactoring in smallrye-graphql --- .../smallrye-graphql/deployment/pom.xml | 2 - .../deployment/SmallRyeGraphQLProcessor.java | 21 --- .../deployment/GraphQLBeanValidationTest.java | 120 ++++++++++++++++++ extensions/smallrye-graphql/runtime/pom.xml | 2 - .../runtime/SmallRyeGraphQLConfig.java | 6 - .../datafetcher/AbstractAsyncDataFetcher.java | 8 +- .../QuarkusDefaultDataFetcher.java | 7 + 7 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLBeanValidationTest.java diff --git a/extensions/smallrye-graphql/deployment/pom.xml b/extensions/smallrye-graphql/deployment/pom.xml index e3319820ed9621..d3a4d7ac0834f3 100644 --- a/extensions/smallrye-graphql/deployment/pom.xml +++ b/extensions/smallrye-graphql/deployment/pom.xml @@ -55,8 +55,6 @@ io.quarkus quarkus-hibernate-validator-deployment - true - provided diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index df1e43ca7a4e5e..89053a9dd37e60 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -88,7 +88,6 @@ import io.smallrye.graphql.cdi.config.MicroProfileConfig; import io.smallrye.graphql.cdi.producer.GraphQLProducer; import io.smallrye.graphql.cdi.tracing.TracingService; -import io.smallrye.graphql.cdi.validation.ValidationService; import io.smallrye.graphql.schema.Annotations; import io.smallrye.graphql.schema.SchemaBuilder; import io.smallrye.graphql.schema.model.Argument; @@ -206,8 +205,6 @@ void registerNativeImageResources(BuildProducer servic // Add a condition for the optional eventing services reflectiveClassCondition.produce(new ReflectiveClassConditionBuildItem(TracingService.class, "io.opentracing.Tracer")); - reflectiveClassCondition - .produce(new ReflectiveClassConditionBuildItem(ValidationService.class, "jakarta.validation.ValidatorFactory")); // Use MicroProfile Config (We use the one from the CDI Module) serviceProvider.produce(ServiceProviderBuildItem.allProvidersFromClassPath(MicroProfileConfig.class.getName())); @@ -628,24 +625,6 @@ void activateTracing(Capabilities capabilities, } } - @BuildStep - void activateValidation(Capabilities capabilities, - SmallRyeGraphQLConfig graphQLConfig, - BuildProducer systemProperties) { - - boolean activate = shouldActivateService(graphQLConfig.validationEnabled, - capabilities.isPresent(Capability.HIBERNATE_VALIDATOR), - "quarkus-hibernate-validator", - Capability.HIBERNATE_VALIDATOR, - "quarkus.smallrye-graphql.validation.enabled", - true); - if (activate) { - systemProperties.produce(new SystemPropertyBuildItem(ConfigKey.ENABLE_VALIDATION, TRUE)); - } else { - systemProperties.produce(new SystemPropertyBuildItem(ConfigKey.ENABLE_VALIDATION, FALSE)); - } - } - @BuildStep void activateEventing(SmallRyeGraphQLConfig graphQLConfig, BuildProducer systemProperties) { if (graphQLConfig.eventsEnabled) { diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLBeanValidationTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLBeanValidationTest.java new file mode 100644 index 00000000000000..8ef79e03df3467 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLBeanValidationTest.java @@ -0,0 +1,120 @@ +package io.quarkus.smallrye.graphql.deployment; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.*; + +import jakarta.validation.constraints.Size; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.mutiny.Uni; + +public class GraphQLBeanValidationTest extends AbstractGraphQLTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ApiWithValidation.class) + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + @GraphQLApi + public static class ApiWithValidation { + + @Query + @NonBlocking + public String nonBlocking(@Size(max = 4, message = "Message too long") String input) { + return input; + } + + @Query + @Blocking + public String blocking(@Size(max = 4, message = "Message too long") String input) { + return input; + } + + @Query + @NonBlocking + public Uni uniNonBlocking(@Size(max = 4, message = "Message too long") String input) { + return Uni.createFrom().item(input); + } + + @Query + @Blocking + public Uni uniBlocking(@Size(max = 4, message = "Message too long") String input) { + return Uni.createFrom().item(input); + } + } + + @Test + public void testNonBlocking() { + String query = getPayload("{nonBlocking(input:\"TOO LONG\")}"); + RestAssured.given() + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql") + .prettyPeek() + .then() + .assertThat() + .statusCode(200) + .body("errors[0].message", containsString("Message too long")) + .body("errors[0].path", hasItems("nonBlocking", "input")) + .body("data.nonBlocking", nullValue()); + } + + @Test + public void testBlocking() { + String query = getPayload("{blocking(input:\"TOO LONG\")}"); + RestAssured.given() + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql") + .prettyPeek() + .then() + .assertThat() + .statusCode(200) + .body("errors[0].message", containsString("Message too long")) + .body("errors[0].path", hasItems("blocking", "input")) + .body("data.blocking", nullValue()); + } + + @Test + public void testUniNonBlocking() { + String query = getPayload("{uniNonBlocking(input:\"TOO LONG\")}"); + RestAssured.given() + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql") + .prettyPeek() + .then() + .assertThat() + .statusCode(200) + .body("errors[0].message", containsString("Message too long")) + .body("errors[0].path", hasItems("uniNonBlocking", "input")) + .body("data.uniNonBlocking", nullValue()); + } + + @Test + public void testUniBlocking() { + String query = getPayload("{uniBlocking(input:\"TOO LONG\")}"); + RestAssured.given() + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql") + .prettyPeek() + .then() + .assertThat() + .statusCode(200) + .body("errors[0].message", containsString("Message too long")) + .body("errors[0].path", hasItems("uniBlocking", "input")) + .body("data.uniBlocking", nullValue()); + } + +} diff --git a/extensions/smallrye-graphql/runtime/pom.xml b/extensions/smallrye-graphql/runtime/pom.xml index 5acfcaa495adcb..1f1911c73af047 100644 --- a/extensions/smallrye-graphql/runtime/pom.xml +++ b/extensions/smallrye-graphql/runtime/pom.xml @@ -50,8 +50,6 @@ io.quarkus quarkus-hibernate-validator - true - provided diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java index 84a9c90c7ce493..79ed56937f73f8 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java @@ -39,12 +39,6 @@ public class SmallRyeGraphQLConfig { @ConfigItem(name = "tracing.enabled") public Optional tracingEnabled; - /** - * Enable validation. By default, this will be enabled if the Hibernate Validator extension is added. - */ - @ConfigItem(name = "validation.enabled") - public Optional validationEnabled; - /** * Enable eventing. Allow you to receive events on bootstrap and execution. */ diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java index 56bfe7fdcba339..7bd79e776aafe4 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/AbstractAsyncDataFetcher.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.concurrent.CompletionStage; +import jakarta.validation.ConstraintViolationException; + import org.eclipse.microprofile.graphql.GraphQLException; import graphql.execution.DataFetcherResult; @@ -16,6 +18,7 @@ import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Type; import io.smallrye.graphql.transformation.AbstractDataFetcherException; +import io.smallrye.graphql.validation.BeanValidationUtil; import io.smallrye.mutiny.Uni; public abstract class AbstractAsyncDataFetcher extends AbstractDataFetcher { @@ -43,12 +46,15 @@ protected O invokeAndTransform( emitter.onTermination(() -> { deactivate(requestContext); }); - if (throwable != null) { eventEmitter.fireOnDataFetchError(c, throwable); if (throwable instanceof GraphQLException) { GraphQLException graphQLException = (GraphQLException) throwable; errorResultHelper.appendPartialResult(resultBuilder, dfe, graphQLException); + } else if (throwable instanceof ConstraintViolationException) { + BeanValidationUtil.addConstraintViolationsToDataFetcherResult( + ((ConstraintViolationException) throwable).getConstraintViolations(), + operationInvoker.getMethod(), resultBuilder, dfe); } else if (throwable instanceof Exception) { emitter.fail(SmallRyeGraphQLServerMessages.msg.dataFetcherException(operation, throwable)); return; diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java index 897efa587f06c2..7b87e659e21be1 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/spi/datafetcher/QuarkusDefaultDataFetcher.java @@ -5,6 +5,8 @@ import java.util.concurrent.CompletionStage; import java.util.function.Consumer; +import jakarta.validation.ConstraintViolationException; + import org.eclipse.microprofile.graphql.GraphQLException; import graphql.execution.AbortExecutionException; @@ -18,6 +20,7 @@ import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Type; import io.smallrye.graphql.transformation.AbstractDataFetcherException; +import io.smallrye.graphql.validation.BeanValidationUtil; import io.smallrye.mutiny.Uni; import io.vertx.core.Context; import io.vertx.core.Promise; @@ -90,6 +93,10 @@ private T invokeAndTransformBlocking(final io.smallrye.graphql.api.Context c } catch (Error e) { resultBuilder.clearErrors().data(null).error(new AbortExecutionException(e)); return (T) resultBuilder.build(); + } catch (ConstraintViolationException cve) { + BeanValidationUtil.addConstraintViolationsToDataFetcherResult(cve.getConstraintViolations(), + operationInvoker.getMethod(), resultBuilder, dfe); + return (T) resultBuilder.build(); } catch (Throwable ex) { throw ex; } From 787ea7cdedcfac0280a2eb44fa69031073baa031 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 11 May 2023 13:32:33 +0300 Subject: [PATCH 294/333] fix: cli plugin arg parsing --- .../main/java/io/quarkus/cli/plugin/PluginCommandFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java index af236f4f08154a..3468df2c531f0a 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java @@ -14,7 +14,6 @@ import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.ISetter; -import picocli.CommandLine.Model.OptionSpec; import picocli.CommandLine.Model.PositionalParamSpec; public class PluginCommandFactory { @@ -51,7 +50,7 @@ public Optional createCommand(Plugin plugin) { spec.usageMessage().description(description); } spec.parser().unmatchedArgumentsAllowed(true); - spec.addOption(OptionSpec.builder("options").type(Map.class).description("options").build()); //This is needed or options are ignored. + spec.parser().unmatchedOptionsArePositionalParams(true); spec.add(PositionalParamSpec.builder().type(String[].class).arity("0..*").description("Positional arguments") .setter(new ISetter() { @Override From c6ce5bb2d20c12306a3d62a35b5a715cddaccd6e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 10 May 2023 16:35:52 +0300 Subject: [PATCH 295/333] Automatically lookup Providers based on the presence of standard Jakarta file Closes: #33262 --- .../deployment/HibernateValidatorProcessor.java | 5 +++++ .../services/jakarta.ws.rs.ext.Providers | 1 - .../ResteasyReactiveCommonProcessor.java | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) delete mode 100644 extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/jakarta.ws.rs.ext.Providers diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java index 4d4d5c1c71c80a..fcb77dda16f832 100644 --- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java +++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java @@ -101,11 +101,13 @@ import io.quarkus.hibernate.validator.runtime.interceptor.MethodValidationInterceptor; import io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyConfigSupport; import io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyReactiveViolationExceptionMapper; +import io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyViolationExceptionMapper; import io.quarkus.hibernate.validator.runtime.jaxrs.ViolationReport; import io.quarkus.hibernate.validator.spi.BeanValidationAnnotationsBuildItem; import io.quarkus.jaxrs.spi.deployment.AdditionalJaxRsResourceMethodAnnotationsBuildItem; import io.quarkus.resteasy.common.spi.ResteasyConfigBuildItem; import io.quarkus.resteasy.common.spi.ResteasyDotNames; +import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem; import io.quarkus.runtime.LocalesBuildTimeConfig; import io.quarkus.runtime.configuration.ConfigBuilder; @@ -331,6 +333,7 @@ void registerAdditionalBeans(HibernateValidatorRecorder hibernateValidatorRecord BuildProducer unremovableBean, BuildProducer autoScopes, BuildProducer syntheticBeanBuildItems, + BuildProducer resteasyJaxrsProvider, Capabilities capabilities) { // The bean encapsulating the Validator and ValidatorFactory additionalBeans.produce(new AdditionalBeanBuildItem(ValidatorProvider.class)); @@ -353,6 +356,8 @@ void registerAdditionalBeans(HibernateValidatorRecorder hibernateValidatorRecord .supplier(hibernateValidatorRecorder.resteasyConfigSupportSupplier( resteasyConfigBuildItem.isPresent() ? resteasyConfigBuildItem.get().isJsonDefault() : false)) .done()); + resteasyJaxrsProvider.produce(new ResteasyJaxrsProviderBuildItem(ResteasyViolationExceptionMapper.class.getName())); + } else if (capabilities.isPresent(Capability.RESTEASY_REACTIVE)) { // The CDI interceptor which will validate the methods annotated with @JaxrsEndPointValidated additionalBeans.produce(new AdditionalBeanBuildItem( diff --git a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/jakarta.ws.rs.ext.Providers b/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/jakarta.ws.rs.ext.Providers deleted file mode 100644 index c88efb99cf0242..00000000000000 --- a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/jakarta.ws.rs.ext.Providers +++ /dev/null @@ -1 +0,0 @@ -io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyViolationExceptionMapper diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 0eda9df3d12179..59368bf1cfa35f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.ext.Providers; import jakarta.ws.rs.ext.RuntimeDelegate; import org.jboss.jandex.AnnotationTarget; @@ -45,8 +46,11 @@ import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BuildTimeConditionBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; @@ -70,6 +74,19 @@ public class ResteasyReactiveCommonProcessor { private static final int LEGACY_READER_PRIORITY = Priorities.USER * 2; // readers are compared by decreased priority private static final int LEGACY_WRITER_PRIORITY = Priorities.USER / 2; // writers are compared by increased priority + private static final String PROVIDERS_SERVICE_FILE = "META-INF/services/" + Providers.class.getName(); + + @BuildStep + void searchForProviders(Capabilities capabilities, + BuildProducer producer) { + if (capabilities.isPresent(Capability.RESTEASY) || capabilities.isPresent(Capability.REST_CLIENT)) { + // in this weird case we don't want the providers to be registered automatically as this would lead to multiple bean definitions + return; + } + // TODO: should we also be looking for the specific provider files? + producer.produce(new AdditionalApplicationArchiveMarkerBuildItem(PROVIDERS_SERVICE_FILE)); + } + @BuildStep void setUpDenyAllJaxRs( CombinedIndexBuildItem index, From 5e6f02d0302392fe543540e1acc7f77353b72d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20Li=C5=A1ka?= Date: Thu, 11 May 2023 12:28:51 +0200 Subject: [PATCH 296/333] init task name starts with service name --- .../src/main/java/io/quarkus/flyway/FlywayProcessor.java | 5 +++-- .../mongodb/deployment/LiquibaseMongodbProcessor.java | 5 +++-- .../io/quarkus/liquibase/deployment/LiquibaseProcessor.java | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index ce3fa9db00e214..aa879715824752 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -46,6 +46,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; @@ -221,9 +222,9 @@ public ServiceStartBuildItem startActions(FlywayRecorder recorder, } @BuildStep - public InitTaskBuildItem configureInitTask() { + public InitTaskBuildItem configureInitTask(ApplicationInfoBuildItem app) { return InitTaskBuildItem.create() - .withName("flyway-init") + .withName(app.getName() + "-flyway-init") .withTaskEnvVars(Map.of("QUARKUS_INIT_AND_EXIT", "true", "QUARKUS_FLYWAY_ENABLED", "true")) .withAppEnvVars(Map.of("QUARKUS_FLYWAY_ENABLED", "false")) .withSharedEnvironment(true) diff --git a/extensions/liquibase-mongodb/deployment/src/main/java/io/quarkus/liquibase/mongodb/deployment/LiquibaseMongodbProcessor.java b/extensions/liquibase-mongodb/deployment/src/main/java/io/quarkus/liquibase/mongodb/deployment/LiquibaseMongodbProcessor.java index 462e0d5f6d61de..0bd1ba82b37909 100644 --- a/extensions/liquibase-mongodb/deployment/src/main/java/io/quarkus/liquibase/mongodb/deployment/LiquibaseMongodbProcessor.java +++ b/extensions/liquibase-mongodb/deployment/src/main/java/io/quarkus/liquibase/mongodb/deployment/LiquibaseMongodbProcessor.java @@ -30,6 +30,7 @@ import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.InitTaskBuildItem; @@ -254,9 +255,9 @@ ServiceStartBuildItem startLiquibase(LiquibaseMongodbRecorder recorder, } @BuildStep - public InitTaskBuildItem configureInitTask() { + public InitTaskBuildItem configureInitTask(ApplicationInfoBuildItem app) { return InitTaskBuildItem.create() - .withName("liquibase-mongodb-init") + .withName(app.getName() + "-liquibase-mongodb-init") .withTaskEnvVars( Map.of("QUARKUS_INIT_AND_EXIT", "true", "QUARKUS_LIQUIBASE_MONGODB_ENABLED", "true")) .withAppEnvVars(Map.of("QUARKUS_LIQUIBASE_MONGODB_ENABLED", "false")) diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index 8666bf778b0f0d..b1e35293535f40 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -41,6 +41,7 @@ import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.IndexDependencyBuildItem; @@ -318,9 +319,9 @@ ServiceStartBuildItem startLiquibase(LiquibaseRecorder recorder, } @BuildStep - public InitTaskBuildItem configureInitTask() { + public InitTaskBuildItem configureInitTask(ApplicationInfoBuildItem app) { return InitTaskBuildItem.create() - .withName("liquibase-init") + .withName(app.getName() + "-liquibase-init") .withTaskEnvVars(Map.of("QUARKUS_INIT_AND_EXIT", "true", "QUARKUS_LIQUIBASE_ENABLED", "true")) .withAppEnvVars(Map.of("QUARKUS_LIQUIBASE_ENABLED", "false")) .withSharedEnvironment(true) From 7c1391f92f8349beb8f3525cfb6161fe24d63ece Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 11 May 2023 17:19:29 +0300 Subject: [PATCH 297/333] test: add test that verify cli plug arg parsing --- .../cli/plugin/PluginCommandFactory.java | 15 +- .../cli/plugin/PluginCommandFactoryTest.java | 198 ++++++++++++++++++ .../io/quarkus/cli/plugin/TestCommand.java | 52 +++++ 3 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 devtools/cli/src/test/java/io/quarkus/cli/plugin/PluginCommandFactoryTest.java create mode 100644 devtools/cli/src/test/java/io/quarkus/cli/plugin/TestCommand.java diff --git a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java index 3468df2c531f0a..2e2f67ca0d5b00 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; import io.quarkus.cli.common.OutputOptionMixin; @@ -20,6 +21,11 @@ public class PluginCommandFactory { private final OutputOptionMixin output; + //Testing + protected PluginCommandFactory() { + this(null); + } + public PluginCommandFactory(OutputOptionMixin output) { this.output = output; } @@ -43,9 +49,12 @@ private Optional createPluginCommand(Plugin plugin) { * Create a command for the specified plugin */ public Optional createCommand(Plugin plugin) { - return createPluginCommand(plugin).map(command -> { + return createPluginCommand(plugin).map(createCommandSpec(plugin.getDescription().orElse(""))); + } + + public Function createCommandSpec(String description) { + return command -> { CommandSpec spec = CommandSpec.wrapWithoutInspection(command); - String description = plugin.getDescription().orElse(""); if (!StringUtil.isNullOrEmpty(description)) { spec.usageMessage().description(description); } @@ -66,7 +75,7 @@ public T set(T value) throws Exception { } }).build()); return spec; - }); + }; } /** diff --git a/devtools/cli/src/test/java/io/quarkus/cli/plugin/PluginCommandFactoryTest.java b/devtools/cli/src/test/java/io/quarkus/cli/plugin/PluginCommandFactoryTest.java new file mode 100644 index 00000000000000..e8744998b09f3f --- /dev/null +++ b/devtools/cli/src/test/java/io/quarkus/cli/plugin/PluginCommandFactoryTest.java @@ -0,0 +1,198 @@ +package io.quarkus.cli.plugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import picocli.CommandLine; + +public class PluginCommandFactoryTest { + + final PluginCommandFactory factory = new PluginCommandFactory(); + + @Test + void testNoArgs() { + //given + TestCommand rootCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)); + //when + commandLine.execute(new String[0]); + //thew + assertEquals(0, rootCommand.getArguments().size()); + } + + @Test + void testSingleArg() { + //given + TestCommand rootCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)); + //when + commandLine.execute("hello"); + //then + assertEquals("hello", rootCommand.getArguments().get(0)); + } + + @Test + void testMultiArgs() { + //given + TestCommand rootCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)); + //when + commandLine.execute("one", "two", "three"); + //then + assertEquals("one", rootCommand.getArguments().get(0)); + assertEquals("two", rootCommand.getArguments().get(1)); + assertEquals("three", rootCommand.getArguments().get(2)); + } + + @Test + void testMultiArgsAndOptions() { + //given + TestCommand rootCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)); + //when + commandLine.execute("one", "two", "three", "--depth", "5"); + //then + assertEquals("one", rootCommand.getArguments().get(0)); + assertEquals("two", rootCommand.getArguments().get(1)); + assertEquals("three", rootCommand.getArguments().get(2)); + assertEquals("--depth", rootCommand.getArguments().get(3)); + assertEquals("5", rootCommand.getArguments().get(4)); + } + + @Test + void testSubCommandNoArgs() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand))); + + //when + commandLine.execute(new String[0]); + //thew + assertEquals(0, rootCommand.getArguments().size()); + assertEquals(0, subCommand.getArguments().size()); + } + + @Test + void testSubSingleArg() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand))); + + //when + commandLine.execute("sub", "hello"); + //then + assertEquals("hello", subCommand.getArguments().get(0)); + } + + @Test + void testSubMultiArgs() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand))); + //when + commandLine.execute("sub", "one", "two", "three"); + //then + assertEquals("one", subCommand.getArguments().get(0)); + assertEquals("two", subCommand.getArguments().get(1)); + assertEquals("three", subCommand.getArguments().get(2)); + } + + @Test + void testSubMultiArgsAndOptions() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand))); + //when + commandLine.execute("sub", "one", "two", "three", "--depth", "5"); + //then + assertEquals("one", subCommand.getArguments().get(0)); + assertEquals("two", subCommand.getArguments().get(1)); + assertEquals("three", subCommand.getArguments().get(2)); + assertEquals("--depth", subCommand.getArguments().get(3)); + assertEquals("5", subCommand.getArguments().get(4)); + } + + @Test + void testSecLevelSubCommandNoArgs() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + TestCommand secondLevelSubCommand = new TestCommand(); + + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand)) + .addSubcommand("sec-sub", new CommandLine( + factory.createCommandSpec("secon level sub command").apply(secondLevelSubCommand)))); + + //when + commandLine.execute(new String[0]); + //thew + assertEquals(0, rootCommand.getArguments().size()); + assertEquals(0, subCommand.getArguments().size()); + assertEquals(0, secondLevelSubCommand.getArguments().size()); + } + + @Test + void testSecLevelSubSingleArg() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + TestCommand secondLevelSubCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand)) + .addSubcommand("sec-sub", new CommandLine( + factory.createCommandSpec("secon level sub command").apply(secondLevelSubCommand)))); + + //when + commandLine.execute("sub", "sec-sub", "hello"); + //then + assertEquals("hello", secondLevelSubCommand.getArguments().get(0)); + } + + @Test + void testSecLevelSubMultiArgs() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + TestCommand secondLevelSubCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand)) + .addSubcommand("sec-sub", new CommandLine( + factory.createCommandSpec("secon level sub command").apply(secondLevelSubCommand)))); + //when + commandLine.execute("sub", "sec-sub", "one", "two", "three"); + //then + assertEquals("one", secondLevelSubCommand.getArguments().get(0)); + assertEquals("two", secondLevelSubCommand.getArguments().get(1)); + assertEquals("three", secondLevelSubCommand.getArguments().get(2)); + } + + @Test + void testSecLevelSubMultiArgsAndOptions() { + //given + TestCommand rootCommand = new TestCommand(); + TestCommand subCommand = new TestCommand(); + TestCommand secondLevelSubCommand = new TestCommand(); + CommandLine commandLine = new CommandLine(factory.createCommandSpec("rootCommand command").apply(rootCommand)) + .addSubcommand("sub", new CommandLine(factory.createCommandSpec("sub command").apply(subCommand)) + .addSubcommand("sec-sub", new CommandLine( + factory.createCommandSpec("secon level sub command").apply(secondLevelSubCommand)))); + //when + commandLine.execute("sub", "sec-sub", "one", "two", "three", "--depth", "5"); + //then + assertEquals("one", secondLevelSubCommand.getArguments().get(0)); + assertEquals("two", secondLevelSubCommand.getArguments().get(1)); + assertEquals("three", secondLevelSubCommand.getArguments().get(2)); + assertEquals("--depth", secondLevelSubCommand.getArguments().get(3)); + assertEquals("5", secondLevelSubCommand.getArguments().get(4)); + } +} diff --git a/devtools/cli/src/test/java/io/quarkus/cli/plugin/TestCommand.java b/devtools/cli/src/test/java/io/quarkus/cli/plugin/TestCommand.java new file mode 100644 index 00000000000000..cec9843bcb84e1 --- /dev/null +++ b/devtools/cli/src/test/java/io/quarkus/cli/plugin/TestCommand.java @@ -0,0 +1,52 @@ +package io.quarkus.cli.plugin; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.cli.common.OutputOptionMixin; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +@Command +public class TestCommand implements PluginCommand { + + private static final String DEFAULT_CMD = "cmd"; + + private final String cmd; + private final List arguments = new ArrayList<>(); + + public TestCommand() { + this(DEFAULT_CMD); + } + + public TestCommand(String cmd) { + this.cmd = cmd; + } + + @Override + public Integer call() throws Exception { + System.out.println("Calling " + cmd + " " + String.join(" ", arguments)); + return CommandLine.ExitCode.OK; + } + + @Override + public List getCommand() { + return List.of(cmd); + } + + @Override + public List getArguments() { + return arguments; + } + + @Override + public void useArguments(List arguments) { + this.arguments.clear(); + this.arguments.addAll(arguments); + } + + @Override + public OutputOptionMixin getOutput() { + return null; + } +} From 1a46fb4dc2961133d58309e0b68d5e600f93efa9 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 11 May 2023 11:39:27 -0300 Subject: [PATCH 298/333] Add missing line break to render doc properly --- docs/src/main/asciidoc/update-quarkus.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/update-quarkus.adoc b/docs/src/main/asciidoc/update-quarkus.adoc index 33354559caa45b..88243eaa4f6ac3 100644 --- a/docs/src/main/asciidoc/update-quarkus.adoc +++ b/docs/src/main/asciidoc/update-quarkus.adoc @@ -31,6 +31,7 @@ The following update command only covers a few items in this quick reference. == Prerequisites * A project based on Quarkus version 2.13 or later. + :prerequisites-time: 30 minutes include::{includes}/prerequisites.adoc[] From c3d44dba03ecc0cc264577f7487be0eba57a4308 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 3 May 2023 11:01:18 -0400 Subject: [PATCH 299/333] update JFR and monitoring docs --- .../quarkus/deployment/pkg/NativeConfig.java | 12 +++++- .../main/asciidoc/building-native-image.adoc | 40 +++++++++++++++++ docs/src/main/asciidoc/native-reference.adoc | 43 +++++++++++++++---- 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index c51f7915711eb3..0f00332f17113e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -247,7 +247,15 @@ default String getEffectiveBuilderImage() { boolean enableVmInspection(); /** - * Enable monitoring options that allow the VM to be inspected at run time. + * Enable monitoring various monitoring options. The value should be comma separated. + *
      + *
    • jfr for JDK flight recorder support
    • + *
    • jvmstat for JVMStat support
    • + *
    • heapdump for heampdump support
    • + *
    • jmxclient for JMX client support (experimental)
    • + *
    • jmxserver for JMX server support (experimental)
    • + *
    • all for all monitoring features
    • + *
    */ Optional> monitoring(); @@ -454,6 +462,8 @@ enum MonitoringOption { HEAPDUMP, JVMSTAT, JFR, + JMXSERVER, + JMXCLIENT, ALL } } diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index a0a652df7085e9..8620002a85b7a2 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -886,6 +886,46 @@ gdb -ex 'directory ./target' ./target/getting-started-1.0.0-SNAPSHOT-runner For a more detailed guide about debugging native images please refer to the xref:native-reference.adoc[Native Reference Guide]. +== Using Monitoring Options + +Monitoring options such as JDK flight recorder, jvmstat, heap dumps, and remote JMX (experimental in Mandrel 23) +can be added to the native executable build. Simply supply a comma separated list of the monitoring options you wish to +include at build time. +[source,bash] +---- +-Dquarkus.native.monitoring= +---- + +|=== +|Monitoring Option |Description |Availability As Of + +|jfr +|Include JDK Flight Recorder support +|GraalVM CE 21.3 Mandrel 21.3 + +|jvmstat +|Adds jvmstat support +|GraalVM 22.3 Mandrel 22.3 + +|heapdump +|Adds support for generating heap dumps +|GraalVM 22.3 Mandrel 22.3 + +|jmxclient +|Adds support for connections to JMX servers. +|GraalVM for JDK 17/20 Mandrel 23.0 + +|jmxserver +|Adds support for accepting connections from JMX clients. +|GraalVM for JDK 17/20 Mandrel 23.0 + +|all +|Adds all monitoring options. +|GraalVM 22.3 Mandrel 22.3 +|=== + +Please see the Quarkus Native Reference Guide for more detailed information on these monitoring options. + [[configuration-reference]] == Configuring the Native Executable diff --git a/docs/src/main/asciidoc/native-reference.adoc b/docs/src/main/asciidoc/native-reference.adoc index ab2cadd835fbad..52d8cbd4c7ef15 100644 --- a/docs/src/main/asciidoc/native-reference.adoc +++ b/docs/src/main/asciidoc/native-reference.adoc @@ -2133,23 +2133,50 @@ $ ./mvnw package -DskipTests -Dnative \ https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH170[Java Flight Recorder (JFR)] and https://www.oracle.com/java/technologies/jdk-mission-control.html[JDK Mission Control (JMC)] can be used to profile native binaries since GraalVM CE 21.2.0. -However, JFR in GraalVM is currently significantly limited in capabilities compared to HotSpot. -The custom event API is fully supported, but many VM level features are unavailable. -They will be added in future releases. Current limitations are: - -* Minimal VM level events +However, JFR in GraalVM is currently limited in capabilities compared to HotSpot. +The custom event API is fully supported, but some VM level features are unavailable. +More events and JFR features will continue to be added in later releases. +The following table outlines Native Image JFR support and limitations by version. + +[cols="2,5a,5a"] +|=== +|GraalVM Version |Supports |Limitations + +|GraalVM CE 21.3 +|* Minimal VM Level events +* Custom events API +* Start recordings upon executabe run or JFR Recording API +| * No old object sampling * No stacktrace tracing -* No Streaming API for JDK 17 +* No event streaming + +|GraalVM CE 22.3 +|* Everything from GraalVM CE 21.3 +* Additional monitor and thread events +|* No old object sampling +* No stacktrace tracing +* No event streaming + +|GraalVM CE for JDK 17/20 +|* Everything from GraalVM CE 22.3 +* Additional monitor, thread, container, and allocation events +* Stacktraces +* Sampling based method profiling +* Event streaming +|* No old object sampling + +|=== + -To use JFR add the application property: `-Dquarkus.native.enable-vm-inspection=true`. +To add JFR support to your Quarkus executable, add the application property: `-Dquarkus.native.monitoring=jfr`. E.g. [source,bash,subs=attributes+] ---- ./mvnw package -DskipTests -Dnative -Dquarkus.native.container-build=true \ -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} \ - -Dquarkus.native.enable-vm-inspection=true + -Dquarkus.native.monitoring=jfr ---- Once the image is compiled, enable and start JFR via runtime flags: `-XX:+FlightRecorder` and `-XX:StartFlightRecording`. For example: From f2a8788a2436300c6a3921da56ec13c1b70f9332 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Thu, 11 May 2023 12:34:15 -0400 Subject: [PATCH 300/333] add mandrel to table --- docs/src/main/asciidoc/native-reference.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/native-reference.adoc b/docs/src/main/asciidoc/native-reference.adoc index 52d8cbd4c7ef15..e1c406f6d05bd9 100644 --- a/docs/src/main/asciidoc/native-reference.adoc +++ b/docs/src/main/asciidoc/native-reference.adoc @@ -2142,7 +2142,7 @@ The following table outlines Native Image JFR support and limitations by version |=== |GraalVM Version |Supports |Limitations -|GraalVM CE 21.3 +|GraalVM CE 21.3 and Mandrel 21.3 |* Minimal VM Level events * Custom events API * Start recordings upon executabe run or JFR Recording API @@ -2151,14 +2151,14 @@ The following table outlines Native Image JFR support and limitations by version * No stacktrace tracing * No event streaming -|GraalVM CE 22.3 +|GraalVM CE 22.3 and Mandrel 22.3 |* Everything from GraalVM CE 21.3 * Additional monitor and thread events |* No old object sampling * No stacktrace tracing * No event streaming -|GraalVM CE for JDK 17/20 +|GraalVM CE for JDK 17/20 and Mandrel 23.0 |* Everything from GraalVM CE 22.3 * Additional monitor, thread, container, and allocation events * Stacktraces From 59540213ef478105c907b63b894524ec03ac3592 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 11 May 2023 15:48:59 +0300 Subject: [PATCH 301/333] Remove @NotNull annotations --- .../deployment/devservices/InfinispanDevServiceProcessor.java | 2 -- .../quarkus/cache/redis/deployment/RedisCacheProcessor.java | 2 -- .../kotlin/serialization/common/JsonBuilderCustomizer.java | 4 +--- .../src/main/java/org/acme/service/SimpleService.java | 3 --- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java index 436353068a1bd6..a332c47b39a4fb 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devservices/InfinispanDevServiceProcessor.java @@ -18,7 +18,6 @@ import org.infinispan.commons.util.Version; import org.infinispan.server.test.core.InfinispanContainer; import org.jboss.logging.Logger; -import org.jetbrains.annotations.NotNull; import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsNormal; @@ -224,7 +223,6 @@ private RunningDevService startContainer(String clientName, DockerStatusBuildIte .orElseGet(infinispanServerSupplier); } - @NotNull private RunningDevService getRunningDevService(String clientName, String containerId, Closeable closeable, String hosts, String username, String password, Map config) { config.put(getConfigPrefix(clientName) + "hosts", hosts); diff --git a/extensions/redis-cache/deployment/src/main/java/io/quarkus/cache/redis/deployment/RedisCacheProcessor.java b/extensions/redis-cache/deployment/src/main/java/io/quarkus/cache/redis/deployment/RedisCacheProcessor.java index ca908f3006e978..22ccdb23535192 100644 --- a/extensions/redis-cache/deployment/src/main/java/io/quarkus/cache/redis/deployment/RedisCacheProcessor.java +++ b/extensions/redis-cache/deployment/src/main/java/io/quarkus/cache/redis/deployment/RedisCacheProcessor.java @@ -20,7 +20,6 @@ import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; import org.jboss.logging.Logger; -import org.jetbrains.annotations.NotNull; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.cache.CompositeCacheKey; @@ -103,7 +102,6 @@ void determineValueTypes(RedisCacheBuildRecorder recorder, CombinedIndexBuildIte recorder.setCacheValueTypes(valueTypes); } - @NotNull private static Map valueTypesFromCacheResultAnnotation(CombinedIndexBuildItem combinedIndex) { Map> valueTypesFromAnnotations = new HashMap<>(); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/JsonBuilderCustomizer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/JsonBuilderCustomizer.java index 5bfab19d9e3b14..4948a28be2a12a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/JsonBuilderCustomizer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/JsonBuilderCustomizer.java @@ -1,7 +1,5 @@ package io.quarkus.resteasy.reactive.kotlin.serialization.common; -import org.jetbrains.annotations.NotNull; - import kotlinx.serialization.json.JsonBuilder; /** @@ -25,7 +23,7 @@ default int priority() { return DEFAULT_PRIORITY; } - default int compareTo(@NotNull JsonBuilderCustomizer o) { + default int compareTo(JsonBuilderCustomizer o) { return Integer.compare(o.priority(), priority()); } } diff --git a/integration-tests/gradle/src/main/resources/multi-source-project/src/main/java/org/acme/service/SimpleService.java b/integration-tests/gradle/src/main/resources/multi-source-project/src/main/java/org/acme/service/SimpleService.java index b8df650a668c1c..0f7635267e5006 100644 --- a/integration-tests/gradle/src/main/resources/multi-source-project/src/main/java/org/acme/service/SimpleService.java +++ b/integration-tests/gradle/src/main/resources/multi-source-project/src/main/java/org/acme/service/SimpleService.java @@ -1,12 +1,9 @@ package org.acme.service; -import org.jetbrains.annotations.NotNull; - import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class SimpleService { - @NotNull public String hello() { return "hello from JavaComponent"; } From 07c00c5989554eacc89bd372c6ebf71b4cb4bb29 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Fri, 12 May 2023 11:35:58 +0200 Subject: [PATCH 302/333] Update mariadb to latest LTS 10.11 for devservices and integration tests --- build-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index b21004cd9add8a..938beb706a7542 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -90,7 +90,7 @@ docker.io/postgres:14 - docker.io/mariadb:10.6 + docker.io/mariadb:10.11 docker.io/ibmcom/db2:11.5.7.0a mcr.microsoft.com/mssql/server:2019-latest docker.io/mysql:8.0 From f6c8d4ea1fbf5e3b5437dcc49259babd5b3bcc39 Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Fri, 12 May 2023 14:30:08 +0300 Subject: [PATCH 303/333] Fix timeout duration default value --- .../config/runtime/exporter/OtlpExporterTracesConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java index c0d590ede03d42..5cc4daa9c2bf5e 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java @@ -63,7 +63,7 @@ public interface OtlpExporterTracesConfig { * Sets the maximum time to wait for the collector to process an exported batch of spans. If * unset, defaults to {@value OtlpExporterRuntimeConfig#DEFAULT_TIMEOUT_SECS}s. */ - @WithDefault("10S") + @WithDefault("10s") Duration timeout(); /** From 2d62eaeb4853e66ebfc915f41f251beedcda9e0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 May 2023 22:20:34 +0000 Subject: [PATCH 304/333] Bump kotlinx-coroutines-bom from 1.6.4 to 1.7.1 Bumps [kotlinx-coroutines-bom](https://github.com/Kotlin/kotlinx.coroutines) from 1.6.4 to 1.7.1. - [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases) - [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md) - [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.6.4...1.7.1) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 fda3f509d69ecd..8fe558d7fb2636 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -158,7 +158,7 @@ 2.2.0 1.0.0 1.8.21 - 1.6.4 + 1.7.1 1.5.0 3.6.0 3.2.0 From d23f4faba6b88a18a7b5587caa89418e6044cb42 Mon Sep 17 00:00:00 2001 From: Mazen Khalil Date: Sat, 13 May 2023 19:53:09 +0300 Subject: [PATCH 305/333] Bump micrometer to 1.11.0 --- 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 fda3f509d69ecd..78d358cf178cc2 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -40,7 +40,7 @@ 1.25.0-alpha 1.8.1 5.0.2.Final - 1.10.6 + 1.11.0 2.1.12 0.22.0 20.1 From 7c0de2405074b273078e7c2b28c9b2d24d118387 Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Thu, 11 May 2023 12:52:10 -0400 Subject: [PATCH 306/333] Add prerequisites section --- .../asciidoc/doc-contribute-docs-howto.adoc | 102 +++++++++++++----- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/docs/src/main/asciidoc/doc-contribute-docs-howto.adoc b/docs/src/main/asciidoc/doc-contribute-docs-howto.adoc index fb5ad1ced4012e..43e8aedd594147 100644 --- a/docs/src/main/asciidoc/doc-contribute-docs-howto.adoc +++ b/docs/src/main/asciidoc/doc-contribute-docs-howto.adoc @@ -1,6 +1,5 @@ //// -This document is maintained in the main Quarkus repository -and pull requests should be submitted there: +To maintain this document, submit issues and pull requests in the main Quarkus repository: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// [id="doc-contribute-howto"] @@ -33,14 +32,15 @@ We suggest you have the following materials nearby: To ensure that your content shows up correctly on the link:https://quarkus.io/guides/[Quarkus documentation home page], use the following steps: -. Decide on a content type that best fits the content that you are contributing. +. Decide on a xref:doc-concept.adoc[Diataxis] content type that best fits the content that you are contributing. + TIP: To help you decide, see the content type descriptions in xref:{doc-guides}/doc-reference.adoc#titles-and-headings[Titles and headings] on the "About Quarkus documentation" page. -+ + . Go to the `src/main/asciidoc/_templates` directory, and make a copy of the relevant template for the content type you have chosen. Be sure to: ** Use the filename syntax of`---.adoc`. For example, `security-basic-authentication-tutorial.adoc`. ** Save the file to the `docs/src/main/asciidoc` folder in the `quarkus` repository. -. Set the minimum required header information as outlined in the following example: + +. Set the minimum required header information to ensure that the content renders correctly in the website portal and on the documentation home page, as outlined in the following example: + [source,asciidoc] ---- @@ -48,36 +48,83 @@ TIP: To help you decide, see the content type descriptions in xref:{doc-guides}/ = Secure a Quarkus application with basic authentication <2> include::_attributes.adoc[] <3> :categories: security,web <4> +<5> ---- -+ <1> Set the `id` value to be the same as the file name but without the extension. You can shorten this if the file name is too long. <2> For information about how to create a good title for each content type, see xref:{doc-guides}/doc-reference.adoc#titles-and-headings[Titles and headings] on the "Quarkus style and content guidelines" page. -<3> The `_attributes.adoc` include is required to ensure that attributes get resolved, the table of contents is generated, and content renders in the website portal. +<3> The `_attributes.adoc` include is required to ensure that attributes get resolved and the table of contents is generated. <4> Set at least one category to ensure that the content is findable on the link:https://quarkus.io/guides/[Quarkus documentation home page]. For a list of Quarkus categories, see xref:{doc-guides}/doc-reference.adoc#document-attributes-and-variables[Document attributes and variables] on the "Quarkus style and content guidelines" page. +<5> Insert a blank line before the abstract. + [IMPORTANT] ==== Ensure there are no line breaks in the header section until after `:categories:` line. ==== + +. Add an abstract that describes the purpose of the guide. + -. Add an abstract to describe the purpose of the guide. [IMPORTANT] ==== -The first sentence of the abstract must explain the value and some benefit of the content in less than 27 words because this automatically displays on the link:https://quarkus.io/guides/[Quarkus guides homepage]. +The first sentence of the abstract must explain the value and some benefit of the content in less than 27 words because this automatically displays on the link:https://quarkus.io/guides/[Quarkus guides homepage]. There must also be a line break before and after the abstract. ==== For more information about the minimum header requirements, see xref:{doc-guides}/doc-reference.adoc#document-structure[Document structure] on the "Quarkus style and content guidelines" page. +[id="add-prerequisites"] +== Add a prerequisites section + +For how-to and tutorial topics, include a prerequisites section just after the abstract. +Declaring prerequisites clarifies the starting place for both how-to and tutorial content. +Include them even though they might seem obvious to knowledgeable users. + +.An example prerequisite with callout explanations + +[source,asciidoc] +---- +.Prerequisites <1> + +:prerequisites-time: 30 minutes <2> +include::{includes}/prerequisites.adoc[] <3> +* <4> +---- +<1> Section heading for the prerequisites +<2> Optional: An attribute that modifies the prerequisites +<3> An include statement for the `prerequisites.adoc` file +<4> Optional: An additional prerequisite not covered by the attributes + +.The default prerequisites + +By default, `include::{includes}/prerequisites.adoc[]` inserts the following asciidoc: + +[quote,subs="none"] +---- +include::{includes}/prerequisites.adoc[] +---- + +.Using attributes to modify the prerequisites + +Optionally, you can add, remove, or modify the default prerequisites by inserting the following attributes on the line before the `include::{includes}/prerequisites.adoc[]` macro. + +* `{prerequisites-time}: ` overrides the default value of 15 minutes. For example, `{prerequisites-time}: 30` adds `* Roughly 30 minutes`. +* `{prerequisites-no-maven}` removes `* Apache Maven `. +* `{prerequisites-docker}` adds `* A working container runtime (Docker or xref:podman.adoc[Podman])`. +* `{prerequisites-docker-compose}` adds `Docker and Docker Compose or xref:podman.adoc[Podman], and Docker Compose`. +* `{prerequisites-no-cli}` removes `* Optionally the xref:cli-tooling.adoc[Quarkus CLI] if you want to use it`. +* `{prerequisites-no-graalvm}` or `{prerequisites-graalvm-mandatory}` remove `* Optionally Mandrel or GraalVM installed and xref:building-native-image.adoc#configuring-graalvm[configured appropriately] if you want to build a native executable (or Docker if you use a native container build)`. +* `{prerequisites-graalvm-mandatory}` adds `* Mandrel or GraalVM installed and xref:building-native-image.adoc#configuring-graalvm[configured appropriately]`. + +For more information about these attributes, inspect the content of the `docs/src/main/asciidoc/_includes/prerequisites.adoc` file. + == Retire and redirect an existing Quarkus AsciiDoc source file As content evolves, you might want to restructure an existing piece of Quarkus content into one or more content types and retire the existing AsciiDoc source file. -If you are retiring or renaming a published Quarkus AsciiDoc source file, ensure that the restructure does not break existing bookmarks and links to original content. +If you are retiring or renaming a published Quarkus AsciiDoc source file, ensure that the restructure does not break existing bookmarks and links to the original content. Configure a URL redirect in the link:https://github.com/quarkusio/quarkusio.github.io/[Quarkus.io Website] GitHub repository by using the following steps: -. Switch to the link:https://github.com/quarkusio/quarkusio.github.io/tree/develop/_redirects/guides[quarkusio/quarkusio.github.io] repository, and open the `_redirects/guides` folder. -. Create a redirection file in Markdown format with a filename that matches the original AsciiDoc source filename that you want to retire. +. Switch to the link:https://github.com/quarkusio/quarkusio.github.io/tree/develop/_redirects/guides[`quarkusio/quarkusio.github.io`] repository, and open the `_redirects/guides` folder. +. Create a redirection file in Markdown format with a filename that matches the original AsciiDoc source filename that you want to retire. . Add the following contents to the Markdown redirection file: + [source,markdown] @@ -87,8 +134,8 @@ newUrl: /guides/ // <2> --- + Where: -<1> Is the name of the original AsciiDoc source file that you are retiring, without the `.adoc` file extension. -<2> Is the name of the AsciiDoc source file that you want to redirect to, without the `.adoc` file extension. +<1> The name of the original AsciiDoc source file that you are retiring, without the `.adoc` file extension. +<2> The name of the AsciiDoc source file that you want to redirect to, without the `.adoc` file extension. .Example @@ -117,7 +164,7 @@ Running `-DquicklyDocs` produces: - Generated AsciiDoc (`adoc` files) describing configuration properties in the `target/asciidoc/generated/config/` directory. - AsciiDoc output (`html` files) in the `docs/target/generated-docs/` directory. -- YAML files containing metadata for all documents individually (`docs/target/indexByFile.yaml`) and grouped by document type (`target/indexByType.yaml`). +- YAML files containing metadata for all documents individually (`docs/target/indexByFile.yaml`) and grouped by document type (`target/indexByType.yaml`). - YAML files that list metadata errors by file (`docs/target/errorsByFile.yaml`) and by error type (`docs/target/errorsByType.yaml`) Review the resulting output and fix any issues before you submit your changes in a PR for review. @@ -150,10 +197,10 @@ Our builds use https://vale.sh[Vale] to check grammar, style, and word usage in === Containerized Vale -This approach requires a working container runtime (Docker or xref:podman.adoc[Podman]). +This approach requires a working container runtime (Docker or xref:podman.adoc[Podman]). -The `docs` module has a JUnit 5 test that will run the Vale linter in a container (using https://www.testcontainers.org/[Testcontainers]). -It verifies both Quarkus document metadata and Vale style rules. +The `docs` module has a JUnit 5 test that will run the Vale linter in a container (using https://www.testcontainers.org/[Testcontainers]). +It verifies both Quarkus document metadata and Vale style rules. Run the test in one of the following ways: @@ -190,7 +237,7 @@ For more information, see the https://vale.sh/manual/[Vale CLI Manual]. https://vale.sh/docs/integrations/guide/[Vale IDE integrations] require the Vale CLI to be installed. -Each IDE integration has its own configuration requirements. The Visual Studio Code IDE extension, for example, requires definition of the Vale CLI path: +Each IDE integration has its own configuration requirements. The Visual Studio Code IDE extension, for example, requires a definition of the Vale CLI path: [source,json] ---- @@ -205,16 +252,17 @@ Each IDE integration has its own configuration requirements. The Visual Studio C Submit your proposed changes to the core Quarkus docs by {gh-pull-requests-fork}[creating a pull request] against the `main` branch of the Quarkus repository from your own {gh-about-forks}[repository fork]. Reviews for code and documentation have different (but overlapping) participants. -To simplify collaborative review, either isolate changes to docs in their own PRs, -or ensure that the PR has a single, focused purpose. For example: +To simplify collaborative review, either isolate changes to docs in separate PRs or ensure that a PR has a single focused purpose. For example: - Create a single PR that adds a configuration option for an extension and updates related materials (how-to, reference) to explain the change. -- Create a single PR for related changes to a group of documents, some examples: -correcting the usage of a term, correcting a recurring error, or moving common content into a shared file. +- Create a single PR for related changes to a group of documents; some examples: +Correcting the usage of a term, correcting a recurring error, or moving common content into a shared file. - If there are extensive code changes and documentation changes, create a separate PR for the documentation changes and include the relationship in the issue description. -Pull requests that contain changes to documentation will have the `area/documentation` label added automatically. +GitHub automatically adds the `area/documentation` label to pull requests that contain changes to documentation files. + +For more information about managing pull requests, see link:https://github.com/quarkusio/quarkus/blob/main/COMMITTERS.adoc[Information for Quarkus Committers]. === Automatic style checking on the PR diff @@ -224,7 +272,7 @@ To ensure that your content gets approved, fix the linter errors, warnings, and We welcome your feedback on the Quarkus documentation style guidelines. -If you disagree with the Vale results, be sure to add the yellow PR label `needs-vale-rule-tweak`. +If you disagree with the Vale results, add the yellow PR label `needs-vale-rule-tweak`. == Previewing doc changes on the Quarkus website @@ -232,7 +280,5 @@ After your PR is merged to `main` and the branch is synchronized with the link:h [IMPORTANT] ==== -The `main` branch of the `quarkus` repository is synchronized daily at 1AM GMT so you will not be able to preview your changes on link:https://quarkus.io/version/main/guides/[Main branch (SNAPSHOT)] until after the next site refresh occurs. +The `main` branch of the `quarkus` repository is synchronized daily at 1 AM GMT, so you cannot preview your changes on the link:https://quarkus.io/version/main/guides/[Main branch (SNAPSHOT)] until after the next site refresh occurs. ==== - - From 56abdd6ce32d29996839594df46111e9d64a99a8 Mon Sep 17 00:00:00 2001 From: Ales Justin Date: Sun, 14 May 2023 14:51:01 +0200 Subject: [PATCH 307/333] Fix mutabillity. --- .../java/io/quarkus/grpc/runtime/stork/StorkGrpcChannel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/StorkGrpcChannel.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/StorkGrpcChannel.java index 97791c2190fea8..51e79954346477 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/StorkGrpcChannel.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/StorkGrpcChannel.java @@ -176,6 +176,7 @@ private Uni pickServerInstance(Service service, boolean measure return Uni.createFrom().item(list); } }) + .map(ArrayList::new) // make it mutable .invoke(list -> { // list should not be empty + sort by id list.sort(Comparator.comparing(ServiceInstance::getId)); From c3d364ae6b8224a2393a04bef2f4e5566a3ec4f3 Mon Sep 17 00:00:00 2001 From: David Salter Date: Sun, 14 May 2023 20:38:13 +0100 Subject: [PATCH 308/333] Fix typo in Azure Functions documentation --- docs/src/main/asciidoc/azure-functions-http.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/azure-functions-http.adoc b/docs/src/main/asciidoc/azure-functions-http.adoc index e3e7f498778d39..b6f281f3582370 100644 --- a/docs/src/main/asciidoc/azure-functions-http.adoc +++ b/docs/src/main/asciidoc/azure-functions-http.adoc @@ -106,7 +106,7 @@ runtime and the HTTP framework you are writing your microservices in. == Azure Deployment Descriptors Templates for Azure Functions deployment descriptors (`host.json`, `function.json`) are within -base Edit them as you need to. Rerun the build when you are ready. +the base directory of the project. Edit them as you need to. Rerun the build when you are ready. [#config-azure-paths] == Configuring Root Paths From b2990c72125ac0675274262975cc106d5ef07608 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 15 May 2023 09:52:47 +0200 Subject: [PATCH 309/333] Fix KubernetesWithFlywayInitTest test The KubernetesWithFlywayInitTest failure is related to https://github.com/quarkusio/quarkus/pull/33302 The job name is not flyway-init any longer but -flyway-init. --- .../kubernetes/KubernetesWithFlywayInitTest.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java index 328352faae67f1..cad12e46331152 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java @@ -26,10 +26,12 @@ public class KubernetesWithFlywayInitTest { + private static final String NAME = "kubernetes-with-flyway"; + @RegisterExtension static final QuarkusProdModeTest config = new QuarkusProdModeTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) - .setApplicationName("kubernetes-with-flyway-init") + .setApplicationName(NAME) .setApplicationVersion("0.1-SNAPSHOT") .setLogFileName("k8s.log") .setForcedDependencies(Arrays.asList( @@ -50,14 +52,14 @@ public void assertGeneratedResources() throws IOException { Optional deployment = kubernetesList.stream() .filter(d -> "Deployment".equals(d.getKind()) - && "kubernetes-with-flyway-init".equals(d.getMetadata().getName())) + && NAME.equals(d.getMetadata().getName())) .map(d -> (Deployment) d).findAny(); assertTrue(deployment.isPresent()); assertThat(deployment).satisfies(j -> j.isPresent()); assertThat(deployment.get()).satisfies(d -> { assertThat(d.getMetadata()).satisfies(m -> { - assertThat(m.getName()).isEqualTo("kubernetes-with-flyway-init"); + assertThat(m.getName()).isEqualTo(NAME); }); assertThat(d.getSpec()).satisfies(deploymentSpec -> { @@ -73,7 +75,8 @@ public void assertGeneratedResources() throws IOException { }); Optional job = kubernetesList.stream() - .filter(j -> "Job".equals(j.getKind()) && "flyway-init".equals(j.getMetadata().getName())).map(j -> (Job) j) + .filter(j -> "Job".equals(j.getKind()) && (NAME + "-flyway-init").equals(j.getMetadata().getName())) + .map(j -> (Job) j) .findAny(); assertTrue(job.isPresent()); @@ -84,7 +87,7 @@ public void assertGeneratedResources() throws IOException { assertThat(t.getSpec()).satisfies(podSpec -> { assertThat(podSpec.getRestartPolicy()).isEqualTo("OnFailure"); assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { - assertThat(container.getName()).isEqualTo("flyway-init"); + assertThat(container.getName()).isEqualTo(NAME + "-flyway-init"); assertThat(container.getEnv()).filteredOn(env -> "QUARKUS_FLYWAY_ENABLED".equals(env.getName())) .singleElement().satisfies(env -> { assertThat(env.getValue()).isEqualTo("true"); @@ -101,7 +104,7 @@ public void assertGeneratedResources() throws IOException { }); Optional roleBinding = kubernetesList.stream().filter( - r -> r instanceof RoleBinding && "kubernetes-with-flyway-init-view-jobs".equals(r.getMetadata().getName())) + r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName())) .map(r -> (RoleBinding) r).findFirst(); assertTrue(roleBinding.isPresent()); } From d6823237ac090b5538cc3a5c07ff4682b1619c9a Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Mon, 24 Apr 2023 15:46:40 +0200 Subject: [PATCH 310/333] Dev UI: Infinispan Client dev ui implementation --- .../asciidoc/images/dev-ui-infinispan.png | Bin 23073 -> 25619 bytes .../infinispan-client/deployment/pom.xml | 11 ++++ .../devconsole/InfinispanDevUiProcessor.java | 47 ++++++++++++++++++ .../devconsole/InfinispanJsonRPCService.java | 25 ++++++++++ 4 files changed, 83 insertions(+) create mode 100644 extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevUiProcessor.java create mode 100644 extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanJsonRPCService.java diff --git a/docs/src/main/asciidoc/images/dev-ui-infinispan.png b/docs/src/main/asciidoc/images/dev-ui-infinispan.png index 378347e23c317b1161109047d765355fd5b23307..c32e78996fcc8e0bd89ecefd0a3c8303729c136c 100644 GIT binary patch literal 25619 zcmd42Wmp_R&^3w^ERbLUf+WG+-QC^Y-F<;Y10=Y+ySuvt2=4AKi@S4|yn65b`~CQM zm}h&syIQJydZtdD`YkUjhJuKT2mt|sA|WoU2mt|U4ZaLNz=3n7;&n^Xu4Ll8 z***PEyp*G3{woLz&#F9M;1N3{C`aZ1^@A-%bA}pKy@wt_2Vn>IcyBMZJ-kW3GpeuR zA|qpQzP+sx)3IkDwm0(kpAH%f8rm^aGyZlbKDR%#wa*IgD*}v<%Q(`OR$DaFnt70$ z-)`@Cd<0l9r%+2c0&%c5KL5@KhC1+m|A^y^aY12L-kX?qg>PHuImd{w1;3T07>D#n zW=p(dVBVuC?~Fo^?HmMc3E+|9lw=7aPKYib6ipEK6(`Y$&nu!Z= zz*y*2V&24aN3)tMs_x|2F7-VCbaH0_U|GP-0qyYKQ z0g=0nwXGAUJJ0ujH8{cP_iVcFME|NdTk(8XmystDvU4;cVx^_0rT@;0NJK=$?PzSu zsVFS^pW@&+LBbo7jLjEpqk8Z=G-TW14z8e69y z|Ix^QwIgieWaMaJ?`&abOZ48ZfuWs?Gtc+$?;ZX3-+zqL#NFb5da`x;&uM`tNcUbs z$3RO@_up-UMY-Q|Iprg4}fPGyh+j_zyMz%LVJ17m=Ipzt@Zxab48z z2m*p1LPA(T*&XsE9X1I=3_S?xqg!zA>Z*i&FwC#kPi}8U#D2toeznH%|BU+i0gZ?q z2D~)vE4%FXAW(i{v&qe^t=-xE5x{l8qs4PAxwyFa^l->tRfL?F9}4`hAV*|0GarRX z4EdP|@-sb5Q5E1@6a+#PKkhgFz;sbrj8cjBYRU_3aN@o&;zZ$SSv|5p$UX)i2|9Uv*2&RKqSyo8UaRzL#zS-ca5 z(R74JqsBPKTBBCHKMaRNC%d~kBynr4K_|a7HzcY$1{g0Bx;5~mv{IkHxIlm!P7!>cE%8P= zgfp=-i8HCUtWL;-K5ZQhw#Hbro}!^Y#&p0rWUv@~hIhmZj5`_Fczf!OH-Fa8J51lkU3acJxO}P;ue!S3{)k zINInx{LRAevwZ!aKK^qySKP{_wDYBkhB)wqrdA*6vVm?s8Z6z_Crb1+J=r+$#EVng zx(m?ut{1eAetAdZQJ4+;)7?%SX#pD@|5kqu!Tm^j+{9;q@Y)?&CxolMNy@T<}oa{i6UGHoQIRldP)ouYBYxxUVJQkwVq8c(CzF}&?^Yg(~P-5l`7 zM93d-i^W~z&^;Xm0cYwJr z@NJ`Qcav(iCaYUh1&WonGK-yWzS0?tLaLyElMb<*`%1XMc8yTMQ8IEOxKBe$?Tu@ z#r(J|MX^OcMNy89TJoBihT>?y-RG0}-O!sXNe^c5w0gZ*bPCf(Syi@Z^S(IxhT*VD zz`b6lUZ1>#%?k}=ff8M8_g(PRV)!-5&>B76QaFAVDJ-0)j_FkidpLurA=s^Mp%$ww zYU3u-P0Ik9w_PBIopn>G7UVdbs}HDVuLkz^(=dAd`C^fU z8fh6%HMHIwCO=aoY9L{va&URyluHg=AdU}t)AN7wWZjU+SS`Mktg)Ecin zi`2M)K2m5f&LsyX%Zao9$OJyLFTNFkDxK}bUhUI2+RAksbuCp(zt>yMMzz$Jwt`^Y zM%30{$@@aEqQ~$xhsj+uXf!%{b|6*NT+a;0&{s6{yj}vVmuhCMdz;zqH=;1;bfqhr zFB#pE)4ut|k)HRw5yWF9vR-H+pT@JTJuHA4#LL2P1KUkcJaW4tzVet-3woXptCb~G zqrb)$X1en))!W+YZe|~psb{*RFR_I6WN?^eP-`_v;*GT6iJ)0`{Iz=tk}3FDud>Pi zAmmN(T-YTUh(W2|wS%H}_jeL~C!q0ByyF>Qe3#K~+6jdJIOIv_O)(Hj$aZ+a<~^lc ztXp2wHrtqJJSVF9cWSqEXIE`qBF=U&2J>=%BKmRz$a7;bv6ff+a2mF3ii&QS8SQ>_NC(`^_B@7GS11^4u) z^Vmw0o;-_FU>AOP1Dp1=kZtD;XDva zmtBH_kk1@kUydnYfe?DMW;*f&MJUUw{lsbKFGFsb&goCLoq;pnXg=?UIp+TE9yiJ_ z$9z_|-1Wpr!(?Zj-`<`da=o5kK}+_kg69*5d>hzqpI+dR8HXNkQ!8V?1$0F7!QZi= z5)<hFSz4YD(cO%z;EDF5ZM1G;k{4*5C5ht2BJ`~h^ z&cm&{{Z=Cn9PZ~)Upd{`2lpu{9&FcYM&sZ+8(c8E>M#EMxWmm~>983_I+}h8Bnx+m zsIW8t1A6IEA{|X~h+WOhsciHV`Z_ynV~q^pPxSJuDUdWU0$MvHEy#-3NIkQ;)bg0xH_FE3wVZ+5)2Yq`?Z zu{#`}&>I{%bX1RL+?|upsM5NUJpi|}P<=&#MCe z7+N?eh3(#kDyRKH6SGw%JJ1SVK-xt@aHGrEvA#^_)!uM{)>qu!^8kLxyQa2SXLhr1 zOTeLoOWVN&8Xd*C@^$j%dIk(Tl!N)_%|gA_C)VW`pv=|IAh4U%>(6KLp-iVhYiU!Y zIacJHXWjPoYYi0cC`My@n=#Ptmm0J2O(#9u_s_Mc_w`3AO=_{)SbjMjzB8Upxv4Qe zM4Se}B%D7H`jCjtZgu8(nz_N=S(nu$-7qFh6#dnne4f3qPP zr6BaV4Yx37!i(Lk)J~ovO^h$qNocL4-n8V*jy}8E89h0D*rKAD+aFIHZ^wCQ9>9g^J?S{U^ zT3Pk2_TizdhA@qxc!w8Y6+{o6f0up>*L+u@wF2sVS)2WlXBi(7Ln4KxVoOWdEpTFl zoNpYD|GeFBK{MeFbvgn)LUaZ`i{;m5$EWzF7*r+AGxs!EN?PuE@OsSjQ0Q+e3XOtb za?7fjTF7$nNFMcl-g+w?=keKEuC(ino7*lQpA<`sFNkl>A%6V5RIyqCI0nId7ucv+ zwnHpIIy3}Md*AkTC=$Iru#Jk(pqj`VJbhcuvbDKNzW(JiFxXF{l_xqo)bBzs%@^ah z(TiD_#WtYVE1C>hY-zp@GFeJToug@i<-Iw0A+;#SogHEH@aKLOrhA#lYdOU9ukfr= z&%Tbm94x6N7<+Inr8rR3;w-q+2@9agp$q&DVh zi$M(vW{Ge1>>0Hi4^js*Nni%r;N_$ZB&4aDC|->c?Q8xWGm)>-_kK8tG=q z+2}?4@Tj`JdR2hL>o+j3usf8HK-cjgpr0;uezSE`(pH8Cxr)hma2L|{vR2ri6QqI? z_?qMmQL9)k`ScC#_NE^JB!r199}xXXN6+u{^HY}q@0X3kY|k_LSF6;@`p1)+MyCLU z*ENswMBWdtF&vMO$OM*XUwy`xE8J=?MuztetwPqA?%faAPKLvSmXeabP+IF%HSubE za5i7=LEf41*&ynsQ(r9Hz7W=AEMjq+Q_FTmFK`DAU=Z?c1P75k#Ud|t#Zg@p&Uai& zmx4A^*YF#~5HbGJY1FppE}vBE|C(Q`Y&YVq4dQ|+yFFf{1Jn&5VbsxeSLF{x5G4AS zJbz~}J}g!$G=7R;=deG|diX<-2_zG1W?y4B30R}f$lQ41yyXW)leMt>#@t_e!F}&#|&jSo#pa*cpU5`fSRH&`ZV5f$- zaX<4seR?Y4%7SMq(V;y_3D(Plc{Na5zl<5wB#vO4sUCn24bLOgZ6h5w<`bwvjHBB_MatN_c^O4VvecTfHMkUgI zH+}ugN%#Zt%gusrzQ0>ys9+13fWJxn-L9IH^hM;-97hI;yx$RkO==!r`{?(6bK?1H zfGGrx%bxCK<~;Uuq3@aBWr)mftqm}ftr1(jlD7<0esaBAZ~Pu)RaCZm>cQfP;_CNJ zOz-xba0CRV>yCOY@w@H!AryS`e_e@~iA14XW2X)p#0pliE1cFQY{wwKt+zBV49jt}GjZMPVH4km3(BSGG{P&xGP2GDPeZ~HS*0a2!ldd{Ai}^v*T-Q!DvEqEq2Nat%ai8(R}f@b>wHA8t_pN z$!8yIJd*USh}&VC)TA?uQLax|-jVNxRhqNWj>5%CMW!$1r`mOjOX|+}rcs__V2z{7 zU4%Ci9;;*9u`A5^^fHxs6ONmO1RO-x~)+`0n-cX1NXlD+u zBIiuS-BQ71L}qKP>%9%!Y90K3ZHh!bx)dsutQto(j-zn7WmIa^ zWZ_4%aXo>E#8Mr_=@KOr*5t(Gpi2`eiD1@5@mDVZ3X(dqPjMfc<*~6-ZUVV3b3G1; z)`Qv2jm!FLfaCq&NnZc~kLyYv4}N7uh_U=ab{u|{<0r`~w=+m3#Q=}`Ji`aZQ$@Ru zidIkFg=8?86BGC1Kgw60&xJ#1S5aw1CMzm33!V$z-I@ciX4d3RS1rpIE0H;>2X;ER z1}-g$m$F;lUj(@ugRkvqDbx5@v^GaAd~CRc)5gZP2T-O1ROw^C6>aQFvt!HMXgXIS z2A!rb7M&J}eX{Z-Ssvy+{k+}Ecd3L*Mr^G;X@-~|p00yYo6lAk&mzOSNtCT+8ns1% zExHM`dNiIh`^ju_wpX~}ZBgSn`BWrI1!0cQI}#c#hT&z}?YfwU3>3=rotn14b)E)p za)LzsIlsx=9&5LK@CZ%fmvr}4v)%bv|% zS9h;vv)S~1E-1Voke)UPtx@G3ShJM%IswzZ=F~I}f9aPtVP&y6eskgE4E~A z2iY#zyAn)2mp=?Ysl*})=BdZN;6w9lKMO=I5)L^Wv1nDCag7#;`&q2m&QDir(iKy%n;9Lt)<7Du#=HS-=O88otP)P^4+Z5k zoKm#X9R;hIQpxB17?7?B<Sn}Pwx(`hzk zDVJ>2v;+*IVqUJGvfUnBC`s!$7;WzbuGp)nIq4p|DAqf|sxX2^Sf59eJO6ya;DQu(9$r1w-k$RkV-4mXKZR#cRod3hZy5kzcyke2q8JRx_W z3!JK9+0-cl^O?GOJU^qGF3NLvGSV71*ls!9MnoiIhEU zWY=>k(`5yDIq`Bv7F^cd;%E8)2R)OLSfO`!hNtUP$UTjq2CrgbJ>QBD3hoY z`FI!WY(EEvl2OKup0MdsRH>UCjH2=#9Y2KT3xTc>Kd{NnN0iSNYEPh%k&=F9c=?1_@%(t%LEL3+yNVk4i_>e6hUQ!Cc#w$oTwi|6TY&%QNh)Ky@u!bfp* zO88VedJ`dyTmv|3g}co+*{BDkM2YDj7vji{n=Tbt6BNA-l~vAzmX*hbT8&7DFb+1i zb=MPEJJJR(yJ`OY50wMvzo2@l=+5-Vf*ZY#TlgOqon1XN3B%9}N0@CJwQLUzkm1`z z-`L%Yq%&B_CIc`KQRbDd3Q%Aw;vYg22J?z*ch7wKhh*kmnKWM6fE5aJO&7z7G&Ew7 z_|sNfe7VS}%ta*CeIW}u;Wf8ztIg<~rUj!ZW^pWQic3X&geSOCYGt#YMAetw%>gyE zh29olkmP4Tvq!K4I8InY^(36bCu}+v)aKf$b6NZf=0&}|sHl~xm1m@|m%B_@jHiCp z=!F)!rY!X_=$$}AzBY!}f;+M@EpBBimCC$39e;j<@!8-2w#9;Z^stPnd}ge&4kAL; zz%vZ>38A_%G|zEDZfEhJ$P{w|R(4uD|3=ppzHPE`deph#v-Z9=ly5X^oLOa)q>h2Q zy|6PVp^eBZlpj+W{SbYjO6tcHa{nK6K29VuPoDsrAg{2PLP8};a_ zYOSh?Nk~YlY*RV~C^=E#mXQ4CO9+03-L827XD=nT`1!v(Aiqxa*X~5gbNp1?%a;MA zsyb`AF^382a|j+Y2x{cEL+)qATnde&-0xnb;Do?Rtx1K!^l6rm&V!VGhN!p3*c45UJ^|>1KHE$QtGQebfGc0-9r9 zc0G1vU`BexK4_^uOjBxNllPwgNF%f6-Y!-d#aSYp4z}qH07si#yPEX{*)t9{+cLze z<#RM1gb(OV)CA3Us(|y1)bL3^wM+1aiR6h>yWB0leLaQy9wf*kvPIVNuwA)R)@87) zyv3kfyP_~J?xF~J~w3F(s@0CYuB zpaYfeZN9?YhfJk(N^CeBnXl3Vol;rQ;>vUvV@Y|6!>*51&-)6DWl|E*}$D=3c41jmS(NN zbW2w4ljmRdx~QqP0J07)hj(8oz-+btZ|E^8j*#!Z-?@m6eUwnA(l^Ac0FaZ5g?Q8Z@uEnJ)vELq2(3-z4g1&WhJ2M z`owj=Lu3|qOYT_9z4&tlXrdr6JBUpK)KLQpNZvYuzO(oSr02?aFDnhh=R)UGhn}5G z7R%dH5;0ijoimpXP(c@;FoKHF!TZ8P63ptcX($|ktgH4d1DjcCH9%>3CS#O6;o2=96L)^9VJwmCs)7frG`A$@h=Y8D$2y2dD&H}5MH)dx06+ycR9 z{c(jV04WJy6WzqEZ;(aP$a!zIGiYV*gi!3EVRJzv(ON?%eDF3+I+I85HI;RqxZ4V# z>+<74k9!`fvH$Ud{j{;EyRU{@%jB$Z`}#0BHF znK#ajfQ9=QBgkNbps#6#;4t z&l2l4g}n&7iJ#`O{M?C_p5c<@G4Nu#QfRiXaX9(f|A9u!^@~Y(kaQD9xqv@ylX=^f zvNJ2Rm2Y=RW#I{vsy(5Rj^dyB`iNjSj}2e3A}F^{D~0E?T!iQLhy2z?W~Xj-RU zCmdUKwCBIC79^%uZ2cFUD&jPj{7>R0mwd10;Pq0~q{|vrU;Uti(+1ndmhnb)rNxaw zu^1n-<4(#28-^DQ>&4UkPb2gVUS7v+Cb=V>*d30jbCnh3+P-y@g`GhW9XMLRhDS8$ z(am)$YL)3AeKSwua=x!h9w@C>nQPKbu$Uj%K_i!JipsK|?6QGHO>Ra?SP)@a?Hs=$ zRTjWIIbr#E3oB=0(}=Gex-#_Ybd`VYT{Fa~`(&jxA8|!c^`svHFDYSD>lb<7dFT`t zkBYtZYce*>ZaJBC;o_)s;Fw>_RRfko0GoYLTW#y7LX_T&hTm|C5=qDzfUod(NG5O_ZEX*v|Ll0DI~ zS55N6P`OTR2)_jWe81SWcDD?p7TtICX>*p~ndw8s6CWfh7$9I$1mTr_<>0&;JJRK0 zO+k&jXoD?~u`+M9HLcwsZ9u-lSz@{;V(q&-QgI=5&nBAW;+V(6!=ol|%Um|zN`#I> z2|pqpNq7b^yV_VqylbX;#+HKrLKBCc9VK!9Hp}N}vfQLs_$)_uBw-N46g_ z(Embmtr>{4y-cfIl~sMep(!9lh&wQ61wEH?L)+=&XwMJ5<}2VcXy+5jIFG-^dy(!} zXa#}dET48C@+E2;@_H?QCG_Y~UsC#H6;7~j4MgJ5 z_u#f!CNM)Dm|FVimtinTTbxq`3G3Z~S8+-?ZAQnq+IueW8?%lz_bDBC;ukn6;rYvW z7SAk@vitYjD5J!r;(CjS(e%m4Daa~h&XKA^_vHK&iV2{aU(xqj8+lwh-O@S;<0WX+GG2^$8F6vTe7)rs5y^#clv<;VaJS79FP z>~986i-$JOt0uBC>byjmc>|UB-P(q^8X0t+%J>M{11H|xLtM44+eodGlLR@|*RMpU z(IVl7wK<$5NnVl$I$R#voz1X*?fXng1eIlUkc z7JpexYenEN^}}$hG8g!_vjrx-pD8vV5Qhjbf`H4Q<6asGyV>l8C(!{vH@g#;0IpMpWupC+0Vjg_8E)XTAS$~03@hQzOP^an*zzUC$>qTb?(N9% zat%oRd0lDk_Nwut3moA}VME936xn=(j>FPN2iCQ=_O8eX!Ue6@37iIA*CT4KSuny% zqMyAGgtYIWcjcRg`&k-WnE%#ID0fYK+xq(4jk;;kYt>%j{qgQN3Jj%e^p;H1h1vh^ zR?*c6MIh@pR(|tRK#aHCuw99~qST%CBX2nAqQ?l;Ic==QL1yy@I&+)dN}CR4sV6#W z#q1*xYANZwAW7LI+);aA>j?3fnk@}5!;$pFk;bTKxQ;ZgEE(6s4Mtz5SRlP-;7C4T z6D--5ZItR>t7j9V`W4y#CLYhG$Nns?3+0Zh_iYZEQ&;ldAimn;(Gx`60GdP!Wk zYK^=@8{=eHsRWQZ)YzVIW60 z3j_q3PBJ(Rm*z*(G1}mUcJ1JXwCxAB7P0dNHHtLlt688_2%ER*AH6hNYwI>f^C~Dn#woH|!z}3qV76(lv;j*RT9S z)Q<@F$3nR?Aqd$?W9Ml$L=5mHs=v4@@_-##eZ4_N2_1h)FWn!(8tDyK1*vYMbAo}| zufW+~d&ORb60oL!PD5_@EBu=U*4T@|2nJpgW~u)ul7*Y4df7whtMp9KFUV*#=ug{9 zL^V833t6SvFhzY)6~BrPF<=q`m#sTL>PQ8i5cfV@3lpheu$HNYy#_+AS5AHUOJ8u# z8lHIoK=<(fh{2B8EdqqCss}7CIDOfrF8p}AZPC2woFq61fP?5GHz#4iP^=PSFkFGx zb3tUSvH=e?-KFuWc8`{Ez%QHf;rlmpxO-po*kHUk{8U>NiTR=Lxa0!Wr5 zh_Vcqo~DoII!1crmW4!`ZAwtEwkWKXO1Sh`igo5&5Cg0!@cnz|WRTE5ewnkSl#4=YZ&d|YjyQn6nv6gyD!wY$cQdW zwxA&^L8S9p95WEVbTfZr_)pixn8HLE)Rh?S;R~!RN`7ZUr(+dLc*vN8-=vVC+KTQp z+e-7ajaNzcqHX#haws9D|9Z*>5 zQq3e~%JVxMrd=tL_&Qs^+&4_}`fcZfon0hRl)TnYOI-7xkkLaBAlY6ds{E8}gmRdC z%R9Y*HdmMsbw3}?z5!U2F<`=rWUIkc9!@(KMA9mnwGaN_m8M3$T4;3sy+x`VR~up8 zcJj83hEDVabyOd!5iT{+3_r#b$CinF7mWQNcpd!;^FRI7`NF~29BSxIjOGHP*w<5` z*BbO2fn6fN;FJd&*(p}EWTEE7OIyq?&mSQz`T(%po0fiN!)GS%ucDkn8daXg{^$Tym zvzGA7yoIm&eM72wnP6Kr3|3>&Y5O zcL3+JxZV365CZ6W=EIezGf%JbebITI2>iG$#onh`ppI?5p{xgKo&LbN4}7SvoXmhX zs;@kEM_EFk&}#WvXEFH)U-QFL_LvU}p%w3S*8ly9Hxrj3ELYvvp+EAMibrqihtbrx@9pfFoO|LGAF$vc=M|KS$)axWh2OD+cM`1;s#jEHTY0qFHMw5Y=^7@10%yxC`h=r`* zHUy8Dc0}iELvOOcoRMk2d^1-3va{4Yw%k_935&Ndr!|ggx9hGI94t%9$0ItSk0-sM zx`R(nXSYi>(DGID4W||>iB2VzwSvmLYpvF<^Fa~wL9JYymm|c0fEXgR)?E^TqycgF62)? zR=lA|D6xs!DVTMQmJcIa?9c1YtSf6iG9QP<4I{hlrf5`}!A-Ns*eAZ0mP`^#d2Sv9 zCWuCI-}oOdR5M9f8ybZUMrA&cS?HaDxzA`o7=4v9UT4Nx%AnvsgF zJ+dV={Wv#`XVJN0U}`RnVzHyhV40t1Ks1=JJ0uqn{I|h|YiDqPAj$>AHgx_ecej3R z9LW$KF6PYpUL-d*GYbCn5Qp5N#AdzF&q}RAm#WqEEE>^7N@|ueK7g}9xwJ~GyHGYg zW|vBJ7|ff=SA3{ZEH(s``(Q;X7dJK;A38oU+!AC7CiGtSSRifS4?;-#Bfn zkNLc`Pfyq6#{q`(eVe&ty8}MhAw}~$A*!?a?wc#R6vSz~ROoxt_?@9Rc09$F3e%JJ?u3R0<-MGs^i zk_h=cDIj7}Zm^ZF-rss=zEgsB3P2TL29?v_F&&2*=RQgfz>%@Mq|Itexa0F5hg6*z z88Yb1++_fb55kO-hZy+FCV6P@(d6Ea6(Dq-B6^_l{Qj}co`47n>YnyVku=R95pe zXNOMPmg-uR=PH(oYE8%E!6Y=v{f{=x;iuMM`#w4KkliDhc@$VbZ^GEx?kZN-bc zMswQ9*Xzz;(q;Pd#T}m`CGXm?&moCSTZ`|U-Gcco$XkQ#ZdtH6hGeHcF(dxwh_lA= zNVyI9#Vl?lgGtYEcZA|^C2%>xlxdBs>Qc8`#)Q~`Vgc|_UJGu@KB6z(VaFy#$9i2I>WuAKaX0eb}p}qLRIGDoX zvBI8lBNJ|N4R=A}LWvo>=2jo;1~1dhZ;+`NxO6r!Xo;{N2U?nqbMwyFqQXmVHm+T~ zJ!h%#EV6%1n`#n8NN#ox?Z6>3*WXVGL&Ew@GD821!2P`=d+E_CO0Ydr=$9lLhW9Mz7XZFUZ-3*hAx zFPQ@Y&omk>ikgjb+VPR)$@CQCm**_xnu-|;@jSgavQD)vcJ-;&OOm{^q5&@_sK{2SK}rldcU}(ePn)taKU&@Ju4=zA)r0if z+8o_H2a^~ADyc^vU6yZ?Es_l%Rt|^yTAt^v8jc+*oyM}H448l2<;n%7)Z46b8|Ir1 zrfBebzsVS_uGpW%Pc;n3GlHWRwr{%_Z43ItL9LicjUg;$dSbeOL!8=Tfp*Tz3G{JX z6Zz(tVgK4EYNd6yo$eFwuo1uCK{}U?K_;R;Gs;1}A?DKTHQ^_$AhB4)UU-+B7z}acsN?GOb>%LQ!{{ zMuo&iL18frdu@i}=}f5W+9n}q zAo$3@fRi8JqLrmi6o`l!2WHM5LYpL17eo^9d|$4A+U^ec4Rptxk=>nNpWMAp0xJB#bj{T@)RqE)b zNruS?g(@@_#N}H}amv_+a-B+0_i^TEuiY1EHJ))VCa#t!6GgV4bQJ1UzR!&3NZX+F z;#ZaX2_$Smj}umOi& zI@C~)%NZQSMpcx>aveCX1{<3gG8Q>4tOCI04#P3{rIIC8UOiMwOx*OGwOC^!SH~k3 zaastBQ)SPAi$B~S&}=?~_ZCL;uBaMaT%S}zDc+{!-K1f& zX?;mQ=hwnRA|W`S8LkvVB+%eDnaHb>$>VUP&(PD0ZtSrp-G?&ICLyqcQb+$mWsY|= zO3SuQ^>WWer^~&Z&wV3{m;SO;&pj4=3iE9xJ&Xug@%%a`p_2Ru|XOvyZHzj2#Jdp%%l$Kn_twy>)li{%+@4_V&3 z)8X8N!2OQU{K*gXQpIg!RgW}D;l0mbWdc~8#G!llrI%;uZADu*d-Tm z1C8H4QOEXS1s%V>@B?w@W0HZ5TqeVEQyA~-Gv4-RhqJF+(l;e4#uqiaN4?%s8)~B4 zc({n2_~*3>Rp$=#6)LOhHFRw&Y7^nM@!nvNPztzv>q&@d)&5A?09;nBQH&>xvz(Vb zSsm;doKF8e#(mkf>d*Si%(LC{`Pv;qHjP^)18~8z9pu-FFr`BH)d!W3Lg*E@!j1xD zGEwQ1&q?@Si=F4y)_zepJF9eo3q`D%eZYd0N25(JMR6vTN%KL9?TA^qN}(gI)gF z8>349h%QaM6m|@mZPOpK$?n5ro41V{M(4Ao$?NgaWTr%|Lw3~5pSM_knIEz2 z9T8e8PF5Sooei#c2L^&!?PIfoB^@OkcM(Xr>hO$zZ}p7^{84+!YP#qW54q>G8*39B zg~=mGf-`X_r;o5cVf)g#~ix{)uda; zEF6K$g}`D)eIe75trefkl>>xz%`FNFb@jWkdFk#rIyxgAOQ0#OT#acXxIs`RyUv-6 z#)#1lYQ&GFlB1_kYY1!n{<&PLq(+pqJFQXvasbb_^D3=q3;PgC^lxvL6nJm4#Is_M z9(|JeaVrwu(|=xsy^U7<9y2*p3vCX*u}d_YPo)6WP2+l;iFHuKyQN zuK8bq=H+Nvf$6Ga7$9)`x&0&<^XNFBgM@mAD5$_F$MjILArBZ*p!ERb71%E%{19Ly z!y*t2hG=pqm63tNmqp}=WXP?BIUGben-9iuA;BPsb{tHOu0f?79lg0x7BfJg{RcL<0`cZ0wXloVu0#|Y^z z=~ko}9TFo)Ghi?}-g`XH`yaf&z0Yy%mmRyeOz0+g&{|q@(!LBn2yln+|KPGj%e|_=v~!(gUt4tl{@b_mnb4g_ zH#c^U^3bi3P>wcB+SIXrpR=cE*ciP6=b`FNhh{naxzEs%awl6fI`LVmm-`{aHRvB)=u zz9)41yIx_~V&evFytvM{K`!QkEpz;*`ceBK(*Cp}T@bQN?Gj>KsKwAlOKkmt(DY&H zTyE3S-|#o>fv3nh^43_up=;}w@F94-bgog^TZ$CKOav-U_$sgD`L}>nt1Ls60TA?_ zvp5~DJ4^f&V5=nk*(d5>cfCy>66viPX2a-_J~=~4=%x{ zmSdtZS3`>|=oQzp?vIgfh;oQMwv3y>_kw^dyb*3E^=YN9hw%zY|IgK``EB>@bVw2A zYS=D{N2mW0B`zu=kif`v)_DE0=HhbDs=%-@n37MYqsg9B3l4n~$GI-Ul(#Z!B?!cs zcXdBv_G5B;CU$0&lkq?X+ZA~}B)H=fnv)uVl1)C%=$Z&x34|TF7GF%LKQM)vC_-%# zx;3Nb*nN&sBE;e=EF>R7IODkTF_-BTFLd+Ee0mT6p`}|TK`S-6{~A4ZHszBLPRgEU z5|BfE|N6^ZTftBinT1b+iv0?3K8YE(oqUTajQjFLoi(o|?9itf@DV3E#Bs%oTwxN@ z{oi5T%SZEd-bLK{H5v`Jdvl8{T{iUHS?E#EbStIbsjKb#Ze=ZJgnJ=aD|%y-xI?o} z>Gs55Zk9SO#4QG&69((){IrT<8eRjoNu%)ryQoRMR9(#aPxFKrz3Mck{mra2NuScg zrkzjA5bSZ+Ic&;LiMflB;mK`shIkV~hy|s%FmjYOC>_H8vLW@`(RcTj%{ay7Np)ap zdc}TkoOtG})YLrhD>#&km5nW?*r;_Fzpf-tCohhza1UHG>ELAeuf#yGIwcZwHY{G2 zXs);3u22Hfj;xfQeno&zGU`p>*g;e$%0H5VTzu6}6EtsV2)x_lC==XRJE-Xyls8tY zKF~~zqOALHH(%RiJymW8kuTt-CTOo^l9kM-LE@cE_*H`K&!GMx-0f<082U{FUvE*z zK+y5SJ4tbmw5UG1H_djEsle)f0_2~GX8?7?l7vzf&s_*SvwLQ~U7C?Qc?{NdYU1mAf*xP5O}pX7P$Cjui;iXUxqN^RG3uqJ@ ziUuEkIBvfvt(!|^)d$7}Q`H&|^nAB|eC`&`)_I5GA;ZTr5r8-hXfpx z7CvNeJ@>g;SJa(Zuu8yIM}n5_{88*i6}}@?DDGYT%(*KtP?sKCG98MAfha(5X(E5i ztH$_xA0#{YeM=~Q>TpZ&Il^m)Dwy*fRS*dkRMOEGd_3zMV`V~nda;EA*fNWcGV-64 z7tho*$V#5981|H2)_t>~X_pHA9_!zw@Th7Ydsqol|~vBW*4n>N?m`Cd7C2q4D$s%#mt(QEEkh5yHt!Z_S?eLhv7WyZE zEILt3dF==mCRYKrpE6J9`)HTDSuVzb>2}ohdBZa+#|+!q0gs-wnuWw zkY}zE47l5AD+BAfx6PNeG~oRU#Gg}eBt)9-G%UAs%Hf%~+_~sHNqk&D&3%?=Idax! zLLShEhl+bKT{e^O8IM+!s?5^B_be(TV_?!%JOOwBaEd9aygA5V14?c->g2Cg8OMcF zdV5C2aSAML-jMZFFkE2rW3oy4$AV%5ukm?0_6E1xg^b*L#`VfO(YH@8RqB>Q(-vCNm2*<+()FZklCg)3mEVTDo?VH=fp>;Axb zO28Mv#2o|gtBnu|5!WPaBS(Y+1T)KZtzr{egRi-=x|D_4@njN=fvT=uFEvDP}06KC`!GM_lQtp~-n1m*H990#3_Fa3f^8 zzn;zDoU1tvEmmC2vz#Hndb;v$cjd^ibXkv&nmCX39u77dWxoo}#;{ zK;CRZD@p8(Zm*;0ggk!w0(>dd5KR@LXtj+gD`;XxAv5-`!4edcC`FV zzz!mlYkG%nA$zSs3Ay6-CJ-OLo_>W*q|)T%C3brT&X3E89h>UA_l5k{L-`$onr)9A z=uUTK$+Q|@efUNl|5lKY`L~(A^cXDKjIH|@ovZQ~3%)mIV`EW>-mmCKJzN@|b+iGsNe3c$v)_Is?O6;=6r7^TRDdP3X>Z zHc4~j8>En^Q&TVs#BxB*eP7nvQLQx|SW&--yx=+V0tv}LM9I+qTVy7ivK{{c)^%f1DJ758e0Ri6VE%@L1j+u5~D@T5T_+>*LC7kC0 z(I$|*s9g!RZFn1mHdo#+Pi{S1vA6#`U=y#)#m*Ia?4=#W3xYMLc2INK=yY|Z$8S`v zy#Z|eKuT})-vM=^$bG$WC>z^>=I zm1wj#zp(CgGb{KL_uf3sLoOnFAre>Gd8@$OE~iH!B+zNJyu@_?CB>>aU64+MVpwNf(oekYTD zmANyUI?_v$Xy@wk_CSs1^tazv$-<>&@%B-&=U=OwbbJZxMgB1iFMq~tN7Ru@3FAYo zxjbM-9xBl&L`Zt{PF|$83=%}V7&(l$ge8Der{>>hrk6$b=mmObF7AV=NaOMoUWDo z<1yI(dzLe{=G#n4FDN^->y1gqtR5q}Og6rl2G4rUQL#no45WaNCjitXW#{DLvWoAv z&GqhtO@#&jWy_+0*YTe+e8wd1QHMak`6~sU-Gg|aEox=+=I@T^{k6+T^Kd=^AH;RG z?_Q4x&ejHxG;ogY-`xF$pcOGsJnPO=NsoEL0iqt>3#;67%Gb^+Jkhzw~S0W#+q4 zZu{tw?0yX+mQD9n1zgCzeRAT}lf)9DUS9r0pn#9+O0zq18na#OK_^}^!TQOG)rtiS z!mWsKyIKLbNc)#SRwAdEu__DT>|Y z+)p)mC*Lxi=G}n>!8Ndv{s?+Ws2wQzC>97m%d?*;qbo5!3 zp79s{Cd|bYMN^86I1>O{y>Z?CENHA})HBjut#ksmr|v=taHjsY0}u5JLEw zMfZ9Yzkst1Z*<$=*Y9;xkJ6q$bacCScn`wQ&%YYtLq!Ok0gsrg?mH%b%2jDcrWS(t z1Kw(MSR)Ci=RGtJ5_omt2V`N05iuTZ$C(Ovx(lW{stmJU9W9n{t%)pAK}?)-{+aot z=>Y8ooi8)J#x9~_xawx6e|{L&vsp7HYK@aCq?!=r((|q=VGEoT*`W^Hc2C|}&M#)O zET-MWev)@&jrgJu!;=0}626F~0i`p!0`ej_AQTWJzY|$d-=HA>_sL7#|MkKD`T#qT z{x=T(*9ZUKjst}d{P{Zi59UUKI0p?AvLE;B75UaA@6iIw>ERstXt54?`mlO`SMY?0t zTV41r9|zf#LF3WYTOx{o(dPJl!(c!Y$b+Y{Yg+aT6xZ0zVWBGVo7$YRc;XX48V&r2 z!fGVr`vQ&f4;$i#;aLO7FLY#f(j|>|VqH}GK=*kb{&#^2O{U~+@s+x!Q zErIrHgx&%@q~S4Qa<|cmNgn6SQRB!oPvIo?X z?p4TJ(*SBeELh-XCw+kbvwE06j`GOmPhFA0=qY;D$Ld&1BNwyQd#WA&}aTFFEr|3(Q#;juL)-A0Y%Pd*n_14Snkf=+t*TFT`I}EA$J)SW0`$wibtB?8gel7 z1$WB6ufsO*=gmEFa&IfA)ZRJM6MyWwswA(Uz^ngmZ;=#2i7k{4#mAXh53M}O$-2E? z{ZNG6Acjc>peXgl@t99UF&w8Sp}D$b6K{U)70~|m+uM~?wsVGNfM#J4xuuvxY(BRo z3RE+{#EoXV7a94zV1#^z!Z*kzhgCCINkh5S<}1n9|FEc?m;&WXBpqg4i7Ra^Khq=fP|m^96l(LH_50V|4*jV z0=FQ$yr>7TZSc~T498F%)e{ zp_0N!AMq(5uNjWDft7{ysQn{Emw!8qrJ|e&|08f+1lJQB6 zud;lApPp$5v&KB(w0F17z#~#Lor$oQ_6R3_UmX2)bl(Mi=sejZsl83hIz@hPBbzCJ zB_5QY-RrW5tJEH%73qM_xWTQQ)L-Ko!(=3m<^AlD9`OkO1@~TSNd<&p zaKJD5woSRQ8MvVY(dGJueoo|$p5y4hOWa33FM}c@GT!fu>2sL$g0?EiV}(07t;B`6 zumb!`+>OxB+6^8yRF$sb-sdvUQ>baTn_!=0DAY{FJ<5ZD|;%hrC z*jyF#r2zm(kMuwkP?R|Q(p;BsoKyKCc9lUX{LGEhfZMQwKt^%KO)uoogHgQt+>3Vx z^WSf=ER2QZHyU-09av&=T$>NPc3R->@KfN69xvt9e!&X_ScLGCn_LN3*h8Et2qE@V@E=}Q9ai8tmF{|6bPFDC#1 literal 23073 zcmdqI19xRXw>BEv?%1|%+jctc*tXfRZFg*=lXPs`wv)T_p7WjWjC=pV-D9k=Yp<%w zs#SYFbJmJbl$U^q!G-|>0)m&46jcTS0;UICGEfkJI~E>B6@VW`mLejGQX(RRijH<> zmcLAafGB_4#P`SuE1`|_d_pM&;|3CA$(Y(7(}?QH1CltQC=;QCCuvd!!Ab4}pdyRCCn zQlQKXi-Wc|AEnflmfj=IeK4;uJdPBz-J4wN%CTXtAwwQ%3k6bBX&hMqS>m3*XL{sd zmi?_=NdIa|W0ARNPZc$71SH`;=r0nLVmTTukqp;|K&k5?N?Bkb1gH*W9#j(SJDk5j-Ral)M;zzTR4OCj!dP&2mEpOAT4Z(uS!rizMibSMCytD9;{naRlkQ3Kjg zK)_*^Kwy9tFyO)lTtGme386rcfbZ|IqIn?yrx%z$5A=W9GT#G*R79kt0N*Mmj;5xz zP8N2~VWgD_JYze#|2-MIlx8&hW^LU)^Awocsc ze8m45!3}7C-)0~t{Lc_)Yd&I4IYmMdJ4aJOc6vs7Mq+*#LPA1b$Dd~0%A(@`O%C|Q zM{MEjY|qWW;O6E=@5VxJ=V;Er#KpzMz{t$N%uELuLFeRQ>ulssXX`}rUrhcFA5l{$ z6GuyXXG=Rf%Kz2#|E5%PGIbQO zvjK>7=KsGl^WVh(yYs&Zc^SSf|6iW?FE{_^F2K+HFuV-^GiUrT_w!#VKtO^(Qldht z?!Xs%kXdL$bJbB`z`qFzPr^}1&3{)JtD}7cVZ~<&iNR4m=jWA4LLjROCKM1BNTLCc z3OP=49sLWb)xUaR=HTey@p4G1Y~Q;0cki6B#9~)b>D3-+tVtO`3f&-GfGx!y=uf!K z3Kkd`h@w|K9ehYgnyk1_noStrj9j;i;5YiuZnvJA)rKQTw~LSviWCEQg2om4AG^az zG^&n}6ay3#NT5Fj80*wCF=OsvG0UxLeCc&?$z4)zFf2%*;5PAgRQNxQl9vIMcEVUg z%d97)Uy}xSZ>USmpwMVwU;(UQ{uIjfApN*Y39aWyO3FijDvE-kK>~ZTgZ$H-t7`sj zao&WFV-k6AUs0`BBplWa7O@?XjR2I6XNFoP!ckzmNm^8Cos`Ip6Vcs-8uXs@u3{Np zT;np~UBLwKZw&G$$E&1a&t)ONncL3FY~}qO!?o@m4OiXY8G=JY^akK$Z%(ou5nRXlL`h98eMF`sB#49r_AX3FN*c`k^RJ**gBcl#l*j5) zI;5C{#2c;}YXZhd>0GJ8T$#(taR=Lu^RxGE>DAfTUv&W8EC$F+{jlQr7)QCyP(u=l z1Of~Kf-C5DOebx`xTNZ#kA?Ptx>>D;x;+5IWb zh=A#Xn%g?i zZuB*iuHYV|L zqNf4DS@oo7Gr$PIdLaYJFsWO3`@7W|w`YiF*tB58oqvO6MErkxx3Ng3u;N@en^Vpl zPN2%-<;z;6w4_F50A2$FCIJz@6l%>0j{KGu z=llaIj-|pv_Gk1y6)2v>e`}2nl3~YtgFJS#s>Vt_pH8NBKuYi*bN@kEOwQ;D`&8|n zTytWl@>_0Zpk_{u{{ff^1tJyU&oCJQ;6{%X;Q}%2T2V$ z)@1Jwp|%gD<uPaIDBd7hg&iU4>8%}+;bqVO2C_v{I0ISD2ILBGU8?jM^;-uOD}_Ua z(C~Y4V0SY63(_;r1{hznFDjvlyW&5ic3N!ULIyYC$*yAZhfQB<(AxHp1vBI9P6aH1 z(-h@1Nla$>Nz`#o_DQ-36!w@Y=0`j;v*@&XELeRWf1<_c;mMMuhCHjGgK<#!uBqHr-{{4FzGpsdhf z4UOOpjn#)LIDiZ+8^2d<_r7xtDP@ZzjaQ5KG;-d?9XWA~n!U+J#zU2!V{hBCX+izF zi5JxS!DRx)Ku-bPD(;3Jqn(N%p@-+9u{njPptYEGo%cf1g&(0@`g5VL=%VM%OD-bf3&uJcc6Fm5l}20ze-T$`vsEZlJ-nog z&rbP7H-~A{bv9%vWpC8!gO4D#0M^HDcFkpQM%s9llWO*G8&jK5nm1?U?SY-(=oIhL4gG5sU)JsEULbo5oJ=KfY@H2%VYyfz!2nx0cM>uG zZYlio5WVH7u}$pYGAh5##!hLIe{o6EMDo@u{plwmjClyRLds7N!Fo9~kZdgyf`;2i zmY@Hs)S4`?SO1zV4W&&Vb}v@^({XaF9W;&2!X6Rm#+6#r0!?dXN^4(;RYt^PE$6=z zwm^-u>doW4UZD(ZhB$dwOqylhJgV9KY!JHt_#!?bbo1lSIXm@y9Ygd$GoC2gLRGq7 zJ2ZT?{J4NP*XM}DMvKb-cVY}hLhZw;;7mbuZ1u29e>e}{*LWn0^l<1iZ}T~6h}}e- z7HiKjK^a$fQSFgzL7hP&?lR@Og(8c(xJeJoUV{1DvviJRrAeHTLxo=5Zgst6+aiCHZmYa%w$zCvgGMU$V^3RFuGVHHfmpHA$n;sFNL$A-H>}QISknUZ z<{tyb77$BMBWy&{g$pMahCI)0l)`QC<^;Wz>e20&0xd(eVUrroR8hUD3KGgwVSAgk z?GRcloFXE3lDgXCv*_~GbohqOcb;_<&~}zktf3S#;wnLQ{l^eb>^Xaof-=?pK zSNtNrYOta+sNNTd2oF{02j-~?$Jp#IGwmJ*xCG*H8w3-s^ka3CqVce}B~*o~8TJAg zdzg;=l>{@KTNbvM49`vpc##%``19}y$SsqQg8ui~Uqqc%igmrNAE_BV> z=TG_0*8@69EGp5bf%yBjwqS9p`aK8OoPymftupPQP0bbH#$~{6g=UT%vg6MDPbR-G z9hZBEc%`xA$Xaz?74t2~ub&aUyNGLWV6|nHM+bB%{;iZxGQe9H#*c`H&6e}u#JqnR zpN+}+W(Nbwl07TX&0wc&YA=wmvO$kl2&)?%X?X!zTK0(`*nnUw2oG+(58vEJ*EAKB zdG=v(Kb^>Fj~{%E&4J~)h^6ZbPQy)*q|tad-APkk!5mLlvK#v;p^6;H3o`Bq$P;Ef3q?E8b?D zW*2gu`MU&a_-|)VK*zsn&%7P75UdMrvE@f|3=2ao_qxEP3f0p0p~b9R+)^8`PD?HE zpYO!_Ses8 zifp$LyhpdJ;Bd75&L}jcwO~}cfer*Dg=R1Ne<)2QuX-3o3jz%jFX zs_7!aV_j>I)jnq4RiQ52errbO9mg-M{ruac$&-GG|CXq}OHKeXowI}oef!$;FnHm0 z2+5v*?8W(FWt!b!p}W{R2pf{ZmMB2b&v-+%XpuvA;TisA#LNuSm`KG71L@c?HV1E2 zkBm15P0p0w`}n{zb?2EQfymUrceU|*Y&nKY4+~nBx(}CqIok>|wj}#%gLwT{olx>F z6;6gI(`m&~-V1dL31X2Nidv16sc?FV&^LCN8u*xVK` zHJ3fK0yaCGk|K_z7TfO2Z>HeemeIsLc-=s|1(DM8fDe6 zB2qvFQW9(^AKB91jXb>ONTl;~Wn&gcGMblg4>F#S=Hcl})Xa|&YW%aM3+aM#WWpdh zGPiShfw~SKasmG{LTP4+y5lg-D87(GA;bcM#{ljmPwmH$F8k^G;uTRvNBE01L;WE* z4qp3I+VrW+zZWBI=u-$dtLvvEtRf^SJTQc8w`5p3f-O3TNU)7|7nubNai%>#Rvj&e zU+czSyF*c`7WSffx&{eLr~`v(sl^gk&cM?JPU3qTp~ToSTjPUJQowjN_(Y}+2Plbi2J)^g=!n^>f$yNjZa7o?5O z$$Lg5vO>}B&+MLpe^Ag_++a2KtELyl`{+HXq|jCPlqlZ%{v!ara4@(h3Obk)ELp>u(FULd!3E1ZeX57t1L~ zOIcwG9LSJ&>)dvnuw?1ck-(Smc>17ho4v@C787CsadaAz{d`$L zxveARf3PJPh_G%-on#)Mhw}$85j{mq?7SQxN~T3d+N}>30|DTh-ccwh2d*}kB3pn0 zh7tZqsbsV$-_-Krg5l$%)g&&c05Bl~7Tii}^ji=R$J;Xln9AGYm1hFDY7!F;nnFi` zeWQj%08>ZDGsz7ABI-o}NR!bdRRjpjiUu%E)R8zP2XIXRNYm;k4gd(-{s~~3wa%@~ z0pMCEN@#TEpto+9p8g+Zgz+d)q~Wc`-BFSe?NUDg!{bAdmV5kqE-I623T(k>vZ|BYx`=15o(??;^^i!6 zSjqkbQW^!QD$P5Bm`v8>)n@mQ3bl%DD(%35!oot!`(-IKB}M_ZUy37F-ij?^G8$#h z4_17V3Ve@@50@PN!hu02nL@cLl9J)oyi?gd@p;b8%&C&2q5gZUHEC&BuU5yD*xaX( z+V$S(dR?yBleZI1$6M(0cD;%jZ%+k3K}njJhX>}0B^WZ^$Ki6-(ys4V(VzN!Kzdn-pC={q>#}c?un=Ch@vH5jVnKxW854t%h(04F1^=5_J zpyiJ2vdMgBiMKgptos$_ERHU_R0;j`tv+7IlQon7xz-A982C2RG&_?ySD?L@;JqI{Nl>&+&tl1XQbMwUx% zH{w(%@!a6O9*)Ji8qA%J(u1C0(f>+}Yg8BCRoU=rR@c9+gEu==ZPY~^{$Y3BUv_{& zgfFaeGtbY^>&Z(*K zAcjZ5MM{n4M8=I{yD#38B!)C5zAY{NB>!b`y5B^hSN*1(i

    @(Rm|so&NX(XP-8v z_S1ZN!!K#5qsQ({vx$$)wNu)IDX-j%cchst1S79UJVot|1XG%m<(tHcE@p=};pivm z8qkx}YTd$B`ZYc+sg|(Fe*%~cdWW4UbNyuFmT!}M&JV9_3*FcvCcdthJlR*-&3t~J zot}>YQMG;&mbzX~1gbVSKL;Q#A=j2Ui)A(yE_AxbetPnt4Qn|Q#W4FE{_-pnSF^eq z`&#dtGy4>CV@VeiYj4Ey*=6WH;eRO$J2eyBOTRh7U2XNCK^-N+3sJAqFU4P<67b!U z(|>EN$@S)1#)IUbZ}&9IL~3uIMrk6fE${gds%5j3b+KN^Yx=TL-4DMzZq9$FMjuxx zzBP45Y3g#=E8#8${W1#lpD)vn=-~O0{CgDvft#<{bxgn2J$=^Sg*&Ok_QPxefgA3p z@%bUWDXpRPZ0o6eceCrN#i7An@^D0F^W}2!r;@s8z&mU9+Vf@>7aBRI9fiOkOX&dY z?$Va|4w?{&P9FpG{=y#@rv>^~ejx_Kks3Z~eBYEC8FYq?Db2fqDAaoME#Ft;YsrTK zCE)j<)Lwz16qTn*n}v^}y9TGN6W-a^MWsRTYEWfH1O2#*z%Xstb6x|4ALR%RD#>1X zXxaQ;DdMp|6u#<9k_7DVh24ZZSXPGXbIjhEB#>icA!n^tpSdbV z1|6S|nM_4P#esudI-jSliv&8&xuT5iTJR@wq~W6<$K9nYp_9_FwP!;uPcbeaem7nCHvoob+Nibu28l z5D5eay76SrG&|l46chq6U~VuWv;Sn*&)8h8KLVmBS6|7nIDDDPrRq}591q>!gUxsy z=$1Rh{*X)>lUa?2J@EGz%MtTemHDj4cX3o{5koc4R)s_c_e!g0#}0|}QP2#OcDpnJ zPTCgrJs(UuU7TySEGFMZx^5M5>J+MfjC{KC#NrbZoi6ln0xIm zWTlUX#)n#tt-F$OZNNsL8bwS}vKKL(az(`B_EudvZloh}tMS_F{BS_cr?H>=c}%ZN z*`J2DVbEB|9r^(c@<4ElxJLU(z+3fr6I6o_W~ba_$^(s`RS-fv|L0RE74u7q>~nWp zp)G_MW-!86KtU$J2g#K)oZND`G%(O^4Naqg9CoGSDsSzu2*mq+*@lE*rB&fAli-&v z)_Xw7#-BIH$j0&O@p5MCkz)w#V$qud+qnP)zdTiF7X{qyw*uJ{6l!?=Xq=W`8VuCS z6fkT?#Xu z7mb2~f(dIu8Q=AWh*d~c4PJzYyPKdXolCRg8mfwkdDR2BGs>Tbo1cWaHa?s5!<)5urD*p5AiO+@`ch0Yf-OZ5l zXvv-Bmbcpl>+(sFg}sGC^0i`d-={)m@s@qtALI=En@+)7vC}!xj^~3^E+&?<%sDdW z{Ot&5?msJ4&X&5Yg~c z;Lc;8!^|QgYI^=XCmL!5WlN9elZ@Qgc)`lSgYeLaLkVf8(L4et5*?-seXg}?O{mCT zVrEdEBYfOkxupFgM5ERegpb=3>irw5bnW_Txhi{^@vB(&j~yFd6#l?zT0}K?p>|rc zQksq0D3NyYm-VLblQVKskb;C^)>3n|aR_YrE)%gS8kV_^vv;PADUTZCFU$DEQu)g2 z76QI`+vrRz+;^(A3jMlq!9!Y2v?EsXR1jF%N+?nxoX_4>bbVPQSv!y2nb%;Cka%le zB5l~{JH~bE0P^lYgLb54ONGH^bD?H0%h`UP&hXMb!GGAPFi8YDP@K|xffetcI@x7M z6dw!rtR)DB`!VjXpyCyb^y&t<*eQVN*XhAm^|NqvL|U2QQ()?{<4Q2 z>dURKrVhSkf(M#|w6`HtTE>`xu&6w&?fjYi?~jodv$^`53ZviEPCMd^4=*q_gEHJd z|4znK8B~<^{#+lvX#6T|t#p#>{(i%ms42eUUj`wqUC5Ay<0hIqBKpTf#{uvofk;5; zPG}Cm3~)D3$E!l+haFGnDv4-pv5cAAtOsy3D}e zNozTrku&~N@8~tdAR(@0Mk6-)tYv#V%mdfm6U;4zw0^5Ej8~sUU39$tpf;0ZY2n#Y z1qp)Ajr4Algq1!$@oFO@DBK5jS9E>PPfKip7fiMm7(HW!!4PEr=u0Dnx$~E=vKc8} z*@(WrieDxh^FxE5SWJRQtO+@-;yIkrqkmSf;cOZX&gA&yg&&f7{?K;Sj?a(e%&s<- zrr)uY1i{0Q1f)$eedYSO{5};6Vc?3M&9kLrBP{GmckWU>yNPPbi40X5eA+zyJ7>_X zu^%hgS;rWLIAgU{5X9ocC#5i~)#Kfw*`=3G{ zUdzW(KbEmhZrBK*3OyNPGvLQY^%RmaW6kZn8ccv?oXG@jb80{x1htKRO+TSY7{H1b z#q0Rv^5nPD2^yrPoVM%?Q06ENNJf_&ot3~2L+VtnV3-(zOSr>?#b9ky3^ zBMg~G;yD2MWyisO8pC-=)cu)cBVQReqwlF)g#f!xx7si7We7y+ z#XgnG0SwBwiuKo>v-~oad6EYjt32u(p<)6{xS?mmX3o>nk!))#?Nz+CuEmO{r2M%( zTD?lEDGXA1_yBlY9@dpa3m}IU6%3~d z=#^pyz*_+L1qp54M$qrjR^V$zF<`C{V)!_x3FsyK`Hwru#m1h6#wy@0UcYysy|ut zdz=7HFAXf5n(tfZK}fQS!^E}9v0R;*)|%~=a_(nP(i5SbL*m|gt`laNCw`1eB$Mr| z>Z{dXjvI_X*|GWwQ(AL4{{>Hdf{9T_ij9%-7$-p_4P(L6g(n){GDvdrC{j6C#C7|F z1$!L^L%=WU8tGD6Lq3XBM|V|= z10R@&!&bC_aS?l)K@o?GrO}*tmPMDX^e7vGF#n9dIIS*E^&4LjQ zs)l|EtvAn^jW&re7Tcq}hDWG$n1@v+vA%(cEh~XYTLC;yb4mekFigd z_6Mw4$!s!bWGP9`W7pMeFr2t!vo&z-NHHdSTRK)u3=4Z9KC!yp{;V{Q_9VI_I? zLgEI-J2NP2G*a_X?xqq zhaZHu34p#dGYP=ryde3J8wWH7`+OiFd#>SXhAshh`td~pFkCU+>nf00kiO21wiwU5 ze(U>qyY46_HTNI|qricn=>{*%;*(z3$D^RoIXiC?{u>L>3ia~9*Q@2OQfZ#|%I-_v z!)uXfoCe?QjQiaNg;_d$9zA1j3A1(!T+TLqCx^7$3Gi+(u`b@}4d=Ii zws2B)fnyGgs`RX4`nmmZR1L<#xhv!}5Q7+F2Q08`A9f(O+Txw8?|Z}LdG`^QJYMBW zr|%+hC14TkS(oE`j(pGxO5)qfZxKKu9;f26^!o;ASIGAEoh2!zsmxxVeO0>qXAqC% z5tV+;Gn2l`$8b#M=@9V5awd>UJO-|$A)V_!ljpkVfdJ|wi7pG{=}aX`4W%L zb9x87KdycO-cRMl-a;1YftZ0cTN9Zr7Jw{K(%~nc*9FtAN%A7r;{gT1ZQfrPYz`0V zuFd-)&YD{YIGp0T5X^gt<#Y&f9{~ERlthUYw)#_I`NYqjLiP4Zv`xA9PR$-J+}>BZbL=pjP+uCZ{$RmVI2DIN-o;wr`=@ zt3PhG5n%PbP>8QV5HPQ=-Ik9}M&`Mnrz?`~kS^uzc5Sm9DnM}Aoo$v9ZtXcx^{7qU zBvKLOptSKCX?W{2bqMx1YKCY|$qM(hYUtW+n>)}oWB;(r{;|UW5^&87 zky*@U+cnWEZ}tjRT|cJ=sK*)nlp zGqN@sSmJ;yL1!;Q`*d)SNAfq#%#b{Jao`Al+D=P7wn{anoV@(xUI@+D3!yqPD}j*V zcixM$c2Dlwf$_9URJ9)B5H${bdEQ1hO1!Ntx>W)Y9D+`ubZ~aD@Qt_SEdAIEl5NV} z_P@>P_Uw6?Td!Korjbr!jIq9b8$=(1LWSXbMiMV{k%^>}eb~)>eN;4HqUp&^klFa+ zY6>WW4uyrg3RS^3ThiXhAiM*D+vp>->2gcI_^(c$0ZGnNRkPtiTJH0H@YuvH7xur~ zITN@B1P1pB6m?JJ(`j2?4J3UYmu8EPf*Ex&eIe^SdTTnXbN^n_uORO{e1556;z7u< z{uy++TnE=fyD%b1Fzd?$0Zhj)d4`ckWP3D+1Cy(sX)MD5W-CJR0XBO0?l(bg#g#Qy zzm*Fi)BdTsq@7gVAQyb4wD)nlQ(Zs;Cs{?E7mKptLx*6HM9zd93qHs-fEznz7WyKx z*B)+{)qr5E5r$=PXpSEW*f<$KR-5R$>O5}Wd<|ugdMYR^?)e{#_K(jS<>d)P-fM|e zSO`z&;zVhL8_EVP=j^+~QIQ#@8q8>NH#|%mI|)W?=suvSuVus%ia%ZiUeXLZ-N@IQ z64%-MQYL6rX!*BqK8w6=-K{H3tLA9#A)$FuC2WajOLo&4nmA~@2G)J|Agyvsg=SC^ z9LRe7(IBTSFFoTLm86@j^wFF`-T0r?9dE!Ue7J4oqKdAUx=$6CIPC=5jE*5ny}?lJ zCP?$J;tq5pvAK%ai26{UHK_lPY=yq=eSP?0kD>60I?;;QSCBRxtQbGWZU`yl+b-G8kK%@m zP9hi1C2p7lll-5z?=d$!iCnKt(6xL~g8d6wYbbCQk7m-7tvFw=|Co#+6t-jj$q4Kg zMp|?xZ?I%BzX@^RH2a%`^(;7So9}`4Z{Ewxqf7mwYrJ{sG*?w?)=I9 zOsPRdA0a2mu(?+2)8`0D0|2M|X_Mt@jsy6O-a^6N4`G0goXj|I|Aggey^#I2o-y!w zOlFG4Yc2~sYMvJx>mOjVDU5oMI9*b|OPyv*XR?kRD-fw%QvKncR`U56hV61Xed^*e znW_g@@dkg$FVslm(xnzqLQuVPVi-?pHa9RCdV>=y2r$*`=ou7?RW0r`}G+68gVH*J?7`AKdbZm^sC~Rei!bA+Bjm zCgKN|_^6^yjUI*R8MWXxXbgn$7~;A_!_5IUaC*Y=I|8z@UXS>S4%pXd;lsQk5DZXB zpfM<<(6d&5p9Rl1N2u%HNuhv8UeyM55bRvMmyYBZ3201);rL@ZHHCm0mTnlWg+A5k z7au(CFi6tY-1p0Ptt|-^=wQ=uuQ47?-==T)Y;G1qC(h^z8lL!yTVNzwP$As;o(<7Q zwiu(QcXn8fPiPRG`(`GBLwC#R!-HI5_S$}$F+V2v-0+Hr&!FEgl8JL+;Rci(RnyAi z^`)X?MX;mbj16?54v{QxUMc}5_0OlJi{IDhM^Xr6#WemidTul2alhSa-&eu~5Zr)x%&oKCp}| z{$Zn(R3NwAKfPNG`-TA0PUzj|IsBYk<-xSRIV}y&%dqp6-7VBZRd;*dh0&!Wqrc;T zU6WoTwu@w2GiKbYN#%;R-6>7UhV`-_YVLOje_Av(@i!zB*^ZrVMR7eD&4mdyz)GGG z6HBFXtI2xrGC164(3JizU2=fLUQoANANS%pTRL9j(!NIl&bJDVk!)KolOI?Ull|wg zrXebz3YsY>#R|Z7_6PvbH)#o59sxjr%K*?fPOgaZUGdEd0@O5}R)wv;MYDgGf1#5^ zzbm^(goJ|!S>4qK9!+XzEatBlG?}H+MG*TWAW~WVv!E0`?C4DV-ODtM*;%{N)Q2@#C?|-~y zdN!|C!l{(L56qGw*^@dL&2Y8O^zZsxah`yK7WyDR?~+ zDC&Dtt5H@q(q=DJwfR+=YTZ|4ufjUt5Qi+BmTQWkZ_zLMl!^0()&KP(VTf*x=|VB; z>wRsqruO=Q8^f>LqF#{AdCWbgjwDF~#c8XBQ$@{nu(Y+e^QE0Qd>+{q3e$3H9D}+V z)g=Fmf`KzSCN@?nRx?K2llJ&&reh;%OMHK+{f@`#qq@6mQ*EW?__AQ zqv&nF5Z`uofcup1?Vh5Lf^d+k-xuT$$l1tM*FkC}y1V1QMKS$5K0Ws|1%}1^e4??Z?Ap%zejiQfCa7AASEV>xZ-zmn zESL8#_ww7wjba9?gMD^QTt&D9fX5y93L! z*I|>Vyx0sO(0ErvSkX#6e&;RdL1OD2E;ii+tK0M&6-BeO`~8SNo0e~kZ8#YABa{Z< zPUgMLEA?QXFT;`z?gag-BAmnbEJ&R$aSem%5681Z2N@jmTR99Sbs2H(R%3|I1)Dv^pyz{*N3hcO z32Btj-V0y({#gpkitl9Wd&$H*jeIxBvJ|EG&2`<{xA0od5YhqQ_SmPxe?nfZuyA%u zBl1~AKI=ZXoW_mCcdI|k0)J9Lt^fW`ntHzf_B?m+fdA;SC?_uK2fv%E>3V~{%ia!! zYbIVUICuZkoTwq)NQpb#J(Ua7S5Em!bez#2^cXs!qf!8)97!$M?;_bMy!%^COo)d|8oQ!8hAubaN%J9 zm$LQi_RtawRs7;x#Z-C@sus#8H&;TONl=71KHVk0W1y?QcpZLaht4s$&p)Q*?k>_; zsm{neph4tC2@NUou=|2t55{GhYVWuOcsx#8rP{>Wjt`A{jrLM(RI8Q2*@i$8Rhf{{ zo#ulZ6HpvRX{rgg%W8JBp*(qVguX5iQ7zA6^&$uF+0h1TS=YIF8?PLIbPJLV^4Nq|l0|tNr@|!$DqD%2=tyky;y& z>o`P>a7lAM{a&1TmSn+=b#S+XQi&&=Zzl7%&wKFY zztWjs6`QHTfmV@hs4_HQ0&|8V9nl7{1AQL-dMKgQ zs-J#=#XN49>It7~;&nepf+yf!&Wgb;MFO!HS4^Ncf$7UWjC4SA@Tj-E*lY+}ZM2!X z?3%6gYg9IKC4*q#C9J_+X;9fxcvGB0^lBTW84F^G^uwSlA(4nxERIAk7llqe%6q_J zpH89EImypJRPGC`aoN?V(vIVADd9ICX-69a!|3b^02DcXgeX@bGBPf_c1An z0tJHc?H}6H(!j&1FC80-XaX72FVa-_kQ;dwQ;J) ze}5N31uy3N=$x(=H^AWVI;RmA1{nB>p?8}8J+i$XLyRvwe%|z+hmOki&GnH-C6{;1 zeF-Tk|4`bB&TDtE>KIV5rU=E`6RyGPbS_obE7BF}jTRJ?4QoX|D@D>yNruDb4IZ^X zQ>#$@?ddbGQq7vdC1X}urrM>ghTfgA)Ew5!4>21^`{Gu9isJdzS_RKkRB*y(^n@o0!?yef;%fB8w?|waKQspxI_Apyp31 z3Ei?mlNB4?Va@H!-H{99#?Pr0ZIw>f4{kc0?ksk@&0YRlH+ubcQ5p@vs}N2{Gc_9> zE_GgBhyS#+pQnhmJ6!RI2n-wnH5RqSTXY(zH{w7+D?%hB>{JfJZhX!(aUY%xYV|5X zlkShuRL9A(_JEZJ^9EVTgwb759Izn&$v>w1gvh_^G52g>xj+E24?%pbGbPOtYi31%9mc&i_y zFt5f2>%$gn4Qkut0IvuY_@&Yprc|$DCC-NNZIx;mSc;cUW#xad1z=uulrefq2yYNg zOZw$7OI5#t8(YuR%i&g^yogWDn1ArAIfTVwNr{9aXuQ3ej}<}VcMKEmtK01A-yv?r zl|Wk0PmcojiXy;6p6}oxY}~sPgl5?XQ7i@PVAL^|&Qfa1><(S=l6NMw)^5X7t<|7D zB(j7IG9i*ExZ&xds+nqQM>Tu8*u)~2#VC)wx=l1-fxcE64KfS-hxhZ@g!=iyZBs4d zHxt4yow{713XMuLUNMqT_%ah>avA&v6 zMM5x*{RBR+SM1>hYB7HEk>X zvN-Z;qM&Yd7qLJN?M8J5AUy)WPE8dc*o%TGj@5(57fPMPAh8vLUyLsZ1Yta_C!+wg zSf!!57qZ{R5XUdq=5(a~Hl5b! zPpE{yzv-o?O@8hWe`(hCNOBak)W+|IdGa)Q9KG`{kC&@h^9Pmz4%=hGwr)tYF@;SV>62saF=F>z#A{2p_Kg7#`T2JS7 zpL!BkITlM(BS?Y;K1X5D8=BV`jk)_q!r!~yslpXjX?3Wtgc*uL0t*rwmug_`_PwXWMZZkw`^f#?RZt;UzR|1?YKEj^4*uY_ny4N8sIkNy20U>$(DDR1 zXN$RzKzSiPx0V8{^)57Y+Ks`|DU?DD=2Q6-IxUno2->G9bo6_^H-LhGsRk`O99F&z zESK9QuGfC|V6cZ>=Q9c*#HRoOmeGv>2FI4J)h zR>FG}W6bzJWJ#zE@^R^YRzqQ9vlcj-j@LWIoK+beD1$ARJ7Km{=&)zpyvZhu#Xwe` zRdw1LWeyx$>tPZ4Zq2@aM0$Iabzix2HHA{d4^H&Qk0Dca#Srz`xF+Zw5@%ZH6~LjvnQlfxElZ z9cl@}6RWw2sE2oLyz%&wH1_S`Llqf~mG$5Bh~z$Ci4GId4F(0^gt}R%U~LSP5jRpb z7$%%86AYLAR@{FWXPBFB>MIklJ%6&|mI#IWVbTBWK#kDu+4Fgtu^!n6yho~L;cpgD z0)Fx)ML=y(lQs6W@x-tA*q(FMBZ0`Ps4Z88$7e+vpi80f8IeM-Q^P34fij3c=qJv! z2zevqdHm?h;^?0BiZ%DQn=oD%igPUS;Q@z(t`;@-ig)!IS_f73w~)l5MZ5E*MY@JD zh){H$km-1ep36qnt#HfbYExwrl?r*~+)aPppI$SyGDR@IG+g{Ez&npUFnV0A+%n;Y z^jKqk^9VF;z0KU-$v_vgv3dMUTvJ|`Z8o_L(y9E}4-c9{$&5n11PY6QN}$CoJPyaK zUC+m*i?KP9HOF)jqqNx*QPLXO3*hjrH2<%w!K@D=dWn_R>7kqZ7@TwdXG~}~FlRS5 zMk|^oI99M-97E+Bm^!HJzu5u)jR)B?vGQFo5=`KWb}+GQv^?mH<*9aH)1mbksdibU z;{C|>sv5|t0m&?;Cx^it7FH#qPl_qF@)H=6Jf=Lctw4O6i}g@?t8LH+(%`^;-=k-? z#$>#|3!$0c(cy$^Y9$632sWUM>TkXGMjHvr0t}Z6Px#OQ{GKm4Z)_a54y-T543#Uk)poNpnb2<0vKjm;9(IJI;&&PkWUIeYEWAYh7y-Ipw3NovjO~adAZ)uI)YNa`L^WlyM^(~iN7!z-M zQ(Bj_A|fISua{&pDUD5z6+A}Rn|&~~wLLS+)dM|d3y$Mz`lz6B0#8E5W4{{%qWlZP z#|f}t(IfLx=rSt7K`#<8ZUr26NgrN{7+m+D-(iiB`+3w3?>uk#HhpsOy{d>F-Wubo z$9Y{&(b&A-shVV2vO)xJ_eZOFN&0?=FU-z2Gp9%f3KAnhY3}w@s>ZpsQ#{Y^G@1Y1 z4scGvQ>E2xRpgpx0-O{)Z}wN&eQ(Ha(TJxc^DJZKAA0^r>?bU z_j=mUn$Nfyb~)HdMaHji_|Viu&gmescmLzE#ts7uU)V1OK=SX+?n# z3CJ;c@G7>m`c30k!0oQBq!Qzr{ha!(S1l38Z8U1`j?rH~^(@hLtsU&d(6cV82Yb5+ zk_&{xVWTV~o4R}Ef2S8-+kSEncs2H%7%6@*gzXsc_ckTIE_txqt*@6lV%dQi<8EJe zqc{PnmQb4Oo3Ob9>3&GyM{UPXScAi1t5{0Eku+kSZ3mW!l%=+BI8qn?Y2Yok^~I=u zu?LBG|Fq>(`PXbZK0gv}hp`Y4RE;GsePyt!#2Tl5bfl%w8OYnYUFByP^{Rsz`x|z> zQwe! zxwm5cy+$U1&iNC=Y&r{eP&1*iA!^N1K>p?mDBn12%7-9BYWEh6svMW*mR3Lx+j1-1 zfb7&B=%4^Ioi7$6!>S+#ZboKjnOBIFxVL zeqqmoBl2lCl6AB-1-T(HJpU`z!N#cMpp4-x_eZu3 zXx`z?;6$E2wKrmx@0zZ+9aWFS6Yzn!8B~0K!CIjx)!NTGC27~cWydi+&vPP5uYV+0 zb3ODj!ly@M-|eOT{rQLG2>9*!^cBsKKP>GY^UD5jJij=mVW*pnJ_Vs>RY{O*#AEgY z`kmf|o?JxmLAvAqCI5LytJf00lNZ>ja!9e+ZC<~0V#Dvo3+C`p%|r`BV{627<`tc^Zf{gYCR#1Svuc*?4zw(J~e{?07JeoC4HM2?}n&z6VF zolPmO-UWv`L(IX~yWYjo&C2GU&%N-jaPq8Nz7Mo5YHm}F{{DJO-}YEvuKuz0l{eT{ z+;;l6yJXbm8;KlYKSs#-i3)61kVjZsaai-2t0!7Al;It zspQ&^H$(f*M(I}v@^5?8T5CXwAvpy)beDM$vK|Fw*1?4a-zJN)SMI0Yeo_<}{}Hvp z+6ceQh_E>MX1~EBEGY*NL~$w|&n@EG+z)D-oF>inl0#*kLA@3KHfqX)brV9*s7I-- z0_!B8r_WMoLk_b_@v&Ce7+$-9XE)LRwyFKxLLv$Zzl=)9Qp!l`Nm&Q|yS4>1gsgX- z6paY)m->_&rYZP~nRhfbCZ7bUwR$hT^UzLyEQ4+Jc1%J38aCo!w+QW!^czeL9R8?l z5@_rN)KA4@|A?14;*Y-#-}5}Ct*1izZcm}7n%#(F1wJ`bjn@tHus!IL$7qP1&+gwH zX5$7OM&6q@b%g^oj5NSH4=6?&x+$CYAW-(o=_Y4App#g7`Rd|mBw>Fn+Y+9!A5%vJ z?PY<-_9Bj0&OPn#1xI5 zpJ4yV-^GKkj#FUwMYFcQrWHibDQ{UmW6>E>tc}Kiy2*VnS9|s+FdEFdY5tpAkKW_| z?_umtZQKc8#$_91dQ+;yC=Lr#zQ!=c3dm8&U|c8bQ8})0lYbZ1Mp{>(92=h7Gjb0G zF~uuV9D{;B7o7eF)qC8cPU9>)(YJ!KSKt>{%ACf!KAeN%Ex8mi}vL_5c(DQ}hE^ zD2p+L^ubdR06j?_{N*IySSA35?h5deLPH1vfJusbUc*ooSR_ueTWM(|kt}38AefhY zWI(;&X>e*8tsTw;6p|qHp2$pT|5kq!w#Z~K=6`+``}^@XmxJ7S+;>zIa7^*S8^2ti zuPi~WDs#t$!VCqp88ZUJ{cvtTOJa2_>oUe?IZ} z&Ee+kkB7qZ2JJ6`dpDjstX$Kn`R2?w@hAvd6E59E_+=}7@oZVpvTyJ8fbT?DWNry! zk;M`AcXRKK(`L)gzQe^}`Z)L3TlpXVm|@1*B(-sa8_na@mOr?2s(NZZ%~@cX6ui5A zM6B8}$LkxWr2;^lT~;qo8Li#64Xl`Cw;%uyyGwP_pA|Bs(3V*LX}C_xlk5-+`mJcD zHhCL5=TvR7Efqry0Q%~me@ld(%h?hC9dBOzsl0PiiC0E1_#H{_*bMlFl$id$*vq8I zE8POKohpW3T6nt&@EWN8L0sG$sQD19mF%ZbTVJ@rTEiGdi2@Zbyb!Kp$Oi^@QN|Qq z9No9pH2=fCCtkBb|2h6okWq|uis{>SZfXW6i)8bF8PyvB;V+9FL^aKm6=y=t8=n_; zs&Jbnob}M8mRU}ZZJ!3A>Zxct8KeE`7xdXbM!?mZYpFLzOZAN=>m7PWiz3aIVe;}? zOZXF_a=hY#GuP`>w!2-gzt1V!7xTtUtWl3e$3#2{XzYI72mWc>gUii1Vid8g!0cv} z>HlGIY!zBivJ>hB$X< z+v6B}?d0KL)NcXU&sy^jldm-VC;{gl1qBjo(HbwdGt&7gN6x!t%qxHMgH}}#E%b`| zhMRfwuFjR}2@J20lZ&U~&JtTb^O#r6{zX;37ge3>4|L*lb+;QFC+U8jL!O>PY&(q)?{XvYexahoeD;eQlNk5eWrDY%SP$?G(QtAsO>u+k{p#z6ym zGC6AskRRz+>2ME*bF&negx|uDcfS;DK5TMFwlY_mG*)CcyrQDFCIP?hY+l;m5jLDvWl~5Yvp}zotEt;O*qroUrQUYrqQu=Lta0)-6xMN4CaEp=XZ`T&@RB-= zk@$&qVe<{_iuFhwo;mSe88E_7>MjFl5vAXmaLIwhcZ8{^I9UvpH57>MpD_?8hy!LK z<_(~GX+3K-_d-e)QM(gfL!R;IT`(ZHIN5!Ab^H+Q^azTOGCf%>!Xj37J%iUq9G0Ru zh3lUUR&PG%0F(nCrCr7z;YHHnZ!yPV++egCX{TO{Z69CM{KpcD|aEv)`_Da^Jo z7F2;~mhlZa(OVNS4->SdQG-BYMLp))kYeO}*6ydl5aPw3l+PY+-_RrG`+ zx}xA)>##p)&=xe&)CP)pm$;i6>peyHelqnyVmn;*5d)NQ#tq+9GDyrhRK$KvT$yCn zSJO;w3Hzbs=zBVW^rfG*+q%VtbLFdb;3!ccV_W0+y07eN?KbU;0m$`LNDactT)}S( zgCxGZ;gkvXDG7P%*5tEhXf@h*U$ujtoNO2#>1QXi+1Yz;M`TM=s!%^^>SL_Tr5mGi zfq9$(Ez$Ok)KcBT3~LlRnx3n$lfcco_&wE^aobDPq~=Lw(<&|2QSZUSFX3X=F#$Gg zP>X^Og&X5o6U)*hRvEEe-00Z#!HIEuCAhXmi{kad%2+b`-X`oc{xFQ%V!V_-==8Q( z?V}VEb>2T6(zj1v6i&wJ-NCFf_-$DNr;?hc2WlWB#Vn5bY_ zI@Yu~sS}?e?4p<^{#0NV=opMmLCoUlk(%G7eNGHp^r=R}O2O|DroU5-^rDfu`8ppYwIxN##-$UkUe39i#~xrA3VK<-O-Ca)>`QSa@+np+*)~NHcvV@EYIbC% zl4Se&apnG|q(SAEcwWUL`tggA1a!KvXS&KuiZBg+!p+a|;~q1*zn1=Ha3As~8qWEe zYBfWDh){dvRY^d}EsJ%t?miKO+YZqV<|$P={>$d63)KC!5DDz3j9H5cT^v#NRjvuX zL}H!kj%oYDM7h-|*Y)aBxsTkchh}MjiT!;wctUK+i>@SZ8gUsO`ir+i8H{x zB-6b$y%~jBi%v-Zg38DSK`?#dt_56902tSWhtiRzbIkuUodYk@wg{!t0pBmbdAkyON z7bKuWPETTrT<(_F0xLks?@FZfDzX@0ir68YWo!nl0EURX|Jxzr|E+C892jHc;tB!R zSgC8F@&md9q?JC&*ube}Gx+IDDsbk8JZk+)`Ym|)RND^iY`TPcg#bUNX@zWvOmv&)fTg&?K~PBVOfQ91-+k&suH&g(&t0nr)bZWvBe4%r;VL5hwcbZo zoRTl^<|=#3=tDcdesxxvw{mx*eLL0-oi2Yk&H6LskB`|&*3n&Ad8{cU`e z;YAt7qD5FNc_`)c^T=muy)i&=r9Thd`L)v+rn9kOTf8=Pu;H^Y1`TX>c_E9!mp@Um zn)(R>*u$$oOLSEs*T54ksBC`h?JUXRnBP7n+`1-?60cct19=z8@#MSaAH1&OYmAI| zN)MAKS`0r@VuDm5QD7Le!d^%X%AWvM+HnfzNn1zoRU3Pdmjh7LC0M-vNa=^>%Io;4NP3A1^cA6sx!zV|z7`D@3 zfJ0vA19NcP7wAqjiVpCG<~%SjD+On?HvTdL>!^cl`o6aAu`OEG2|@e+nRjdb1B94e z38`<%053`0r!*)CbCm{# zSDmJ6ohv_5WeV*8hORQ(h!5MfqNbLT>vwwGXN3(>+OeYPTq^Nw6ozzR1cFV4Nh`T6 zqQnmM;j@`-W4`&pPN@Z-Sjf=pmT(n_0GNfP=5Qdpo#jqH`z5XnW=&TMeb;N{sgPG! zKurLOdcCTb)~9}BdlnFO|gd6Go%?`e{9|6 zlNGqXzG1omC=`JNCNFJPv}h~in^Q`%7|1I@Z^!liN+q-4;??qaKG%!RQsn~|1tOQG z?uwj@W*?(S6`0DLfaaiT3vJ} z@>d&BTwp+@5$lnTGu8o$N)RAESfb;TBZWpL0qTSwNIm{*OhE0k28z%>H60wO!VyXF z78a>GQ9$kMU;~QRC-Cb%pr8?86hPQd{{@&4xIh)s3abW6`~3XH3FVfxcPu|V5OuK@5ea0u;WOiwQwgZ z(7l%w17VSLD2}j{VA8$B$stfr+7?akR}zu}*VoPpdV6)OzT=m|K?ve!t0oTH?z6J8 zn;(!wDx~=}_uCpIkrI&dk#I@Zps=tozCle*jRhH&v;TTvIatr)`I;lQ)9zi?SWgg+ z<0!@Xcz=!fDoaR)$C|l7S;l?f1jY)SVpxZ!%HHK_mET;=6SBMr##DWm#L}aI-U)MW z-Z<&HtfQ2|tO>s+uaandV}j= z@6jj4kfXWm(&B0WtaJ~*li2gYGe`2>F!Dc%O;}jy&XX}|nP^)el`_~G zVXrKYrT@xmiumAKwsGb$+LksCvL4mvnM?D@~y0n6X~_UBLh z_CY+X$u0Xd?f9s5duv1&wcLXB?+rkdA;%z@mGwXAk1z(ZGmC5&$1CF}6K{`vGn=t; z9>G2M&vjJ1K$zL|JdTa%0B$|0mr z3+{&@vGcy(ZTTYoSWjquarkus-devservices-deployment + + io.quarkus + quarkus-vertx-http-dev-ui-spi + + + + io.quarkus + quarkus-vertx-http-deployment + true + + io.quarkus quarkus-smallrye-health-spi diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevUiProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevUiProcessor.java new file mode 100644 index 00000000000000..6327fc36395f3b --- /dev/null +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/devconsole/InfinispanDevUiProcessor.java @@ -0,0 +1,47 @@ +package io.quarkus.infinispan.client.deployment.devconsole; + +import org.infinispan.commons.util.Version; + +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; +import io.quarkus.devui.spi.page.CardPageBuildItem; +import io.quarkus.devui.spi.page.Page; +import io.quarkus.devui.spi.page.PageBuilder; +import io.quarkus.infinispan.client.runtime.devconsole.InfinispanJsonRPCService; + +public class InfinispanDevUiProcessor { + + @BuildStep(onlyIf = IsDevelopment.class) + public CardPageBuildItem infinispanServer() { + CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); + + final PageBuilder consoleLink = Page.externalPageBuilder("Infinispan Server Console") + .dynamicUrlJsonRPCMethodName("getConsoleDefaultLink") + .doNotEmbed() + .icon("font-awesome-solid:server") + .staticLabel(Version.getMajorMinor()); + + cardPageBuildItem.addPage(consoleLink); + + final PageBuilder documentation = Page.externalPageBuilder("Documentation") + .icon("font-awesome-solid:info") + .url("https://infinispan.org/") + .doNotEmbed(); + cardPageBuildItem.addPage(documentation); + + final PageBuilder codeTutorials = Page.externalPageBuilder("Code Tutorials") + .icon("font-awesome-solid:hat-wizard") + .url("https://github.com/infinispan/infinispan-simple-tutorials") + .doNotEmbed(); + + cardPageBuildItem.addPage(codeTutorials); + + return cardPageBuildItem; + } + + @BuildStep(onlyIf = IsDevelopment.class) + public JsonRPCProvidersBuildItem createJsonRPCService() { + return new JsonRPCProvidersBuildItem(InfinispanJsonRPCService.class); + } +} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanJsonRPCService.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanJsonRPCService.java new file mode 100644 index 00000000000000..78cc6622122f24 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/devconsole/InfinispanJsonRPCService.java @@ -0,0 +1,25 @@ +package io.quarkus.infinispan.client.runtime.devconsole; + +import jakarta.inject.Singleton; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.Unremovable; +import io.quarkus.arc.profile.IfBuildProfile; +import io.quarkus.logging.Log; +import io.smallrye.common.annotation.NonBlocking; + +@IfBuildProfile("dev") +@Unremovable +@Singleton +public class InfinispanJsonRPCService { + + @NonBlocking + public String getConsoleDefaultLink() { + InfinispanClientsContainer clientsContainer = Arc.container().instance(InfinispanClientsContainer.class).get(); + if (clientsContainer != null) { + Log.info(clientsContainer.clientsInfo().get(0).serverUrl); + return "http://" + clientsContainer.clientsInfo().get(0).serverUrl + "/console"; + } + return ""; + } +} From d16dee7c26f1fab27db040abd5c5bb6c7b167c62 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 09:49:01 +0300 Subject: [PATCH 311/333] Make check for RESTEasy Classic stricter Fixes: #33360 --- .../common/deployment/ResteasyReactiveCommonProcessor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 59368bf1cfa35f..8d86d692b62f32 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -46,6 +46,7 @@ import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BuildTimeConditionBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; @@ -79,7 +80,9 @@ public class ResteasyReactiveCommonProcessor { @BuildStep void searchForProviders(Capabilities capabilities, BuildProducer producer) { - if (capabilities.isPresent(Capability.RESTEASY) || capabilities.isPresent(Capability.REST_CLIENT)) { + if (capabilities.isPresent(Capability.RESTEASY) || capabilities.isPresent(Capability.REST_CLIENT) + || QuarkusClassLoader.isClassPresentAtRuntime( + "org.jboss.resteasy.reactive.server.injection.JaxrsServerFormUrlEncodedProvider")) { // RESTEasy Classic could be imported via non-Quarkus dependencies // in this weird case we don't want the providers to be registered automatically as this would lead to multiple bean definitions return; } From d100a4f87a164ffbbdcb0eb9bc3e3382d3b2066b Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Mon, 15 May 2023 10:45:11 +0200 Subject: [PATCH 312/333] Arc - document the need to execute full rebuild to enable monitoring of bussiness method invocations --- .../main/java/io/quarkus/arc/deployment/ArcDevModeConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcDevModeConfig.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcDevModeConfig.java index f5966f30e4921b..bfa6cd3d19ab6c 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcDevModeConfig.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcDevModeConfig.java @@ -8,6 +8,8 @@ public class ArcDevModeConfig { /** * If set to true then the container monitors business method invocations and fired events during the development mode. + *

    + * NOTE: This config property should not be changed in the development mode as it requires a full rebuild of the application */ @ConfigItem(defaultValue = "false") public boolean monitoringEnabled; From dea727932c6f69f43ee6255c7ef32c34e98b7fb6 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 2 Feb 2023 10:48:43 +0100 Subject: [PATCH 313/333] ArC: add a context object for the @Dependent pseudo-scope --- .../io/quarkus/arc/impl/ArcContainerImpl.java | 3 +- .../java/io/quarkus/arc/impl/Contexts.java | 28 +++++--- .../io/quarkus/arc/impl/DependentContext.java | 64 +++++++++++++++++++ .../quarkus/arc/tck/porting/ContextsImpl.java | 28 +------- .../src/test/resources/testng.xml | 22 ------- 5 files changed, 87 insertions(+), 58 deletions(-) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 2f6a85ecd8aa3e..469f6d4f76a4f7 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -177,7 +177,8 @@ public List get() { notifierOrNull(Set.of(BeforeDestroyed.Literal.REQUEST, Any.Literal.INSTANCE)), notifierOrNull(Set.of(Destroyed.Literal.REQUEST, Any.Literal.INSTANCE))), new ApplicationContext(), - new SingletonContext()); + new SingletonContext(), + new DependentContext()); // Add custom contexts for (Components c : components) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java index af2567c577b9c9..5858b982dcc874 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Contexts.java @@ -10,6 +10,7 @@ import java.util.Set; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Singleton; @@ -27,10 +28,12 @@ class Contexts { final ManagedContext requestContext; final InjectableContext applicationContext; final InjectableContext singletonContext; + final InjectableContext dependentContext; // Used to optimize the getContexts(Class) private final List applicationContextSingleton; private final List singletonContextSingleton; + private final List dependentContextSingleton; private final List requestContextSingleton; // Lazily computed list of contexts for a scope @@ -39,14 +42,16 @@ class Contexts { // Precomputed values final Set> scopes; - Contexts(ManagedContext requestContext, InjectableContext applicationContext, - InjectableContext singletonContext, Map, List> contexts) { + Contexts(ManagedContext requestContext, InjectableContext applicationContext, InjectableContext singletonContext, + InjectableContext dependentContext, Map, List> contexts) { this.requestContext = requestContext; this.applicationContext = applicationContext; this.singletonContext = singletonContext; + this.dependentContext = dependentContext; this.applicationContextSingleton = Collections.singletonList(applicationContext); this.singletonContextSingleton = Collections.singletonList(singletonContext); + this.dependentContextSingleton = Collections.singletonList(dependentContext); List requestContexts = contexts.get(RequestScoped.class); this.requestContextSingleton = requestContexts != null ? requestContexts : Collections.singletonList(requestContext); @@ -77,21 +82,24 @@ protected List computeValue(Class type) { Set> all = new HashSet<>(contexts.keySet()); all.add(ApplicationScoped.class); all.add(Singleton.class); + all.add(Dependent.class); all.add(RequestScoped.class); this.scopes = Set.copyOf(all); } else { // No custom context is registered this.unoptimizedContexts = null; - this.scopes = Set.of(ApplicationScoped.class, Singleton.class, RequestScoped.class); + this.scopes = Set.of(ApplicationScoped.class, Singleton.class, Dependent.class, RequestScoped.class); } } InjectableContext getActiveContext(Class scopeType) { - // Application/Singleton context is always active and it's not possible to register a custom context for these scopes + // Application/Singleton/Dependent context is always active and it's not possible to register a custom context for these scopes if (ApplicationScoped.class.equals(scopeType)) { return applicationContext; } else if (Singleton.class.equals(scopeType)) { return singletonContext; + } else if (Dependent.class.equals(scopeType)) { + return dependentContext; } List contextsForScope = getContexts(scopeType); InjectableContext selected = null; @@ -99,7 +107,7 @@ InjectableContext getActiveContext(Class scopeType) { if (context.isActive()) { if (selected != null) { throw new IllegalArgumentException( - "More than one context object for the given scope: " + selected + " " + context); + "More than one active context object for the given scope: " + selected + " " + context); } selected = context; } @@ -108,13 +116,15 @@ InjectableContext getActiveContext(Class scopeType) { } List getContexts(Class scopeType) { - // Optimize for buil-in normal scopes - this method is used internally during client proxy invocation + // Optimize for built-in scopes - this method is used internally during client proxy invocation if (ApplicationScoped.class.equals(scopeType)) { return applicationContextSingleton; } else if (RequestScoped.class.equals(scopeType)) { return requestContextSingleton; } else if (Singleton.class.equals(scopeType)) { return singletonContextSingleton; + } else if (Dependent.class.equals(scopeType)) { + return dependentContextSingleton; } return unoptimizedContexts != null ? unoptimizedContexts.get(scopeType) : Collections.emptyList(); } @@ -124,13 +134,15 @@ static class Builder { private final ManagedContext requestContext; private final InjectableContext applicationContext; private final InjectableContext singletonContext; + private final InjectableContext dependentContext; private final Map, List> contexts = new HashMap<>(); public Builder(ManagedContext requestContext, InjectableContext applicationContext, - InjectableContext singletonContext) { + InjectableContext singletonContext, InjectableContext dependentContext) { this.requestContext = requestContext; this.applicationContext = applicationContext; this.singletonContext = singletonContext; + this.dependentContext = dependentContext; } Builder putContext(InjectableContext context) { @@ -151,7 +163,7 @@ Contexts build() { // If a custom request context is registered then add the built-in context as well putContext(requestContext); } - return new Contexts(requestContext, applicationContext, singletonContext, contexts); + return new Contexts(requestContext, applicationContext, singletonContext, dependentContext, contexts); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java new file mode 100644 index 00000000000000..cf6919e188e2ce --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/DependentContext.java @@ -0,0 +1,64 @@ +package io.quarkus.arc.impl; + +import java.lang.annotation.Annotation; +import java.util.Objects; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.Contextual; +import jakarta.enterprise.context.spi.CreationalContext; + +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableContext; + +/** + * The built-in context for {@link jakarta.enterprise.context.Dependent}. + */ +class DependentContext implements InjectableContext { + @Override + public Class getScope() { + return Dependent.class; + } + + @Override + public T get(Contextual contextual, CreationalContext creationalContext) { + Objects.requireNonNull(contextual, "Contextual must not be null"); + + if (creationalContext == null) { + // there's never an "existing instance" of a dependent-scoped bean + return null; + } + + T instance = contextual.create(creationalContext); + if (creationalContext instanceof CreationalContextImpl) { + // we can remove this `if` and cast unconditionally after https://github.com/jakartaee/cdi-tck/pull/452 + CreationalContextImpl ccimpl = (CreationalContextImpl) creationalContext; + ccimpl.addDependentInstance((InjectableBean) contextual, instance, creationalContext); + } + return instance; + } + + @Override + public T get(Contextual contextual) { + return get(contextual, null); + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public void destroy(Contextual contextual) { + throw new UnsupportedOperationException(); + } + + @Override + public void destroy() { + throw new UnsupportedOperationException(); + } + + @Override + public ContextState getState() { + throw new UnsupportedOperationException(); + } +} diff --git a/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java b/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java index ee91e4c4273819..d87675bfa11aee 100644 --- a/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java +++ b/independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java @@ -1,13 +1,10 @@ package io.quarkus.arc.tck.porting; -import java.lang.annotation.Annotation; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.spi.Context; -import jakarta.enterprise.context.spi.Contextual; -import jakarta.enterprise.context.spi.CreationalContext; import org.jboss.cdi.tck.spi.Contexts; @@ -40,30 +37,7 @@ public Context getRequestContext() { @Override public Context getDependentContext() { - // ArC doesn't have a context object for the dependent context, let's fake it here for now - // TODO we'll likely have to implement this in ArC properly - - return new Context() { - @Override - public Class getScope() { - return Dependent.class; - } - - @Override - public T get(Contextual contextual, CreationalContext creationalContext) { - throw new UnsupportedOperationException(); - } - - @Override - public T get(Contextual contextual) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isActive() { - return true; - } - }; + return Arc.container().getActiveContext(Dependent.class); } @Override diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index d1ce5d1d664fd6..504dbce63f803c 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -192,24 +192,7 @@ - - - - - - - - - - - - - - - - - @@ -219,11 +202,6 @@ - - - - - From 704dcf347dfe2fb19b23907534e6bfe1a5b8e0a5 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 28 Apr 2023 16:15:56 +0200 Subject: [PATCH 314/333] ArC: disable interceptors/decorators without @Priority in strict mode --- .../quarkus/arc/processor/BeanDeployment.java | 10 +++- .../io/quarkus/arc/processor/Decorators.java | 12 ++++ .../quarkus/arc/processor/Interceptors.java | 7 ++- .../src/test/resources/testng.xml | 1 - .../DisabledDecoratorInStrictModeTest.java | 53 +++++++++++++++++ .../DisabledInterceptorInStrictModeTest.java | 58 +++++++++++++++++++ ...eDestroyInterceptorAndClientProxyTest.java | 7 --- 7 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 83ccfac1497a45..0dcd87b2927c7f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -1422,7 +1422,10 @@ private List findInterceptors(List injectio // Skip vetoed interceptors continue; } - interceptors.add(Interceptors.createInterceptor(interceptorClass, this, injectionPointTransformer)); + InterceptorInfo interceptor = Interceptors.createInterceptor(interceptorClass, this, injectionPointTransformer); + if (interceptor != null) { + interceptors.add(interceptor); + } } if (LOGGER.isTraceEnabled()) { for (InterceptorInfo interceptor : interceptors) { @@ -1448,7 +1451,10 @@ private List findDecorators(List injectionPoi // Skip vetoed decorators continue; } - decorators.add(Decorators.createDecorator(decoratorClass, this, injectionPointTransformer)); + DecoratorInfo decorator = Decorators.createDecorator(decoratorClass, this, injectionPointTransformer); + if (decorator != null) { + decorators.add(decorator); + } } if (LOGGER.isTraceEnabled()) { for (DecoratorInfo decorator : decorators) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java index ae8f38cbc3f73b..1192c35268f021 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Decorators.java @@ -26,6 +26,13 @@ final class Decorators { private Decorators() { } + /** + * + * @param decoratorClass + * @param beanDeployment + * @param transformer + * @return a new decorator info, or (only in strict mode) {@code null} if the decorator is disabled + */ static DecoratorInfo createDecorator(ClassInfo decoratorClass, BeanDeployment beanDeployment, InjectionPointModifier transformer) { @@ -100,6 +107,11 @@ static DecoratorInfo createDecorator(ClassInfo decoratorClass, BeanDeployment be } if (priority == null) { + if (beanDeployment.strictCompatibility) { + // decorator without `@Priority` is disabled per the specification + return null; + } + LOGGER.info("The decorator " + decoratorClass + " does not declare any @Priority. " + "It will be assigned a default priority value of 0."); priority = 0; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java index f8d0c7f6f84867..09e1678dda1b37 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java @@ -32,7 +32,7 @@ private Interceptors() { * * @param interceptorClass * @param beanDeployment - * @return a new interceptor info + * @return a new interceptor info, or (only in strict mode) {@code null} if the interceptor is disabled */ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeployment beanDeployment, InjectionPointModifier transformer) { @@ -60,6 +60,11 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym } if (priority == null) { + if (beanDeployment.strictCompatibility) { + // interceptor without `@Priority` is disabled per the specification + return null; + } + LOGGER.info("The interceptor " + interceptorClass + " does not declare any @Priority. " + "It will be assigned a default priority value of 0."); priority = 0; diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 504dbce63f803c..e471212d16fb5a 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -167,7 +167,6 @@ - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java new file mode 100644 index 00000000000000..31a3af6efed722 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/disabled/DisabledDecoratorInStrictModeTest.java @@ -0,0 +1,53 @@ +package io.quarkus.arc.test.decorators.disabled; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DisabledDecoratorInStrictModeTest { + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Converter.class, ToUpperCaseConverter.class, TrimConverterDecorator.class) + .strictCompatibility(true) + .build(); + + @Test + public void test() { + ToUpperCaseConverter converter = Arc.container().instance(ToUpperCaseConverter.class).get(); + assertEquals(" HOLA! ", converter.convert(" holA! ")); + } + + interface Converter { + T convert(T value); + } + + @Singleton + static class ToUpperCaseConverter implements Converter { + @Override + public String convert(String value) { + return value.toUpperCase(); + } + } + + @Decorator + // no @Priority, the decorator is disabled in strict mode + static class TrimConverterDecorator implements Converter { + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java new file mode 100644 index 00000000000000..0a17e441300066 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/disabled/DisabledInterceptorInStrictModeTest.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.test.interceptors.disabled; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DisabledInterceptorInStrictModeTest { + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class) + .strictCompatibility(true) + .build(); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("pong", bean.ping()); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + String ping() { + return "pong"; + } + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + // no @Priority, the interceptor is disabled in strict mode + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java index 87c45c0f8bd3f4..b996e91a8ce5e9 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/predestroy/PreDestroyInterceptorAndClientProxyTest.java @@ -25,19 +25,12 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ClientProxy; -import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.test.ArcTestContainer; public class PreDestroyInterceptorAndClientProxyTest { @RegisterExtension ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); - @Test - public void bagr() { - InstanceHandle handle = Arc.container().instance(MyBean.class); - handle.destroy(); - } - @Test public void test() { BeanManager beanManager = Arc.container().beanManager(); From 61cfc7bf74976f5856f0f8bdd131886225f22dc0 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 28 Apr 2023 16:58:50 +0200 Subject: [PATCH 315/333] ArC: fix order of BeanManager.resolveInterceptors() and resolveDecorators() Additionally, if a delegate injection point has no qualifiers, ArC doesn't generate an implementation of `getDelegateQualifiers()`, which means that the generated class implementing `Decorator` cannot be loaded. This commit adds a `default` implementation of that method to `InjectableDecorator`, returning the set of "implicit" qualifiers (`@Any` and `@Default`), just like `InjectableBean.getQualifiers()`. Finally, this commit allows finding a decorator instance by `String` ID (using `ArcContainer.bean(String)`), just like beans and interceptors. --- .../io/quarkus/arc/InjectableDecorator.java | 9 +++ .../io/quarkus/arc/impl/ArcContainerImpl.java | 25 ++++++-- .../src/test/resources/testng.xml | 1 - .../arc/test/beanmanager/BeanManagerTest.java | 64 ++++++++++++++++++- 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java index 98af86b9f6a119..da3a4fcfacde61 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableDecorator.java @@ -1,7 +1,12 @@ package io.quarkus.arc; +import java.lang.annotation.Annotation; +import java.util.Set; + import jakarta.enterprise.inject.spi.Decorator; +import io.quarkus.arc.impl.Qualifiers; + /** * Quarkus representation of a decorator bean. * This interface extends the standard CDI {@link Decorator} interface. @@ -13,4 +18,8 @@ default Kind getKind() { return Kind.DECORATOR; } + @Override + default Set getDelegateQualifiers() { + return Qualifiers.DEFAULT_QUALIFIERS; + } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 469f6d4f76a4f7..e180462c3bbbbd 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -140,7 +141,8 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str // register built-in beans addBuiltInBeans(beans); - interceptors.sort((i1, i2) -> Integer.compare(i2.getPriority(), i1.getPriority())); + interceptors.sort(Comparator.comparingInt(InjectableInterceptor::getPriority)); + decorators.sort(Comparator.comparingInt(InjectableDecorator::getPriority)); resolved = new ComputingCache<>(this::resolve); beansById = new ComputingCache<>(this::findById); @@ -589,6 +591,11 @@ private InjectableBean findById(String identifier) { return interceptorBean; } } + for (InjectableDecorator decoratorBean : decorators) { + if (decoratorBean.getIdentifier().equals(identifier)) { + return decoratorBean; + } + } return null; } @@ -819,10 +826,14 @@ List> resolveDecorators(Set types, Annotation... qualifiers) if (Objects.requireNonNull(types).isEmpty()) { throw new IllegalArgumentException("The set of bean types must not be empty"); } + if (qualifiers == null || qualifiers.length == 0) { + qualifiers = new Annotation[] { Default.Literal.INSTANCE }; + } else { + registeredQualifiers.verify(qualifiers); + } List> decorators = new ArrayList<>(); for (InjectableDecorator decorator : this.decorators) { - if (decoratorMatches(types, Set.of(qualifiers), decorator.getDelegateType(), - decorator.getDelegateQualifiers().toArray(new Annotation[] {}))) { + if (decoratorMatches(decorator.getDelegateType(), decorator.getDelegateQualifiers(), types, Set.of(qualifiers))) { decorators.add(decorator); } } @@ -867,12 +878,12 @@ private boolean matches(Set beanTypes, Set beanQualifiers, Typ return registeredQualifiers.hasQualifiers(beanQualifiers, qualifiers); } - private boolean decoratorMatches(Set beanTypes, Set beanQualifiers, Type delegateType, - Annotation... delegateQualifiers) { - if (!DelegateInjectionPointAssignabilityRules.instance().matches(delegateType, beanTypes)) { + private boolean decoratorMatches(Type delegateType, Set delegateQualifiers, Set requiredTypes, + Set requiredQualifiers) { + if (!DelegateInjectionPointAssignabilityRules.instance().matches(delegateType, requiredTypes)) { return false; } - return registeredQualifiers.hasQualifiers(beanQualifiers, delegateQualifiers); + return registeredQualifiers.hasQualifiers(delegateQualifiers, requiredQualifiers.toArray(new Annotation[0])); } static ArcContainerImpl unwrap(ArcContainer container) { diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index e471212d16fb5a..c8aec0f6296f8a 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -169,7 +169,6 @@ - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java index 99536e2a7a0780..4ab7925d0315fb 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java @@ -29,6 +29,8 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.RequestScoped; @@ -46,6 +48,7 @@ import jakarta.enterprise.inject.spi.ObserverMethod; import jakarta.enterprise.util.AnnotationLiteral; import jakarta.enterprise.util.Nonbinding; +import jakarta.enterprise.util.TypeLiteral; import jakarta.inject.Inject; import jakarta.inject.Qualifier; import jakarta.inject.Singleton; @@ -70,7 +73,8 @@ public class BeanManagerTest { .beanClasses(Legacy.class, AlternativeLegacy.class, Fool.class, LowFool.class, DummyInterceptor.class, DummyBinding.class, LowPriorityInterceptor.class, WithInjectionPointMetadata.class, High.class, Low.class, Observers.class, - BeanWithCustomQualifier.class) + BeanWithCustomQualifier.class, + ToUpperCaseConverter.class, TrimConverterDecorator.class, RepeatDecorator.class) .qualifierRegistrars(new QualifierRegistrar() { @Override public Map> getAdditionalQualifiers() { @@ -190,8 +194,25 @@ public void testResolveInterceptors() { interceptors = beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, new DummyBinding.Literal(false, true), new UselessBinding.Literal()); assertEquals(2, interceptors.size()); - assertEquals(DummyInterceptor.class, interceptors.get(0).getBeanClass()); - assertEquals(LowPriorityInterceptor.class, interceptors.get(1).getBeanClass()); + assertEquals(LowPriorityInterceptor.class, interceptors.get(0).getBeanClass()); + assertEquals(DummyInterceptor.class, interceptors.get(1).getBeanClass()); + } + + @Test + public void testResolveDecorator() { + Set converterStringTypes = Set.of(new TypeLiteral>() { + }.getType()); + + BeanManager beanManager = Arc.container().beanManager(); + List> decorators; + decorators = beanManager.resolveDecorators(converterStringTypes); + assertEquals(2, decorators.size()); + assertEquals(RepeatDecorator.class, decorators.get(0).getBeanClass()); + assertEquals(TrimConverterDecorator.class, decorators.get(1).getBeanClass()); + + assertThrows(IllegalArgumentException.class, () -> { + beanManager.resolveDecorators(converterStringTypes, new Default.Literal(), new Default.Literal()); + }); } @Test @@ -394,4 +415,41 @@ static class WithInjectionPointMetadata { } + interface Converter { + T convert(T value); + } + + @Singleton + static class ToUpperCaseConverter implements Converter { + @Override + public String convert(String value) { + return value.toUpperCase(); + } + } + + @Decorator + @Priority(10) + static class TrimConverterDecorator implements Converter { + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value.trim()); + } + } + + @Decorator + @Priority(1) + static class RepeatDecorator implements Converter { + @Inject + @Delegate + Converter delegate; + + @Override + public String convert(String value) { + return delegate.convert(value).repeat(2); + } + } } From 1514eb2ec682c16160ed6fee6d5cd7df2d037cd7 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 28 Apr 2023 17:28:44 +0200 Subject: [PATCH 316/333] ArC: treat interceptor bindings at runtime properly First and foremost, this entails transferring the full set of bindings from build time to runtime (through `ComponentsProvider`). This means that custom interceptor bindings (not annotated `@InterceptorBinding`) are known at runtime. This commit uses that runtime information to make the `BeanManager` behave as specified: the `isInterceptorBinding()` method works properly, and the `resolveInterceptors()` method does proper input validation. --- .../ComponentsProviderGenerator.java | 14 +++- .../main/java/io/quarkus/arc/Components.java | 7 ++ .../io/quarkus/arc/impl/ArcContainerImpl.java | 13 ++-- .../io/quarkus/arc/impl/BeanManagerImpl.java | 2 +- .../quarkus/arc/impl/InterceptorBindings.java | 70 +++++++++++++++++++ .../src/test/resources/testng.xml | 2 - .../arc/test/beanmanager/BeanManagerTest.java | 26 ++++++- 7 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java index 76e34376eef3a2..00b60332dc1421 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java @@ -108,6 +108,14 @@ Collection generate(String name, BeanDeployment beanDeployment, Map> entry : beanDeployment.getTransitiveInterceptorBindings().entrySet()) { ResultHandle bindingsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); @@ -160,9 +168,9 @@ Collection generate(String name, BeanDeployment beanDeployment, Map> removedBeans; private final Collection> observers; private final Collection contexts; + private final Set interceptorBindings; private final Map, Set> transitiveInterceptorBindings; private final Map> qualifierNonbindingMembers; private final Set qualifiers; public Components(Collection> beans, Collection> observers, Collection contexts, + Set interceptorBindings, Map, Set> transitiveInterceptorBindings, Supplier> removedBeans, Map> qualifierNonbindingMembers, Set qualifiers) { this.beans = beans; this.observers = observers; this.contexts = contexts; + this.interceptorBindings = interceptorBindings; this.transitiveInterceptorBindings = transitiveInterceptorBindings; this.removedBeans = removedBeans; this.qualifierNonbindingMembers = qualifierNonbindingMembers; @@ -42,6 +45,10 @@ public Collection getContexts() { return contexts; } + public Set getInterceptorBindings() { + return interceptorBindings; + } + public Map, Set> getTransitiveInterceptorBindings() { return transitiveInterceptorBindings; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index e180462c3bbbbd..2310027778dc53 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -84,7 +84,6 @@ public class ArcContainerImpl implements ArcContainer { private final List> interceptors; private final List> decorators; private final List> observers; - private final Map, Set> transitiveInterceptorBindings; private final Contexts contexts; private final ComputingCache>> resolved; private final ComputingCache> beansById; @@ -94,6 +93,7 @@ public class ArcContainerImpl implements ArcContainer { final InstanceImpl instance; final Qualifiers registeredQualifiers; + final InterceptorBindings registeredInterceptorBindings; private volatile ExecutorService executorService; @@ -110,6 +110,7 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str List> interceptors = new ArrayList<>(); List> decorators = new ArrayList<>(); List> observers = new ArrayList<>(); + Set interceptorBindings = new HashSet<>(); Map, Set> transitiveInterceptorBindings = new HashMap<>(); Map> qualifierNonbindingMembers = new HashMap<>(); Set qualifiers = new HashSet<>(); @@ -133,6 +134,7 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str } removedBeans.add(c.getRemovedBeans()); observers.addAll(c.getObservers()); + interceptorBindings.addAll(c.getInterceptorBindings()); transitiveInterceptorBindings.putAll(c.getTransitiveInterceptorBindings()); qualifierNonbindingMembers.putAll(c.getQualifierNonbindingMembers()); qualifiers.addAll(c.getQualifiers()); @@ -170,8 +172,8 @@ public List get() { return List.copyOf(removed); } }); - this.transitiveInterceptorBindings = Map.copyOf(transitiveInterceptorBindings); this.registeredQualifiers = new Qualifiers(qualifiers, qualifierNonbindingMembers); + this.registeredInterceptorBindings = new InterceptorBindings(interceptorBindings, transitiveInterceptorBindings); Contexts.Builder contextsBuilder = new Contexts.Builder( new RequestContext(this.currentContextFactory.create(RequestScoped.class), @@ -543,10 +545,6 @@ Set> getBeans(String name) { return new HashSet<>(getMatchingBeans(name)); } - Map, Set> getTransitiveInterceptorBindings() { - return transitiveInterceptorBindings; - } - boolean isScope(Class annotationType) { if (annotationType.isAnnotationPresent(Scope.class) || annotationType.isAnnotationPresent(NormalScope.class)) { return true; @@ -802,11 +800,12 @@ List> resolveInterceptors(InterceptionType type, Annotation... in if (interceptorBindings.length == 0) { throw new IllegalArgumentException("No interceptor bindings"); } + registeredInterceptorBindings.verify(interceptorBindings); List> interceptors = new ArrayList<>(); List bindings = new ArrayList<>(); for (Annotation binding : interceptorBindings) { bindings.add(binding); - Set transitive = transitiveInterceptorBindings.get(binding.annotationType()); + Set transitive = registeredInterceptorBindings.getTransitive(binding.annotationType()); if (transitive != null) { bindings.addAll(transitive); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java index 2386de23527306..aa47c4be52cbe9 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java @@ -166,7 +166,7 @@ public boolean isQualifier(Class annotationType) { @Override public boolean isInterceptorBinding(Class annotationType) { return annotationType.isAnnotationPresent(InterceptorBinding.class) - || ArcContainerImpl.instance().getTransitiveInterceptorBindings().containsKey(annotationType); + || ArcContainerImpl.instance().registeredInterceptorBindings.isRegistered(annotationType); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java new file mode 100644 index 00000000000000..0abebd30ac73b8 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptorBindings.java @@ -0,0 +1,70 @@ +package io.quarkus.arc.impl; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +public final class InterceptorBindings { + private final Set allInterceptorBindings; + private final Map, Set> transitiveInterceptorBindings; + + InterceptorBindings(Set interceptorBindings, + Map, Set> transitiveInterceptorBindings) { + this.allInterceptorBindings = interceptorBindings; + this.transitiveInterceptorBindings = transitiveInterceptorBindings; + } + + boolean isRegistered(Class annotationType) { + return allInterceptorBindings.contains(annotationType.getName()); + } + + Set getTransitive(Class interceptorBinding) { + return transitiveInterceptorBindings.get(interceptorBinding); + } + + void verify(Annotation[] interceptorBindings) { + if (interceptorBindings.length == 0) { + return; + } + if (interceptorBindings.length == 1) { + verifyInterceptorBinding(interceptorBindings[0].annotationType()); + } else { + Map, Integer> timesQualifierWasSeen = new HashMap<>(); + for (Annotation interceptorBinding : interceptorBindings) { + verifyInterceptorBinding(interceptorBinding.annotationType()); + timesQualifierWasSeen.compute(interceptorBinding.annotationType(), TimesSeenBiFunction.INSTANCE); + } + checkInterceptorBindingsForDuplicates(timesQualifierWasSeen); + } + } + + // in various cases, specification requires to check interceptor bindings for duplicates and throw IAE + private static void checkInterceptorBindingsForDuplicates(Map, Integer> timesSeen) { + timesSeen.forEach(InterceptorBindings::checkInterceptorBindingsForDuplicates); + } + + private static void checkInterceptorBindingsForDuplicates(Class annClass, Integer timesSeen) { + if (timesSeen > 1 && !annClass.isAnnotationPresent(Repeatable.class)) { + throw new IllegalArgumentException("Interceptor binding " + annClass + + " was used repeatedly but is not @Repeatable"); + } + } + + private void verifyInterceptorBinding(Class annotationType) { + if (!allInterceptorBindings.contains(annotationType.getName())) { + throw new IllegalArgumentException("Annotation is not a registered interceptor binding: " + annotationType); + } + } + + private static class TimesSeenBiFunction implements BiFunction, Integer, Integer> { + private static final TimesSeenBiFunction INSTANCE = new TimesSeenBiFunction(); + + @Override + public Integer apply(Class k, Integer v) { + return v == null ? 1 : v + 1; + } + } +} diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index c8aec0f6296f8a..f0f918ee86e922 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -167,8 +167,6 @@ - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java index 4ab7925d0315fb..2d3d096c18ad9d 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java @@ -63,6 +63,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ManagedContext; +import io.quarkus.arc.processor.InterceptorBindingRegistrar; import io.quarkus.arc.processor.QualifierRegistrar; import io.quarkus.arc.test.ArcTestContainer; @@ -71,7 +72,7 @@ public class BeanManagerTest { @RegisterExtension public ArcTestContainer container = new ArcTestContainer.Builder() .beanClasses(Legacy.class, AlternativeLegacy.class, Fool.class, LowFool.class, DummyInterceptor.class, - DummyBinding.class, + DummyBinding.class, UselessBinding.class, CustomBinding.class, LowPriorityInterceptor.class, WithInjectionPointMetadata.class, High.class, Low.class, Observers.class, BeanWithCustomQualifier.class, ToUpperCaseConverter.class, TrimConverterDecorator.class, RepeatDecorator.class) @@ -81,6 +82,12 @@ public Map> getAdditionalQualifiers() { return Map.of(DotName.createSimple(Low.class.getName()), Set.of()); } }) + .interceptorBindingRegistrars(new InterceptorBindingRegistrar() { + @Override + public List getAdditionalBindings() { + return List.of(InterceptorBinding.of(CustomBinding.class)); + } + }) .build(); @Test @@ -196,6 +203,16 @@ public void testResolveInterceptors() { assertEquals(2, interceptors.size()); assertEquals(LowPriorityInterceptor.class, interceptors.get(0).getBeanClass()); assertEquals(DummyInterceptor.class, interceptors.get(1).getBeanClass()); + + assertThrows(IllegalArgumentException.class, () -> { + // not an interceptor binding + beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, new Default.Literal()); + }); + + assertThrows(IllegalArgumentException.class, () -> { + beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, new DummyBinding.Literal(true, true), + new DummyBinding.Literal(false, false)); + }); } @Test @@ -228,6 +245,7 @@ public void testIsQualifier() { public void testIsInterceptorBinding() { BeanManager beanManager = Arc.container().beanManager(); assertTrue(beanManager.isInterceptorBinding(DummyBinding.class)); + assertTrue(beanManager.isInterceptorBinding(CustomBinding.class)); assertFalse(beanManager.isInterceptorBinding(Default.class)); } @@ -385,6 +403,12 @@ class Literal extends AnnotationLiteral implements UselessBindin } } + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + public @interface CustomBinding { + } + @DummyBinding(alpha = true, bravo = true) @Priority(10) @Interceptor From ff6bf2ebf147b37cb5142099066e5a93f5a374a9 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 1 May 2023 16:16:28 +0200 Subject: [PATCH 317/333] ArC: validate managed beans whose declaring class is generic They must be `@Dependent` per the specification. We already have similar validation for producers, this commit adds it for managed beans. --- .../test/lookup/ListInvalidTypeParamTest.java | 4 +-- .../test/CustomNonBlockingReturnTypeTest.java | 2 ++ .../java/io/quarkus/arc/processor/Beans.java | 6 ++++ .../src/test/resources/testng.xml | 5 --- .../illegal/NonDependentGenericBeanTest.java | 33 +++++++++++++++++++ ...ParameterizedBeanTypeWithVariableTest.java | 4 +-- 6 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java index 79ca7374025786..680506aa09dad9 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/lookup/ListInvalidTypeParamTest.java @@ -7,9 +7,9 @@ import java.util.List; import java.util.stream.Collectors; +import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.spi.DeploymentException; import jakarta.inject.Inject; -import jakarta.inject.Singleton; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -36,7 +36,7 @@ public void testFailure() { fail(); } - @Singleton + @Dependent static class Foo { @Inject diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java index b2c95b7836c4bd..698106c7c2bbb4 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/CustomNonBlockingReturnTypeTest.java @@ -10,6 +10,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import jakarta.enterprise.context.Dependent; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.WebApplicationException; @@ -137,6 +138,7 @@ public interface HasMessage { } @Provider + @Dependent public static class HasMessageMessageBodyWriter implements ServerMessageBodyWriter { @Override diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 621be618c9787d..5e35c2d14530d5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -1276,6 +1276,12 @@ BeanInfo create() { } else { scope = scopes.get(0); } + if (scope != null // `null` is just like `@Dependent` + && !BuiltinScope.DEPENDENT.is(scope) + && !beanClass.typeParameters().isEmpty()) { + throw new DefinitionException( + "Declaring class of a managed bean is generic, its scope must be @Dependent: " + beanClass); + } if (!isAlternative) { isAlternative = initStereotypeAlternative(stereotypes, beanDeployment); } diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index f0f918ee86e922..1ac651b1898b17 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -138,11 +138,6 @@ - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java new file mode 100644 index 00000000000000..3260007e17da40 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/illegal/NonDependentGenericBeanTest.java @@ -0,0 +1,33 @@ +package io.quarkus.arc.test.bean.illegal; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NonDependentGenericBeanTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(IllegalBean.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Declaring class of a managed bean is generic, its scope must be @Dependent")); + } + + @ApplicationScoped + static class IllegalBean { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java index 790364cc5eb58a..6e2fbc7bb2f6ef 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/discovery/ParameterizedBeanTypeWithVariableTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,7 +22,7 @@ public void testBeans() { } - @ApplicationScoped + @Dependent static class Foo { protected String ping() { From 20b665611c7b9b318fb8951772046319838d1180 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 2 May 2023 12:07:04 +0200 Subject: [PATCH 318/333] ArC: add links to TCK challenges to some test exclusions --- .../arc/tcks/cdi-tck-runner/src/test/resources/testng.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 1ac651b1898b17..f7630e55ef81d6 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -80,6 +80,7 @@ + @@ -162,6 +163,7 @@ + @@ -185,7 +187,7 @@ - + From 622a955f04b6d0e302b0196ed4f0eab0fc9541fd Mon Sep 17 00:00:00 2001 From: Marek Skacelik Date: Mon, 15 May 2023 11:27:32 +0200 Subject: [PATCH 319/333] Fixed GraphQLOpenTelemetryTests to use getSpanByKindAndParentId method insted of get --- .../GraphQLOpenTelemetryTest.java | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java index 8b54abe555d724..74a77054275538 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java @@ -22,7 +22,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.IntStream; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -84,14 +83,13 @@ void singleResultQueryTraceTest() { final List spans = Collections.unmodifiableList(spanExporter.getFinishedSpanItems(3)); assertTimeForSpans(spans); - assertHttpSpan(spans); - final SpanData operationSpan = spans.get(1); - assertEquals(spans.get(2).getSpanId(), operationSpan.getParentSpanId()); + final SpanData httpSpan = assertHttpSpan(spans); + + final SpanData operationSpan = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, httpSpan.getSpanId()); assertGraphQLSpan(operationSpan, "GraphQL:hello", "QUERY", "hello"); - final SpanData querySpan = spans.get(0); - assertEquals(operationSpan.getSpanId(), querySpan.getParentSpanId()); + final SpanData querySpan = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, operationSpan.getSpanId()); assertEquals("HelloResource.hello", querySpan.getName()); } @@ -108,14 +106,12 @@ void multipleResultQueryTraceTest() { final List spans = Collections.unmodifiableList(spanExporter.getFinishedSpanItems(3)); assertTimeForSpans(spans); - assertHttpSpan(spans); + final SpanData httpSpan = assertHttpSpan(spans); - final SpanData operationSpan = spans.get(1); - assertEquals(spans.get(2).getSpanId(), operationSpan.getParentSpanId()); + final SpanData operationSpan = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, httpSpan.getSpanId()); assertGraphQLSpan(operationSpan, "GraphQL", "QUERY", ""); - final SpanData querySpan = spans.get(0); - assertEquals(operationSpan.getSpanId(), querySpan.getParentSpanId()); + final SpanData querySpan = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, operationSpan.getSpanId()); assertEquals("HelloResource.hellos", querySpan.getName()); } @@ -159,17 +155,16 @@ private void getNestedCdiBeanTestResult(CompletableFuture[] futures, int i assertEquals(spanCollections.size(), iterations); for (List spanGroup : spanCollections) { assertTimeForSpans(spanGroup); - assertHttpSpan(spanGroup); + final SpanData httpSpan = assertHttpSpan(spanGroup); - final SpanData operationSpan = spanGroup.get(2); - assertEquals(spanGroup.get(3).getSpanId(), operationSpan.getParentSpanId()); + final SpanData operationSpan = getSpanByKindAndParentId(spanGroup, SpanKind.INTERNAL, httpSpan.getSpanId()); assertGraphQLSpan(operationSpan, "GraphQL", "QUERY", ""); - final SpanData querySpan = spanGroup.get(1); + final SpanData querySpan = getSpanByKindAndParentId(spanGroup, SpanKind.INTERNAL, operationSpan.getSpanId()); assertEquals(operationSpan.getSpanId(), querySpan.getParentSpanId()); assertEquals("HelloResource.helloAfterSecond", querySpan.getName()); - final SpanData cdiBeanSpan = spanGroup.get(0); + final SpanData cdiBeanSpan = getSpanByKindAndParentId(spanGroup, SpanKind.INTERNAL, querySpan.getSpanId()); assertEquals(querySpan.getSpanId(), cdiBeanSpan.getParentSpanId()); assertEquals("CustomCDIBean.waitForSomeTime", cdiBeanSpan.getName()); } @@ -185,14 +180,12 @@ void mutationTraceTest() { final List spans = Collections.unmodifiableList(spanExporter.getFinishedSpanItems(3)); assertTimeForSpans(spans); - assertHttpSpan(spans); + final SpanData httpSpan = assertHttpSpan(spans); - final SpanData operationSpan = spans.get(1); - assertEquals(spans.get(2).getSpanId(), operationSpan.getParentSpanId()); + final SpanData operationSpan = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, httpSpan.getSpanId()); assertGraphQLSpan(operationSpan, "GraphQL:addHello", "MUTATION", "addHello"); - final SpanData mutationSpan = spans.get(0); - assertEquals(operationSpan.getSpanId(), mutationSpan.getParentSpanId()); + final SpanData mutationSpan = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, operationSpan.getSpanId()); assertEquals("HelloResource.createHello", mutationSpan.getName()); } @@ -267,24 +260,28 @@ private Void assertSuccessfulRequestContainingMessages(String request, String... } private void assertTimeForSpans(List spans) { + // Method expects for each span to have at least one child span if (spans.size() <= 1) { return; } - IntStream.range(0, spans.size() - 1).forEach(i -> { - SpanData current = spans.get(i); - SpanData successor = spans.get(i + 1); - assertTrue(current.getStartEpochNanos() >= successor.getStartEpochNanos()); - assertTrue(current.getEndEpochNanos() <= successor.getEndEpochNanos()); - }); + SpanData current = getSpanByKindAndParentId(spans, SpanKind.SERVER, "0000000000000000"); + for (int i = 0; i < spans.size() - 1; i++) { + SpanData successor = getSpanByKindAndParentId(spans, SpanKind.INTERNAL, current.getSpanId()); + assertTrue(current.getStartEpochNanos() <= successor.getStartEpochNanos()); + assertTrue(current.getEndEpochNanos() >= successor.getEndEpochNanos()); + current = successor; + } } - private void assertHttpSpan(List spans) { + private SpanData assertHttpSpan(List spans) { final SpanData server = getSpanByKindAndParentId(spans, SpanKind.SERVER, "0000000000000000"); assertEquals("POST /graphql", server.getName()); assertEquals(HTTP_OK, server.getAttributes().get(HTTP_STATUS_CODE)); assertEquals("POST", server.getAttributes().get(HTTP_METHOD)); assertEquals("/graphql", server.getAttributes().get(HTTP_ROUTE)); + + return server; } private void assertGraphQLSpan(SpanData span, String name, String OperationType, String OperationName) { From f7c590f4b05b9206feaf50526e8fb4f83a7d62b1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 13:55:10 +0300 Subject: [PATCH 320/333] Present more useful exception when QuarkusTestResourceLifecycleManager fails Fixes: #33371 --- .../test/junit/QuarkusTestExtension.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 73c7260696caf3..c693b540d9d686 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; +import java.util.concurrent.CompletionException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; @@ -313,14 +314,17 @@ public void close() throws IOException { activateLogging(); } + Throwable effectiveException = determineEffectiveException(e); + try { if (testResourceManager != null) { testResourceManager.close(); } } catch (Exception ex) { - e.addSuppressed(ex); + effectiveException.addSuppressed(determineEffectiveException(ex)); } - throw e; + + throw effectiveException; } finally { if (originalCl != null) { Thread.currentThread().setContextClassLoader(originalCl); @@ -328,6 +332,17 @@ public void close() throws IOException { } } + private Throwable determineEffectiveException(Throwable e) { + Throwable effectiveException = e; + if ((e instanceof InvocationTargetException) && (e.getCause() != null)) { // QuarkusTestResourceLifecycleManager.start is called reflectively + effectiveException = e.getCause(); + if ((effectiveException instanceof CompletionException) && (effectiveException.getCause() != null)) { // can happen because instances of QuarkusTestResourceLifecycleManager are started asynchronously + effectiveException = effectiveException.getCause(); + } + } + return effectiveException; + } + private void shutdownHangDetection() { if (hangTaskKey != null) { hangTaskKey.cancel(true); From 712379572bb992105dd3bbcc0092a100cfea4efb Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 15:14:19 +0300 Subject: [PATCH 321/333] Update docs on running @QuarkusIntegrationTest against a running application The feature has been around for over 18 months and has not changed much, so there is no reason to still call it experimental --- docs/src/main/asciidoc/getting-started-testing.adoc | 5 ----- .../main/java/io/quarkus/test/common/TestHostLauncher.java | 2 -- 2 files changed, 7 deletions(-) diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index d7cb54870dfc6a..628392be3c4296 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1385,11 +1385,6 @@ public class CustomResource implements QuarkusTestResourceLifecycleManager, DevS === Executing against a running application -[WARNING] -==== -This feature is considered experimental and is likely to change in future versions of Quarkus. -==== - `@QuarkusIntegrationTest` supports executing tests against an already running instance of the application. This can be achieved by setting the `quarkus.http.test-host` system property when running the tests. diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/TestHostLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/TestHostLauncher.java index ee9f1d2224802c..269a2b9eae84d1 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/TestHostLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/TestHostLauncher.java @@ -7,8 +7,6 @@ * A launcher that simply sets the {@code quarkus.http.host} property based on the value {@code quarkus.http.test-host} * in order to support the case of running integration tests against an already running application * using RestAssured without any chances. - * - * This is highly experimental, so changes are to be expected. */ @SuppressWarnings("rawtypes") public class TestHostLauncher implements ArtifactLauncher { From e723c0b760cbed5d1be2d65106276f839001734c Mon Sep 17 00:00:00 2001 From: brunobat Date: Mon, 15 May 2023 12:00:04 +0100 Subject: [PATCH 322/333] Intercept and report exceptions when using the WithSpan annotation --- .../deployment/WithSpanInterceptorTest.java | 30 +++++++++++++++++++ .../tracing/cdi/WithSpanInterceptor.java | 5 ++++ 2 files changed, 35 insertions(+) diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java index ee2a43b4721651..52de43f905ee15 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java @@ -3,9 +3,13 @@ import static io.opentelemetry.api.trace.SpanKind.CLIENT; import static io.opentelemetry.api.trace.SpanKind.INTERNAL; import static io.opentelemetry.api.trace.SpanKind.SERVER; +import static io.opentelemetry.api.trace.StatusCode.ERROR; import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; import static java.net.HttpURLConnection.HTTP_OK; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; import java.util.List; @@ -27,6 +31,7 @@ import io.opentelemetry.instrumentation.annotations.SpanAttribute; import io.opentelemetry.instrumentation.annotations.WithSpan; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; import io.quarkus.runtime.StartupEvent; @@ -62,6 +67,7 @@ void span() { List spanItems = spanExporter.getFinishedSpanItems(1); assertEquals("SpanBean.span", spanItems.get(0).getName()); assertEquals(INTERNAL, spanItems.get(0).getKind()); + assertNotEquals(ERROR, spanItems.get(0).getStatus().getStatusCode()); } @Test @@ -112,6 +118,25 @@ void spanCdiRest() { final SpanData server = getSpanByKindAndParentId(spans, SERVER, client.getSpanId()); } + @Test + void spanWithException() { + try { + spanBean.spanWithException(); + fail("Exception expected"); + } catch (Exception e) { + assertThrows(RuntimeException.class, () -> { + throw e; + }); + } + List spanItems = spanExporter.getFinishedSpanItems(1); + assertEquals("SpanBean.spanWithException", spanItems.get(0).getName()); + assertEquals(INTERNAL, spanItems.get(0).getKind()); + assertEquals(ERROR, spanItems.get(0).getStatus().getStatusCode()); + assertEquals(1, spanItems.get(0).getEvents().size()); + assertEquals("spanWithException for tests", + ((ExceptionEventData) spanItems.get(0).getEvents().get(0)).getException().getMessage()); + } + @ApplicationScoped public static class SpanBean { @WithSpan @@ -119,6 +144,11 @@ public void span() { } + @WithSpan + public void spanWithException() { + throw new RuntimeException("spanWithException for tests"); + } + @WithSpan("name") public void spanName() { diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/WithSpanInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/WithSpanInterceptor.java index 49eecfad212892..2948f09f273df0 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/WithSpanInterceptor.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/WithSpanInterceptor.java @@ -70,6 +70,11 @@ public Object span(final ArcInvocationContext invocationContext) throws Exceptio } return result; + } catch (Throwable t) { + if (shouldStart) { + instrumenter.end(spanContext, methodRequest, null, t); + } + throw t; } finally { if (scope != null) { scope.close(); From 29ec2a28989af1e4e73cc61f11f6ab34a8bf9e97 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 15 May 2023 14:36:10 +0200 Subject: [PATCH 323/333] Fix documentation for Secured Routes in OpenShift Fix https://github.com/quarkusio/quarkus/issues/33275 --- .../main/asciidoc/deploying-to-openshift.adoc | 15 +++++++++++++++ .../kubernetes/deployment/RouteConfig.java | 2 +- .../kubernetes/deployment/TLSConfig.java | 19 +++++++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/deploying-to-openshift.adoc b/docs/src/main/asciidoc/deploying-to-openshift.adoc index 869c6454cfe4c9..5768aa7f4a0de4 100644 --- a/docs/src/main/asciidoc/deploying-to-openshift.adoc +++ b/docs/src/main/asciidoc/deploying-to-openshift.adoc @@ -220,6 +220,21 @@ You don't necessarily need to add this property in the `application.properties`. The same applies to all properties listed below. ==== +==== Securing the Route resource + +To secure the incoming connections, OpenShift provides several types of TLS termination to serve certifications. You can read more information about how to secure routes in https://docs.openshift.com/container-platform/4.12/networking/routes/secured-routes.html[the official OpenShift guide]. + +Let's see an example about how to configure a secured Route using passthrough termination by simply adding the "quarkus.openshift.route.tls" properties: + +[source,properties] +---- +quarkus.openshift.route.expose=true +quarkus.openshift.route.target-port=https +## Route TLS configuration: +quarkus.openshift.route.tls.termination=passthrough +quarkus.openshift.route.tls.insecure-edge-termination-policy=None +---- + === Labels To add a label in the generated resources: diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RouteConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RouteConfig.java index d581f30ce8a4a4..4e175e29724ca8 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RouteConfig.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RouteConfig.java @@ -35,7 +35,7 @@ public class RouteConfig { Map annotations; /** - * @return the TLS configuration. + * The TLS configuration for the route. */ TLSConfig tls; } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/TLSConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/TLSConfig.java index 334151df102b27..51d813b0a34912 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/TLSConfig.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/TLSConfig.java @@ -3,36 +3,43 @@ import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; @ConfigGroup public class TLSConfig { /** - * @return the cert authority certificate contents. + * The cert authority certificate contents. */ + @ConfigItem Optional caCertificate; /** - * @return the certificate contents. + * The certificate contents. */ + @ConfigItem Optional certificate; /** - * @return the contents of the ca certificate of the final destination. + * The contents of the ca certificate of the final destination. */ + @ConfigItem Optional destinationCACertificate; /** - * @return the desired behavior for insecure connections to a route. Options are: `allow`, `disable`, and `redirect`. + * The desired behavior for insecure connections to a route. */ + @ConfigItem Optional insecureEdgeTerminationPolicy; /** - * @return the key file contents. + * The key file contents. */ + @ConfigItem Optional key; /** - * @return the termination type. + * The termination type. */ + @ConfigItem Optional termination; } From 4accb98e7a45a44533f7311af0ff6df85d6cf057 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 16:29:04 +0300 Subject: [PATCH 324/333] Replace Kotlin related method with Arc utility --- .../SmallRyeReactiveMessagingProcessor.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java index c78440905aa618..d83a1ab0e44ac5 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java @@ -37,6 +37,7 @@ import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.KotlinUtils; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.Feature; @@ -240,7 +241,7 @@ public void build(SmallRyeReactiveMessagingRecorder recorder, RecorderContext re } try { - boolean isSuspendMethod = isSuspendMethod(methodInfo); + boolean isSuspendMethod = KotlinUtils.isKotlinSuspendMethod(methodInfo); QuarkusMediatorConfiguration mediatorConfiguration = QuarkusMediatorConfigurationUtil .create(methodInfo, isSuspendMethod, bean, recorderContext, @@ -276,14 +277,6 @@ public void build(SmallRyeReactiveMessagingRecorder recorder, RecorderContext re .done()); } - private boolean isSuspendMethod(MethodInfo methodInfo) { - if (!methodInfo.parameterTypes().isEmpty()) { - return methodInfo.parameterType(methodInfo.parametersCount() - 1).name() - .equals(ReactiveMessagingDotNames.CONTINUATION); - } - return false; - } - /** * Generates an invoker class that looks like the following: * From 6e052ed39f1883e47faa845f5f44fb6a82b5ec93 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 17:04:33 +0300 Subject: [PATCH 325/333] Properly handle Kotlin suspend methods in Reactive Messaging Kafka Fixes: #33308 --- ...allRyeReactiveMessagingKafkaProcessor.java | 28 +++++++++++++++++-- .../reactive/kotlin/CountriesEndpoint.kt | 9 +++--- .../kotlin/CountryNameMessageTransformer.kt | 6 ++-- .../kotlin/CountryNamePayloadTransformer.kt | 4 +-- .../reactive/kotlin/CountryNameProducer.kt | 8 +++++- .../src/main/resources/application.properties | 5 ---- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java b/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java index c78452f9455158..97357ddb04df22 100644 --- a/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java +++ b/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java @@ -25,6 +25,7 @@ import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.processor.KotlinUtils; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -358,6 +359,8 @@ private Type getIncomingTypeFromMethod(MethodInfo method) { } else if ((isSubscriber(returnType) && parametersCount == 0) || (isSubscriberBuilder(returnType) && parametersCount == 0)) { incomingType = returnType.asParameterizedType().arguments().get(0); + } else if (KotlinUtils.isKotlinSuspendMethod(method)) { + incomingType = parameterTypes.get(0); } // @Incoming @Outgoing @@ -373,6 +376,8 @@ private Type getIncomingTypeFromMethod(MethodInfo method) { incomingType = returnType.asParameterizedType().arguments().get(0); } else if (parametersCount == 1) { incomingType = parameterTypes.get(0); + } else if (KotlinUtils.isKotlinSuspendMethod(method)) { + incomingType = parameterTypes.get(0); } // @Incoming @Outgoing stream manipulation @@ -412,6 +417,8 @@ private Type getOutgoingTypeFromMethod(MethodInfo method) { outgoingType = returnType.asParameterizedType().arguments().get(0); } else if (parametersCount == 0) { outgoingType = returnType; + } else if (KotlinUtils.isKotlinSuspendMethod(method)) { + outgoingType = getReturnTypeFromKotlinSuspendMethod(method); } // @Incoming @Outgoing @@ -427,6 +434,8 @@ private Type getOutgoingTypeFromMethod(MethodInfo method) { outgoingType = returnType.asParameterizedType().arguments().get(1); } else if (parametersCount == 1) { outgoingType = returnType; + } else if (KotlinUtils.isKotlinSuspendMethod(method)) { + outgoingType = getReturnTypeFromKotlinSuspendMethod(method); } // @Incoming @Outgoing stream manipulation @@ -438,6 +447,19 @@ private Type getOutgoingTypeFromMethod(MethodInfo method) { return outgoingType; } + private static Type getReturnTypeFromKotlinSuspendMethod(MethodInfo method) { + Type continuationReturnType = method.parameterType(method.parametersCount() - 1); + + if (continuationReturnType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + Type firstGenericType = continuationReturnType.asParameterizedType().arguments().get(0); + if (firstGenericType.kind() == Type.Kind.WILDCARD_TYPE) { + return firstGenericType.asWildcardType().superBound(); + } + } + + return null; + } + private Type getOutgoingTypeFromChannelInjectionPoint(Type injectionPointType) { if (injectionPointType == null) { return null; @@ -454,8 +476,10 @@ private void processOutgoingType(DefaultSerdeDiscoveryState discovery, Type outg BiConsumer serializerAcceptor, BuildProducer generatedClass, BuildProducer reflection, Map alreadyGeneratedSerializer) { extractKeyValueType(outgoingType, (key, value, isBatch) -> { - Result keySerializer = serializerFor(discovery, key, generatedClass, reflection, alreadyGeneratedSerializer); - Result valueSerializer = serializerFor(discovery, value, generatedClass, reflection, alreadyGeneratedSerializer); + Result keySerializer = serializerFor(discovery, key, generatedClass, reflection, + alreadyGeneratedSerializer); + Result valueSerializer = serializerFor(discovery, value, generatedClass, reflection, + alreadyGeneratedSerializer); serializerAcceptor.accept(keySerializer, valueSerializer); }); } diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountriesEndpoint.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountriesEndpoint.kt index 733358f6b9d58a..20c7c8beffb396 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountriesEndpoint.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountriesEndpoint.kt @@ -13,7 +13,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient class CountriesEndpoint( @RestClient private val countriesGateway: CountriesGateway, private val countryNameConsumer: CountryNameConsumer, - @Channel("countries-emitter") private val countryEmitter: Emitter + @Channel("countries-emitter") private val countryEmitter: Emitter ) { @GET @@ -26,10 +26,11 @@ class CountriesEndpoint( @POST @Path("/kafka/{name}") - suspend fun sendCountryNameToKafka(name: String): String { + suspend fun sendCountryNameToKafka(name: String): Country { delay(50) - countryEmitter.sendSuspending(name) - return name + val country = Country(name, "capital-$name") + countryEmitter.sendSuspending(country) + return country } @GET diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameMessageTransformer.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameMessageTransformer.kt index 6b174226c13764..0bbc2b44d7094c 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameMessageTransformer.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameMessageTransformer.kt @@ -11,10 +11,8 @@ class CountryNameMessageTransformer { @Incoming("countries-t1-in") @Outgoing("countries-t2-out") - suspend fun transform(input: Message): Message { - // suspend fun transform(input: String): String { + suspend fun transform(input: Message): Message { delay(100) - return input.withPayload(input.payload.toLowerCase()) - // return input.toLowerCase() + return input.withPayload(input.payload.name.toLowerCase()) } } diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNamePayloadTransformer.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNamePayloadTransformer.kt index 217f3f7d3968eb..62ec56690c6e5c 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNamePayloadTransformer.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNamePayloadTransformer.kt @@ -10,8 +10,8 @@ class CountryNamePayloadTransformer { @Incoming("countries-in") @Outgoing("countries-t1-out") - suspend fun transform(countryName: String): String { + suspend fun transform(country: Country): Country { delay(100) - return countryName.toUpperCase() + return Country(country.name.toUpperCase(), country.capital) } } diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameProducer.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameProducer.kt index 97fd608d94bf68..3ed56d41d12b51 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameProducer.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CountryNameProducer.kt @@ -8,5 +8,11 @@ import org.eclipse.microprofile.reactive.messaging.Outgoing class CountryNameProducer { @Outgoing("countries-out") - fun generate(): Multi = Multi.createFrom().items("Greece", "USA", "France") + fun generate(): Multi = + Multi.createFrom() + .items( + Country("Greece", "Athens"), + Country("USA", "Washington D.C"), + Country("France", "Paris") + ) } diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties b/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties index 5e5d8b8138f0ca..cb24b32a3b1197 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties @@ -5,21 +5,16 @@ ft-hello/mp-rest/url=${test.url} mp.messaging.outgoing.countries-emitter.connector=smallrye-kafka mp.messaging.outgoing.countries-emitter.topic=countries -mp.messaging.outgoing.countries-emitter.value.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.outgoing.countries-out.connector=smallrye-kafka mp.messaging.outgoing.countries-out.topic=countries -mp.messaging.outgoing.countries-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.incoming.countries-in.connector=smallrye-kafka mp.messaging.incoming.countries-in.topic=countries mp.messaging.incoming.countries-in.auto.offset.reset=earliest -mp.messaging.incoming.countries-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer mp.messaging.outgoing.countries-t1-out.connector=smallrye-kafka mp.messaging.outgoing.countries-t1-out.topic=countries-t1 -mp.messaging.outgoing.countries-t1-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.incoming.countries-t1-in.connector=smallrye-kafka mp.messaging.incoming.countries-t1-in.topic=countries-t1 mp.messaging.incoming.countries-t1-in.auto.offset.reset=earliest -mp.messaging.incoming.countries-t1-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer mp.messaging.outgoing.countries-t2-out.connector=smallrye-kafka mp.messaging.outgoing.countries-t2-out.topic=countries-t2 mp.messaging.outgoing.countries-t2-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer From fd23ae19d6cf61a04d367e8c669ff9cfb99c67ff Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 17:18:56 +0300 Subject: [PATCH 326/333] Remove unnecessary serde configuration --- .../standard/src/main/resources/application.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties b/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties index cb24b32a3b1197..978550ec835eaf 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/resources/application.properties @@ -17,11 +17,9 @@ mp.messaging.incoming.countries-t1-in.topic=countries-t1 mp.messaging.incoming.countries-t1-in.auto.offset.reset=earliest mp.messaging.outgoing.countries-t2-out.connector=smallrye-kafka mp.messaging.outgoing.countries-t2-out.topic=countries-t2 -mp.messaging.outgoing.countries-t2-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.incoming.countries-t2-in.connector=smallrye-kafka mp.messaging.incoming.countries-t2-in.topic=countries-t2 mp.messaging.incoming.countries-t2-in.auto.offset.reset=earliest -mp.messaging.incoming.countries-t2-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer quarkus.package.quiltflower.enabled=true From 37f7f5b9a2700f18ff29b34ce75fe48f6b74a051 Mon Sep 17 00:00:00 2001 From: Mathias Holzer Date: Mon, 15 May 2023 16:48:44 +0200 Subject: [PATCH 327/333] Fix security-csrf-prevention.adoc Fixed typo in mention of default value for token name; fixed missing parameter type and import in code example --- docs/src/main/asciidoc/security-csrf-prevention.adoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/security-csrf-prevention.adoc b/docs/src/main/asciidoc/security-csrf-prevention.adoc index df61eebd648420..c2622274cec4f9 100644 --- a/docs/src/main/asciidoc/security-csrf-prevention.adoc +++ b/docs/src/main/asciidoc/security-csrf-prevention.adoc @@ -120,7 +120,7 @@ public class UserNameResource { The form POST request will fail with HTTP status `400` if the filter finds the hidden CSRF form field is missing, the CSRF cookie is missing, or if the CSRF form field and CSRF cookie values do not match. -At this stage no additional configuration is needed - by default the CSRF form field and cookie name will be set to `csrf_token`, and the filter will verify the token. But you can change these names if you would like: +At this stage no additional configuration is needed - by default the CSRF form field and cookie name will be set to `csrf-token`, and the filter will verify the token. But you can change these names if you would like: [source,properties] ---- @@ -241,6 +241,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.MediaType; import io.quarkus.qute.Template; @@ -263,7 +264,7 @@ public class UserNameResource { @Path("/csrfTokenForm") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_PLAIN) - public String postCsrfTokenForm(@CookieParam("csrf-token") csrfCookie, @FormParam("csrf-token") String formCsrfToken, @FormParam("name") String userName) { + public String postCsrfTokenForm(@CookieParam("csrf-token") Cookie csrfCookie, @FormParam("csrf-token") String formCsrfToken, @FormParam("name") String userName) { if (!csrfCookie.getValue().equals(formCsrfToken)) { <1> throw new BadRequestException(); } From eed217ff7c8e8a384830315c0abf971574680006 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 May 2023 17:49:17 +0300 Subject: [PATCH 328/333] Properly make check for RESTEasy Classic stricter Fixes: #33382 Follows up on: #33362 --- .../common/deployment/ResteasyReactiveCommonProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 8d86d692b62f32..e49ba79eb47031 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -82,7 +82,7 @@ void searchForProviders(Capabilities capabilities, BuildProducer producer) { if (capabilities.isPresent(Capability.RESTEASY) || capabilities.isPresent(Capability.REST_CLIENT) || QuarkusClassLoader.isClassPresentAtRuntime( - "org.jboss.resteasy.reactive.server.injection.JaxrsServerFormUrlEncodedProvider")) { // RESTEasy Classic could be imported via non-Quarkus dependencies + "org.jboss.resteasy.plugins.providers.JaxrsServerFormUrlEncodedProvider")) { // RESTEasy Classic could be imported via non-Quarkus dependencies // in this weird case we don't want the providers to be registered automatically as this would lead to multiple bean definitions return; } From bc2a6880b686133c9f1f54141e83d75325c80944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 22:02:31 +0000 Subject: [PATCH 329/333] Bump kubernetes-client-bom from 6.6.0 to 6.6.2 Bumps [kubernetes-client-bom](https://github.com/fabric8io/kubernetes-client) from 6.6.0 to 6.6.2. - [Release notes](https://github.com/fabric8io/kubernetes-client/releases) - [Changelog](https://github.com/fabric8io/kubernetes-client/blob/master/CHANGELOG.md) - [Commits](https://github.com/fabric8io/kubernetes-client/compare/v6.6.0...v6.6.2) --- updated-dependencies: - dependency-name: io.fabric8:kubernetes-client-bom 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 e59b2eb4533580..34534f51932e53 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 0.8.10 - 6.6.0 + 6.6.2 1.55.1 From fd30896b74ec789c7ab33757709785c1cd61987c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 22:03:46 +0000 Subject: [PATCH 330/333] Bump error_prone_annotations from 2.18.0 to 2.19.1 Bumps [error_prone_annotations](https://github.com/google/error-prone) from 2.18.0 to 2.19.1. - [Release notes](https://github.com/google/error-prone/releases) - [Commits](https://github.com/google/error-prone/compare/v2.18.0...v2.19.1) --- updated-dependencies: - dependency-name: com.google.errorprone:error_prone_annotations dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- 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 3a74af16f64785..15d8f888667895 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -188,7 +188,7 @@ 21.0.2 1.15.0 3.32.0 - 2.18.0 + 2.19.1 0.23.0 1.43.1 2.1 From d25a86651b1c3b624e6f4e3de48d21e29ec50ae6 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 10 May 2023 22:52:24 +1000 Subject: [PATCH 331/333] Allow other UIs to embed better Signed-off-by: Phillip Kruger --- .../branding/smallrye-graphql-ui-graphiql.css | 8 ------- .../META-INF/branding/smallrye-health-ui.css | 10 -------- .../branding/smallrye-open-api-ui.css | 10 -------- .../devui/SmallRyeGraphQLDevUIProcessor.java | 4 +++- .../devui/OpenApiDevUIProcessor.java | 6 ++++- .../dev-ui/qwc/qwc-extension-link.js | 23 +++++++++++++++++-- 6 files changed, 29 insertions(+), 32 deletions(-) diff --git a/core/deployment/src/main/resources/META-INF/branding/smallrye-graphql-ui-graphiql.css b/core/deployment/src/main/resources/META-INF/branding/smallrye-graphql-ui-graphiql.css index 8192d1d9d65379..81deb074fe840c 100644 --- a/core/deployment/src/main/resources/META-INF/branding/smallrye-graphql-ui-graphiql.css +++ b/core/deployment/src/main/resources/META-INF/branding/smallrye-graphql-ui-graphiql.css @@ -26,14 +26,6 @@ body { color: #9a9da0; } -#graphiql .topBar::after { - content: "{applicationHeader}"; - position: absolute; - padding-top: 6px; - right: 100px; - color: #9a9da0; -} - #graphiql .docExplorerShow { background: #4695eb; color: white; diff --git a/core/deployment/src/main/resources/META-INF/branding/smallrye-health-ui.css b/core/deployment/src/main/resources/META-INF/branding/smallrye-health-ui.css index 3c165e3c020d75..2c046c65cc4e6f 100644 --- a/core/deployment/src/main/resources/META-INF/branding/smallrye-health-ui.css +++ b/core/deployment/src/main/resources/META-INF/branding/smallrye-health-ui.css @@ -23,14 +23,4 @@ .nav-item h3 span { font-size: 12px -} - -.navbar::after { - content: "{applicationHeader}"; - font-size: 0.9em; - padding-right: 10px; - padding-left: 10px; - position: absolute; - right: 0px; - color: #9a9da0; } \ No newline at end of file diff --git a/core/deployment/src/main/resources/META-INF/branding/smallrye-open-api-ui.css b/core/deployment/src/main/resources/META-INF/branding/smallrye-open-api-ui.css index 6eba7644a8b497..e3d81e668f64fe 100644 --- a/core/deployment/src/main/resources/META-INF/branding/smallrye-open-api-ui.css +++ b/core/deployment/src/main/resources/META-INF/branding/smallrye-open-api-ui.css @@ -36,16 +36,6 @@ html{ min-width: 30px; } -#swagger-ui .topbar-wrapper .download-url-wrapper::after { - content: "{applicationHeader}"; - color: #9a9da0; - padding-top: 12px; - position: absolute; - right: 15px; - font-size: 0.83em; - z-index: 0; -} - @media (max-width: 1200px) { #swagger-ui .topbar-wrapper .download-url-wrapper::after { display: none; diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/devui/SmallRyeGraphQLDevUIProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/devui/SmallRyeGraphQLDevUIProcessor.java index 306658c374ba12..fc960b8ffeba0b 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/devui/SmallRyeGraphQLDevUIProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/devui/SmallRyeGraphQLDevUIProcessor.java @@ -26,7 +26,9 @@ CardPageBuildItem createCard(NonApplicationRootPathBuildItem nonApplicationRootP String uiPath = nonApplicationRootPathBuildItem.resolvePath(graphQLConfig.ui.rootPath); PageBuilder uiPage = Page.externalPageBuilder("GraphQL UI") .icon("font-awesome-solid:table-columns") - .url(uiPath); + .staticLabel("") + .url(uiPath + "/index.html?embed=true"); // Learn PageBuilder learnLink = Page.externalPageBuilder("Learn more about GraphQL") diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/devui/OpenApiDevUIProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/devui/OpenApiDevUIProcessor.java index 68d100cec746e4..e98b6a9f9f230f 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/devui/OpenApiDevUIProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/devui/OpenApiDevUIProcessor.java @@ -11,6 +11,8 @@ public class OpenApiDevUIProcessor { @BuildStep(onlyIf = IsDevelopment.class) public CardPageBuildItem pages(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { + String uiPath = nonApplicationRootPathBuildItem.resolvePath("swagger-ui"); + CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); cardPageBuildItem.addPage(Page.externalPageBuilder("Schema yaml") @@ -24,7 +26,9 @@ public CardPageBuildItem pages(NonApplicationRootPathBuildItem nonApplicationRoo .icon("font-awesome-solid:file-code")); cardPageBuildItem.addPage(Page.externalPageBuilder("Swagger UI") - .url(nonApplicationRootPathBuildItem.resolvePath("swagger-ui")) + .url(uiPath + "/index.html?embed=true") + .staticLabel("") .isHtmlContent() .icon("font-awesome-solid:signs-post")); diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js index acc17a62ef1406..c802d6126c788f 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-link.js @@ -1,4 +1,5 @@ import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element'; +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; import { JsonRpc } from 'jsonrpc'; import '@vaadin/icon'; import 'qui-badge'; @@ -9,16 +10,28 @@ import 'qui-badge'; export class QwcExtensionLink extends QwcHotReloadElement { static styles = css` + :host { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + color: var(--lumo-contrast-80pct); + font-size: small; + padding: 2px 5px; + text-decoration: none; + gap: 5px; + } .extensionLink { display: flex; flex-direction: row; justify-content: space-between; align-items: center; - color: var(--lumo-contrast); + color: var(--lumo-contrast-80pct); font-size: small; padding: 2px 5px; cursor: pointer; text-decoration: none; + gap: 5px; } .extensionLink:hover { filter: brightness(80%); @@ -200,8 +213,14 @@ export class QwcExtensionLink extends QwcHotReloadElement { _renderBadge() { if (this._effectiveLabel) { - return html`${this._effectiveLabel}`; + if(this.isHTML(this._effectiveLabel)){ + return html`${unsafeHTML(this._effectiveLabel)}`; + }else{ + return html`${this._effectiveLabel}`; + } } } + + isHTML = RegExp.prototype.test.bind(/(<([^>]+)>)/i); } customElements.define('qwc-extension-link', QwcExtensionLink); \ No newline at end of file From 307a042184fca67d582e011f2356d1c6f00b9266 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 16 May 2023 08:26:57 +0200 Subject: [PATCH 332/333] Do not build the REST Client instances in CDI wrapper constructor Creating REST Client instance in the CDI wrapper constructor makes the beans to use wrong CDI contexts (and it does not work for Request bean context). Fix https://github.com/quarkusio/quarkus/issues/33377 --- .../RestClientReactiveProcessor.java | 6 ++-- .../reactive/RestClientListenerTest.java | 15 ++++++---- .../client/reactive/TestHeaderConfig.java | 16 ++++++++++ .../reactive/TestRestClientListener.java | 13 ++++++-- .../RestClientReactiveCDIWrapperBase.java | 30 +++++++++++++++---- 5 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestHeaderConfig.java diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 558dbfe0468b3f..cd72108fecec64 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -16,6 +16,7 @@ import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.CDI_WRAPPER_SUFFIX; import static org.jboss.resteasy.reactive.common.processor.JandexUtil.isImplementorOf; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.BLOCKING; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REQUEST_SCOPED; import static org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner.BUILTIN_HTTP_ANNOTATIONS_TO_METHOD; import java.lang.annotation.RetentionPolicy; @@ -456,11 +457,12 @@ void addRestClientBeans(Capabilities capabilities, ResultHandle baseUriHandle = constructor.load(baseUri != null ? baseUri.asString() : ""); constructor.invokeSpecialMethod( MethodDescriptor.ofConstructor(RestClientReactiveCDIWrapperBase.class, Class.class, String.class, - String.class), + String.class, boolean.class), constructor.getThis(), constructor.loadClassFromTCCL(jaxrsInterface.toString()), baseUriHandle, - configKey.isPresent() ? constructor.load(configKey.get()) : constructor.loadNull()); + configKey.isPresent() ? constructor.load(configKey.get()) : constructor.loadNull(), + constructor.load(scope.getDotName().equals(REQUEST_SCOPED))); constructor.returnValue(null); // METHODS: diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/RestClientListenerTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/RestClientListenerTest.java index 94f9d2f2e27514..2141fc4d1d967e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/RestClientListenerTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/RestClientListenerTest.java @@ -1,12 +1,14 @@ package io.quarkus.rest.client.reactive; import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; -import static io.quarkus.rest.client.reactive.TestRestClientListener.HEADER_PARAM_NAME; +import static io.quarkus.rest.client.reactive.TestHeaderConfig.HEADER_PARAM_NAME; import static io.quarkus.rest.client.reactive.TestRestClientListener.HEADER_PARAM_VALUE; import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.Path; @@ -22,11 +24,13 @@ import io.quarkus.test.QuarkusUnitTest; import io.smallrye.mutiny.Uni; +@ActivateRequestContext public class RestClientListenerTest { + @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(Client.class, Resource.class, TestRestClientListener.class) + .addClasses(Client.class, Resource.class, TestHeaderConfig.class, TestRestClientListener.class) .addAsResource( new StringAsset(setUrlForClass(Client.class)), "application.properties") @@ -38,7 +42,7 @@ public class RestClientListenerTest { Client client; @Test - void shouldCallRegisteredRestClient() { + public void shouldCallRegisteredRestClient() { String result = client.get() .await().atMost(Duration.ofSeconds(10)); assertThat(result).isEqualTo(HEADER_PARAM_VALUE); @@ -47,14 +51,15 @@ void shouldCallRegisteredRestClient() { @Path("/") @RegisterRestClient @Produces(MediaType.TEXT_PLAIN) - interface Client { + @RequestScoped + public static interface Client { @GET Uni get(); } @Path("/") @Produces(MediaType.TEXT_PLAIN) - static class Resource { + public static class Resource { @GET public String get(@HeaderParam(HEADER_PARAM_NAME) String headerParam) { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestHeaderConfig.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestHeaderConfig.java new file mode 100644 index 00000000000000..2240bec47d9e2d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestHeaderConfig.java @@ -0,0 +1,16 @@ +package io.quarkus.rest.client.reactive; + +import jakarta.enterprise.context.RequestScoped; + +import io.quarkus.arc.Unremovable; + +@RequestScoped +@Unremovable +public class TestHeaderConfig { + + public static final String HEADER_PARAM_NAME = "filterheader"; + + public String getHeaderPropertyName() { + return HEADER_PARAM_NAME; + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestRestClientListener.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestRestClientListener.java index 4293243bf03309..06d1f84cc37dd7 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestRestClientListener.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestRestClientListener.java @@ -1,5 +1,6 @@ package io.quarkus.rest.client.reactive; +import jakarta.enterprise.inject.spi.CDI; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; @@ -8,19 +9,25 @@ public class TestRestClientListener implements RestClientListener { - public static final String HEADER_PARAM_NAME = "filterheader"; public static final String HEADER_PARAM_VALUE = "present"; @Override public void onNewClient(Class aClass, RestClientBuilder restClientBuilder) { - restClientBuilder.register(new TestRestClientFilter()); + String headerPropertyName = CDI.current().select(TestHeaderConfig.class).get().getHeaderPropertyName(); + restClientBuilder.register(new TestRestClientFilter(headerPropertyName)); } static class TestRestClientFilter implements ClientRequestFilter { + private final String headerPropertyName; + + public TestRestClientFilter(String headerPropertyName) { + this.headerPropertyName = headerPropertyName; + } + @Override public void filter(ClientRequestContext clientRequestContext) { - clientRequestContext.getHeaders().putSingle(HEADER_PARAM_NAME, HEADER_PARAM_VALUE); + clientRequestContext.getHeaders().putSingle(headerPropertyName, HEADER_PARAM_VALUE); } } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java index dfdbf373a1fb97..9677bc5827b718 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java @@ -13,17 +13,28 @@ public abstract class RestClientReactiveCDIWrapperBase implements Closeable, MockedThroughWrapper { private static final Logger log = Logger.getLogger(RestClientReactiveCDIWrapperBase.class); - private final T delegate; + private final Class jaxrsInterface; + private final String baseUriFromAnnotation; + private final String configKey; + + private T delegate; private Object mock; - public RestClientReactiveCDIWrapperBase(Class jaxrsInterface, String baseUriFromAnnotation, String configKey) { - this.delegate = RestClientCDIDelegateBuilder.createDelegate(jaxrsInterface, baseUriFromAnnotation, configKey); + public RestClientReactiveCDIWrapperBase(Class jaxrsInterface, String baseUriFromAnnotation, + String configKey, boolean requestScope) { + this.jaxrsInterface = jaxrsInterface; + this.baseUriFromAnnotation = baseUriFromAnnotation; + this.configKey = configKey; + if (!requestScope) { + // when not using the Request scope, we eagerly create the delegate + delegate(); + } } @Override @NoClassInterceptors public void close() throws IOException { - if (mock == null) { + if (mock == null && delegate != null) { delegate.close(); } } @@ -44,7 +55,7 @@ public void destroy() { @SuppressWarnings("unused") @NoClassInterceptors public Object getDelegate() { - return mock == null ? delegate : mock; + return mock == null ? delegate() : mock; } @Override @@ -58,4 +69,13 @@ public void setMock(Object mock) { public void clearMock() { this.mock = null; } + + @NoClassInterceptors + private T delegate() { + if (delegate == null) { + delegate = RestClientCDIDelegateBuilder.createDelegate(jaxrsInterface, baseUriFromAnnotation, configKey); + } + + return delegate; + } } From 01cdd00f0f69769b44b975d5d4c0bbd9e214d4c8 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 16 May 2023 10:28:44 +0300 Subject: [PATCH 333/333] Remove k8s client from Dependabot config This is done because we pretty don't want to blindly merge the client updates without feedback from upstream and downstream --- .github/dependabot.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cf39d2f71f148d..49a94fd0739ae5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,7 +19,6 @@ updates: - dependency-name: org.liquibase:* - dependency-name: org.liquibase.ext:* - dependency-name: org.freemarker:freemarker - - dependency-name: io.fabric8:* - dependency-name: org.apache.httpcomponents:* - dependency-name: org.apache.james:apache-mime4j - dependency-name: org.quartz-scheduler:quartz