From 77c716d76772cd437e27896b822c49b29cd2ab74 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 29 Nov 2021 13:36:31 +0100 Subject: [PATCH 1/8] Resteasy Reactive: Inspect class tree for @BeanParam to find annotations The method `ClassInfo.annotations` only returns the annotations at the current class level. This PR makes the parser method to also find the inherited annotations. Fix https://github.com/quarkusio/quarkus/issues/21759 (cherry picked from commit b44601af37883a86b7b55ed874cb00d83b9e7aa5) --- .../reactive/beanparam/BeanPathParamTest.java | 51 +++++++++++++++++++ .../processor/beanparam/BeanParamParser.java | 8 +++ 2 files changed, 59 insertions(+) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanPathParamTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanPathParamTest.java index 1c05c09c7ac23..54503b61b1973 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanPathParamTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanPathParamTest.java @@ -35,6 +35,13 @@ void shouldPassPathParamFromBeanParamAndMethod() { assertThat(client.getWithBeanParam("foo", new MyBeanParam("123"))).isEqualTo("it works with method too!"); } + @Test + void shouldResolvePathParamsWhenBeanParamClassExtendsAnother() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + assertThat(client.getWithBeanParamInheritance(new MyChildBeanParam("child", "123"))).isEqualTo( + "it works with inheritance too!"); + } + @Path("/my/{id}/resource") public interface Client { @GET @@ -43,6 +50,10 @@ public interface Client { @GET @Path("/{name}") String getWithBeanParam(@PathParam("name") String name, @BeanParam MyBeanParam beanParam); + + @GET + @Path("/item/{base}") + String getWithBeanParamInheritance(@BeanParam MyChildBeanParam beanParam); } public static class MyBeanParam { @@ -58,6 +69,40 @@ public String getId() { } } + public static class MyChildBeanParam extends MyBeanParam { + private final String base; + + public MyChildBeanParam(String base, String id) { + super(id); + this.base = base; + } + + @PathParam("base") + public String getBase() { + return base; + } + } + + public static class TestMyChildBeanParam { + private final String id; + private final String base; + + public TestMyChildBeanParam(String base, String id) { + this.id = id; + this.base = base; + } + + @PathParam("id") + public String getId() { + return id; + } + + @PathParam("base") + public String getBase() { + return base; + } + } + @Path("/my/123/resource") public static class Resource { @GET @@ -70,5 +115,11 @@ public String get() { public String getWithLongerPath() { return "it works with method too!"; } + + @Path("/item/child") + @GET + public String getWithInheritance() { + return "it works with inheritance too!"; + } } } 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 a6e66a234eb29..a09a7d7e12a23 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 @@ -17,10 +17,18 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; +import org.jboss.resteasy.reactive.common.processor.JandexUtil; public class BeanParamParser { + public static List parse(ClassInfo beanParamClass, IndexView index) { List resultList = new ArrayList<>(); + + // Parse class tree recursively + if (!JandexUtil.DOTNAME_OBJECT.equals(beanParamClass.superName())) { + resultList.addAll(parse(index.getClassByName(beanParamClass.superName()), index)); + } + Map> annotations = beanParamClass.annotations(); List queryParams = annotations.get(QUERY_PARAM); if (queryParams != null) { From c56536147ac21f1d53564884a259e616aced1f49 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 29 Nov 2021 16:42:48 +0200 Subject: [PATCH 2/8] Fix GraalVM version pattern matching and add test for isJava17 The regexp group "distro" was greedy and was capturing the java version as well. (cherry picked from commit 2cfbd4f7077a464f37f353defcf467f7b95f8693) --- .../io/quarkus/deployment/pkg/steps/GraalVM.java | 3 ++- .../quarkus/deployment/pkg/steps/GraalVMTest.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java index 6db75d80ed332..0181ebf97e6de 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java @@ -8,7 +8,7 @@ final class GraalVM { static final class Version implements Comparable { private static final Pattern PATTERN = Pattern.compile( - "(GraalVM|native-image)( Version)? (?[1-9][0-9]*(\\.[0-9]+)+(-dev\\p{XDigit}*)?)(?[^\n$]*)(Java Version (?[^)]+))?\\s*"); + "(GraalVM|native-image)( Version)? (?[1-9][0-9]*(\\.[0-9]+)+(-dev\\p{XDigit}*)?)(?.*?)?(\\(Java Version (?[^)]+)\\))?$"); static final Version UNVERSIONED = new Version("Undefined", "snapshot", Distribution.ORACLE); static final Version VERSION_21_2 = new Version("GraalVM 21.2", "21.2", Distribution.ORACLE); @@ -100,6 +100,7 @@ private static boolean isMandrel(String s) { public String toString() { return "Version{" + "version=" + version + + ", fullVersion=" + fullVersion + ", distribution=" + distribution + ", javaVersion=" + javaVersion + '}'; diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java index 9df45196c4c9b..bbe66bbad6c7d 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java @@ -116,4 +116,17 @@ static void assertNewerThan(String version, String other) { assertThat(Version.of(Stream.of(version)).isNewerThan(Version.of(Stream.of(other)))).isTrue(); } + @Test + public void testGraalVMVersionIsJava17() { + Version graalVM21_3_11 = Version.of(Stream.of("GraalVM 21.3.0 Java 11 CE (Java Version 11.0.13+7-jvmci-21.3-b05)")); + assertThat(graalVM21_3_11.isJava17()).isFalse(); + Version graalVM21_3_17 = Version.of(Stream.of("GraalVM 21.3.0 Java 17 CE (Java Version 17.0.1+12-jvmci-21.3-b05)")); + assertThat(graalVM21_3_17.isJava17()).isTrue(); + Version mandrel21_3_11 = Version + .of(Stream.of("native-image 21.3.0.0-Final Mandrel Distribution (Java Version 11.0.13+8)")); + assertThat(mandrel21_3_11.isJava17()).isFalse(); + Version mandrel21_3_17 = Version + .of(Stream.of("native-image 21.3.0.0-Final Mandrel Distribution (Java Version 17.0.1+12)")); + assertThat(mandrel21_3_17.isJava17()).isTrue(); + } } From 9b584687f96c7e4061f2f6ea6412385862b7d84c Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 29 Nov 2021 13:43:11 -0500 Subject: [PATCH 3/8] Base64 encode correctly in mock event server (cherry picked from commit 2d3f799b243c3c94b7963c1a23f77b876c8829a3) --- .../io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java | 2 +- .../io/quarkus/amazon/lambda/runtime/MockRestEventServer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java b/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java index 5130a50ad374a..78276861553e6 100644 --- a/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java +++ b/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java @@ -69,7 +69,7 @@ public void handleHttpRequests(RoutingContext ctx) { if (body != null) { String ct = ctx.request().getHeader("content-type"); if (ct == null || isBinary(ct)) { - String encoded = Base64.getMimeEncoder().encodeToString(body.getBytes()); + String encoded = Base64.getEncoder().encodeToString(body.getBytes()); event.setBody(encoded); event.setIsBase64Encoded(true); } else { diff --git a/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java b/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java index c8dfbf23c8db2..83880ba6a9126 100644 --- a/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java +++ b/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java @@ -93,7 +93,7 @@ public void handleHttpRequests(RoutingContext ctx) { if (body != null) { String ct = ctx.request().getHeader("content-type"); if (ct == null || isBinary(ct)) { - String encoded = Base64.getMimeEncoder().encodeToString(body.getBytes()); + String encoded = Base64.getEncoder().encodeToString(body.getBytes()); event.setBody(encoded); event.setIsBase64Encoded(true); } else { From fc109caed461018d43b6e86a31f52ef9d3bf60e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 22:29:22 +0000 Subject: [PATCH 4/8] Bump gizmo from 1.0.9.Final to 1.0.10.Final Bumps [gizmo](https://github.com/quarkusio/gizmo) from 1.0.9.Final to 1.0.10.Final. - [Release notes](https://github.com/quarkusio/gizmo/releases) - [Commits](https://github.com/quarkusio/gizmo/compare/1.0.9.Final...1.0.10.Final) --- updated-dependencies: - dependency-name: io.quarkus.gizmo:gizmo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 0797cb554d91eb414e327f8f373715b17294379d) --- 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 02e0c07a22bdb..b0903630d72c7 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -83,7 +83,7 @@ 2.1.0 21.3.0 - 1.0.9.Final + 1.0.10.Final 2.12.5 1.0.0.Final 3.12.0 diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 0aa535307f31d..17d1d30507ccc 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -44,7 +44,7 @@ 3.21.0 3.4.2.Final 1.3.5 - 1.0.9.Final + 1.0.10.Final 2.2.3 3.0.0-M5 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 08cb5873f5f9d..edfbbe43fde7b 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -38,7 +38,7 @@ 11 5.8.1 3.21.0 - 1.0.9.Final + 1.0.10.Final 3.4.2.Final 3.0.0-M5 1.6.8 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 40561c536a58c..69c7eff2deacb 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -43,7 +43,7 @@ 3.21.0 3.4.2.Final 1.3.5 - 1.0.9.Final + 1.0.10.Final 2.2.3 3.0.0-M5 From 565784b0d2de5659f68ba1daf3b842028bae12c2 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 25 Nov 2021 18:13:43 +0000 Subject: [PATCH 5/8] Get OIDC recover from the connection failure if the discovery is disabled (cherry picked from commit 7f040cf0bdd561e03a366f56d55b6c9f82a96f84) --- .../io/quarkus/oidc/runtime/OidcRecorder.java | 13 ++++--- .../it/keycloak/CustomTenantResolver.java | 3 ++ ...UsersResourceOidcRecoveredNoDiscovery.java | 28 +++++++++++++++ .../src/main/resources/application.properties | 7 ++++ .../BearerTokenOidcRecoveredTest.java | 36 +++++++++++++++++-- 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResourceOidcRecoveredNoDiscovery.java 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 f4bea29b04773..36da33973b1e1 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 @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; import java.util.function.Function; @@ -150,8 +151,10 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf if (!oidcConfig.discoveryEnabled) { if (!isServiceApp(oidcConfig)) { if (!oidcConfig.authorizationPath.isPresent() || !oidcConfig.tokenPath.isPresent()) { - throw new OIDCException("'web-app' applications must have 'authorization-path' and 'token-path' properties " - + "set when the discovery is disabled."); + throw new ConfigurationException( + "'web-app' applications must have 'authorization-path' and 'token-path' properties " + + "set when the discovery is disabled.", + Set.of("quarkus.oidc.authorization-path", "quarkus.oidc.token-path")); } } // JWK and introspection endpoints have to be set for both 'web-app' and 'service' applications @@ -159,8 +162,9 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf if (!oidcConfig.authentication.isIdTokenRequired() && oidcConfig.authentication.isUserInfoRequired()) { LOG.debugf("tenant %s supports only UserInfo", oidcConfig.tenantId.get()); } else { - throw new OIDCException( - "Either 'jwks-path' or 'introspection-path' properties must be set when the discovery is disabled."); + throw new ConfigurationException( + "Either 'jwks-path' or 'introspection-path' properties must be set when the discovery is disabled.", + Set.of("quarkus.oidc.jwks-path", "quarkus.oidc.introspection-path")); } } } @@ -253,6 +257,7 @@ protected static Uni getJsonWebSetUni(OidcProviderClient client, .withBackOff(OidcCommonUtils.CONNECTION_BACKOFF_DURATION, OidcCommonUtils.CONNECTION_BACKOFF_DURATION) .expireIn(connectionDelayInMillisecs) .onFailure() + .transform(t -> toOidcException(t, oidcConfig.authServerUrl.get())) .invoke(client::close); } else { return client.getJsonWebKeySet(); diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java index 957f2e65f7565..08961376f000c 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java @@ -11,6 +11,9 @@ public class CustomTenantResolver implements TenantResolver { @Override public String resolve(RoutingContext context) { String path = context.normalizedPath(); + if (path.contains("recovered-no-discovery")) { + return "no-discovery"; + } if (path.endsWith("code-flow")) { return "code-flow"; } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResourceOidcRecoveredNoDiscovery.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResourceOidcRecoveredNoDiscovery.java new file mode 100644 index 0000000000000..1b1f29f27ebf9 --- /dev/null +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResourceOidcRecoveredNoDiscovery.java @@ -0,0 +1,28 @@ +package io.quarkus.it.keycloak; + +import javax.annotation.security.RolesAllowed; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.it.keycloak.model.User; +import io.quarkus.security.identity.SecurityIdentity; + +@Path("/recovered-no-discovery/api/users") +public class UsersResourceOidcRecoveredNoDiscovery { + + @Inject + SecurityIdentity identity; + + @GET + @Path("/preferredUserName") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + public User preferredUserName() { + return new User(((JsonWebToken) identity.getPrincipal()).getClaim("preferred_username")); + } +} diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index 8e5296fa397cf..347269c186f0a 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -4,6 +4,13 @@ quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret quarkus.oidc.authentication.scopes=profile,email,phone +quarkus.oidc.no-discovery.auth-server-url=http://localhost:8180/auth/realms/quarkus2/ +quarkus.oidc.no-discovery.discovery-enabled=false +quarkus.oidc.no-discovery.jwks-path=protocol/openid-connect/certs +quarkus.oidc.no-discovery.client-id=quarkus-app +quarkus.oidc.no-discovery.credentials.secret=secret +quarkus.oidc.no-discovery.authentication.scopes=profile,email,phone + quarkus.oidc.code-flow.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.code-flow.client-id=quarkus-web-app quarkus.oidc.code-flow.credentials.secret=secret diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java index 5e9f58289038c..19e6646124186 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenOidcRecoveredTest.java @@ -16,12 +16,20 @@ public class BearerTokenOidcRecoveredTest { @Test - public void testSecureAccessSuccessPreferredUsername() { + public void testOidcRecoveredWithDiscovery() { + String token = getAccessToken("alice", new HashSet<>(Arrays.asList("user", "admin"))); + // Server has not started + RestAssured.given().auth().oauth2(token) + .when().get("/recovered/api/users/preferredUserName") + .then() + .statusCode(500); + + // Server is starting now WiremockTestResource server = new WiremockTestResource(); server.start(); try { - RestAssured.given().auth().oauth2(getAccessToken("alice", new HashSet<>(Arrays.asList("user", "admin")))) + RestAssured.given().auth().oauth2(token) .when().get("/recovered/api/users/preferredUserName") .then() .statusCode(200) @@ -31,6 +39,30 @@ public void testSecureAccessSuccessPreferredUsername() { } } + @Test + public void testOidcRecoveredWithNoDiscovery() { + String token = getAccessToken("alice", new HashSet<>(Arrays.asList("user", "admin"))); + + // Server has not started + RestAssured.given().auth().oauth2(token) + .when().get("/recovered-no-discovery/api/users/preferredUserName") + .then() + .statusCode(500); + + // Server is starting now + WiremockTestResource server = new WiremockTestResource(); + server.start(); + try { + RestAssured.given().auth().oauth2(token) + .when().get("/recovered-no-discovery/api/users/preferredUserName") + .then() + .statusCode(200) + .body("userName", equalTo("alice")); + } finally { + server.stop(); + } + } + private String getAccessToken(String userName, Set groups) { return Jwt.preferredUserName(userName) .groups(groups) From 025f7d6d5c8161047e2cc7d089714e56581c5e31 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 29 Nov 2021 16:23:25 +0100 Subject: [PATCH 6/8] Qute type-safe validation - honor the TemplateAttribute annotations - resolves #21779 (cherry picked from commit 15337f96b41a911c9e8550c6fc43ad1fcd35c7cd) --- .../qute/deployment/QuteProcessor.java | 27 +++++++--------- .../TemplateExtensionMethodBuildItem.java | 16 ++++++++-- .../TemplateExtensionAttributeTest.java | 15 +++++++++ .../io/quarkus/qute/TemplateExtension.java | 2 +- .../generator/ExtensionMethodGenerator.java | 32 +++++++++---------- 5 files changed, 57 insertions(+), 35 deletions(-) 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 00873e95b7fda..3594df36ea84e 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 @@ -111,6 +111,7 @@ import io.quarkus.qute.generator.ExtensionMethodGenerator; import io.quarkus.qute.generator.ExtensionMethodGenerator.NamespaceResolverCreator; import io.quarkus.qute.generator.ExtensionMethodGenerator.NamespaceResolverCreator.ResolveCreator; +import io.quarkus.qute.generator.ExtensionMethodGenerator.Param; import io.quarkus.qute.generator.ValueResolverGenerator; import io.quarkus.qute.runtime.ContentTypes; import io.quarkus.qute.runtime.EngineProducer; @@ -1729,31 +1730,26 @@ private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info // Name does not match continue; } - List parameters = extensionMethod.getMethod().parameters(); - int realParamSize = parameters.size(); - if (!extensionMethod.hasNamespace()) { - realParamSize -= 1; - } - if (TemplateExtension.ANY.equals(extensionMethod.getMatchName())) { - realParamSize -= 1; - } - if (realParamSize > 0 && !info.isVirtualMethod()) { + + List evaluatedParams = extensionMethod.getParams().evaluated(); + if (evaluatedParams.size() > 0 && !info.isVirtualMethod()) { // If method accepts additional params the info must be a virtual method continue; } if (info.isVirtualMethod()) { // For virtual method validate the number of params and attempt to validate the parameter types if available VirtualMethodPart virtualMethod = info.part.asVirtualMethod(); + boolean isVarArgs = ValueResolverGenerator.isVarArgs(extensionMethod.getMethod()); - int lastParamIdx = parameters.size() - 1; + int lastParamIdx = evaluatedParams.size() - 1; if (isVarArgs) { // For varargs methods match the minimal number of params - if ((realParamSize - 1) > virtualMethod.getParameters().size()) { + if ((evaluatedParams.size() - 1) > virtualMethod.getParameters().size()) { continue; } } else { - if (virtualMethod.getParameters().size() != realParamSize) { + if (virtualMethod.getParameters().size() != evaluatedParams.size()) { // Check number of parameters; some of params of the extension method must be ignored continue; } @@ -1761,8 +1757,7 @@ private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info // Check parameter types if available boolean matches = true; - // Skip base and name param if needed - int idx = parameters.size() - realParamSize; + int idx = 0; for (Expression param : virtualMethod.getParameters()) { @@ -1772,9 +1767,9 @@ private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info Type paramType; if (isVarArgs && (idx >= lastParamIdx)) { // Replace the type for varargs methods - paramType = parameters.get(lastParamIdx).asArrayType().component(); + paramType = evaluatedParams.get(lastParamIdx).type.asArrayType().component(); } else { - paramType = parameters.get(idx); + paramType = evaluatedParams.get(idx).type; } if (!Types.isAssignableFrom(paramType, result.type, index)) { diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExtensionMethodBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExtensionMethodBuildItem.java index 0495521b5b47d..0bd918fff926b 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExtensionMethodBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExtensionMethodBuildItem.java @@ -9,6 +9,8 @@ import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.qute.Namespaces; import io.quarkus.qute.TemplateExtension; +import io.quarkus.qute.generator.ExtensionMethodGenerator; +import io.quarkus.qute.generator.ExtensionMethodGenerator.Parameters; /** * Represents a template extension method. @@ -24,6 +26,7 @@ public final class TemplateExtensionMethodBuildItem extends MultiBuildItem { private final Type matchType; private final int priority; private final String namespace; + private final Parameters params; public TemplateExtensionMethodBuildItem(MethodInfo method, String matchName, String matchRegex, Type matchType, int priority, String namespace) { @@ -34,6 +37,7 @@ public TemplateExtensionMethodBuildItem(MethodInfo method, String matchName, Str this.priority = priority; this.namespace = (namespace != null && !namespace.isEmpty()) ? Namespaces.requireValid(namespace) : namespace; this.matchPattern = (matchRegex == null || matchRegex.isEmpty()) ? null : Pattern.compile(matchRegex); + this.params = new ExtensionMethodGenerator.Parameters(method, matchPattern != null || matchesAny(), hasNamespace()); } public MethodInfo getMethod() { @@ -68,11 +72,19 @@ boolean matchesName(String name) { if (matchPattern != null) { return matchPattern.matcher(name).matches(); } - return TemplateExtension.ANY.equals(matchName) ? true : matchName.equals(name); + return matchesAny() ? true : matchName.equals(name); } - boolean hasNamespace() { + boolean matchesAny() { + return TemplateExtension.ANY.equals(matchName); + } + + public boolean hasNamespace() { return namespace != null && !namespace.isEmpty(); } + public Parameters getParams() { + return params; + } + } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/TemplateExtensionAttributeTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/TemplateExtensionAttributeTest.java index e669dc943f64f..b5b6873dc6308 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/TemplateExtensionAttributeTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/extensions/TemplateExtensionAttributeTest.java @@ -6,6 +6,7 @@ import javax.inject.Inject; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -19,6 +20,9 @@ public class TemplateExtensionAttributeTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar + .addAsResource( + new StringAsset("{ping:transform('Foo')}"), + "templates/foo.txt") .addClasses(Extensions.class)); @Inject @@ -34,6 +38,8 @@ public void testTemplateExtensions() { engine.parse("{foo.myAttr}").render()); assertEquals("OK", engine.parse("{attr:ping}").instance().setAttribute("myAttribute", "OK").render()); + assertEquals("foo::cs", + engine.getTemplate("foo").instance().setAttribute("locale", "cs").render()); } @TemplateExtension @@ -54,4 +60,13 @@ static String ping(@TemplateAttribute Object myAttribute) { } + @TemplateExtension(namespace = "ping") + public static class NamespaceExtensions { + + static String transform(@TemplateAttribute("locale") Object loc, String val) { + return val.toLowerCase() + "::" + loc.toString(); + } + + } + } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java index 7d8dbda352744..b66e07cf9162e 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java @@ -107,7 +107,7 @@ String namespace() default ""; /** - * Used to annotated a template extension method parameter that should be obtained via + * Used to annotate a template extension method parameter that should be obtained via * {@link TemplateInstance#getAttribute(String)}. The parameter type must be {@link java.lang.Object}. * *
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 508b75e9ab578..4faedb0025429 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
@@ -670,11 +670,11 @@ static Type box(Primitive primitive) {
         }
     }
 
-    static class Parameters implements Iterable {
+    public static final class Parameters implements Iterable {
 
         final List params;
 
-        Parameters(MethodInfo method, boolean matchAnyOrRegex, boolean hasNamespace) {
+        public Parameters(MethodInfo method, boolean matchAnyOrRegex, boolean hasNamespace) {
             List parameters = method.parameters();
             Map attributeParamNames = new HashMap<>();
             for (AnnotationInstance annotation : method.annotations()) {
@@ -721,20 +721,20 @@ static class Parameters implements Iterable {
             if (matchAnyOrRegex) {
                 Param nameParam = getFirst(ParamKind.NAME);
                 if (nameParam == null || !nameParam.type.name().equals(DotNames.STRING)) {
-                    throw new IllegalStateException(
+                    throw new TemplateException(
                             "Template extension method declared on " + method.declaringClass().name()
                                     + " must accept at least one string parameter to match the name: " + method);
                 }
             }
             if (!hasNamespace && getFirst(ParamKind.BASE) == null) {
-                throw new IllegalStateException(
+                throw new TemplateException(
                         "Template extension method declared on " + method.declaringClass().name()
                                 + " must accept at least one parameter to match the base object: " + method);
             }
 
             for (Param param : params) {
                 if (param.kind == ParamKind.ATTR && !param.type.name().equals(DotNames.OBJECT)) {
-                    throw new IllegalStateException(
+                    throw new TemplateException(
                             "Template extension method parameter annotated with @TemplateAttribute declared on "
                                     + method.declaringClass().name()
                                     + " must be of type java.lang.Object: " + method);
@@ -742,7 +742,7 @@ static class Parameters implements Iterable {
             }
         }
 
-        String[] parameterTypesAsStringArray() {
+        public String[] parameterTypesAsStringArray() {
             String[] types = new String[params.size()];
             for (int i = 0; i < params.size(); i++) {
                 types[i] = params.get(i).type.name().toString();
@@ -750,7 +750,7 @@ String[] parameterTypesAsStringArray() {
             return types;
         }
 
-        Param getFirst(ParamKind kind) {
+        public Param getFirst(ParamKind kind) {
             for (Param param : params) {
                 if (param.kind == kind) {
                     return param;
@@ -759,15 +759,15 @@ Param getFirst(ParamKind kind) {
             return null;
         }
 
-        Param get(int index) {
+        public Param get(int index) {
             return params.get(index);
         }
 
-        int size() {
+        public int size() {
             return params.size();
         }
 
-        boolean needsEvaluation() {
+        public boolean needsEvaluation() {
             for (Param param : params) {
                 if (param.kind == ParamKind.EVAL) {
                     return true;
@@ -776,7 +776,7 @@ boolean needsEvaluation() {
             return false;
         }
 
-        List evaluated() {
+        public List evaluated() {
             if (params.isEmpty()) {
                 return Collections.emptyList();
             }
@@ -796,12 +796,12 @@ public Iterator iterator() {
 
     }
 
-    static class Param {
+    public static final class Param {
 
-        final String name;
-        final Type type;
-        final int position;
-        final ParamKind kind;
+        public final String name;
+        public final Type type;
+        public final int position;
+        public final ParamKind kind;
 
         public Param(String name, Type type, int position, ParamKind paramKind) {
             this.name = name;

From 6a9baf585039fda71e8c3b27ae7a22e467817c76 Mon Sep 17 00:00:00 2001
From: Phillip Kruger 
Date: Tue, 30 Nov 2021 14:10:14 +0200
Subject: [PATCH 7/8] Updated SmallRye OpenAPI to 2.1.16

Signed-off-by: Phillip Kruger 
(cherry picked from commit bc0b811f902270cf4431c79517633e3e2f751306)
---
 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 b0903630d72c7..bed3baf31d278 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -45,7 +45,7 @@
         2.6.1
         3.1.2
         3.0.4
-        2.1.15
+        2.1.16
         1.3.5
         2.0.1
         5.2.1

From ee0685ec19d9fdd098f4a0e61f887c3b41e1946e Mon Sep 17 00:00:00 2001
From: Ladislav Thon 
Date: Thu, 25 Nov 2021 10:46:39 +0100
Subject: [PATCH 8/8] Copy default methods of a RestClient Reactive interface
 to the generated class

RestClient Reactive generates a `...$$CDIWrapper` class for each
RestClient interface, and creates an implementation of each
RestClient method. This leaves non-RestClient methods (`default`
methods) on the interface, which means that if they are annotated
with an interceptor binding, the interceptor is not invoked.
This is because interceptor bindings are not inherited from
superinterfaces, only from superclasses (and while ArC has some
support for intercepting `default` methods from superinterfaces,
it doesn't extends that far).

This PR doesn't attempt to support intercepting `default` methods
in general, because that's a gray area. Instead, it fixes how
RestClient Reactive generates the class for a RestClient interface
(because RestClient interfaces are special in that they may define
class-based beans if annotated `@RegisterRestClient`). In addition
to RestClient methods, `default` methods from the RestClient
interface are also copied to the generated class. (The "copy"
delegates to the `default` method inherited from the superinterface,
which had to be added to Gizmo, hence the Gizmo update.) That itself
is enough for interceptors to start working.

(cherry picked from commit 6b19e71a1815f26849ea8b6e69bb793a405a7085)
---
 .../RestClientReactiveProcessor.java          | 54 ++++++++++++-------
 .../rest-client-reactive/pom.xml              | 19 ++++++-
 .../client/main/ClientCallingResource.java    |  7 +++
 .../client/main/FaultToleranceClient.java     | 28 ++++++++++
 .../src/main/resources/application.properties |  1 +
 .../io/quarkus/it/rest/client/BasicTest.java  |  8 +++
 6 files changed, 98 insertions(+), 19 deletions(-)
 create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceClient.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 a9e4a01558d37..894acee5413f9 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
@@ -335,11 +335,21 @@ void addRestClientBeans(Capabilities capabilities,
             ClassInfo jaxrsInterface = registerRestClient.target().asClass();
             // for each interface annotated with @RegisterRestClient, generate a $$CDIWrapper CDI bean that can be injected
             if (Modifier.isAbstract(jaxrsInterface.flags())) {
-                List restMethods = new ArrayList<>();
-
-                // search this class and its super interfaces for jaxrs methods
-                searchForJaxRsMethods(restMethods, jaxrsInterface, index);
-                if (restMethods.isEmpty()) {
+                List methodsToImplement = new ArrayList<>();
+
+                // search this interface and its super interfaces for jaxrs methods
+                searchForJaxRsMethods(methodsToImplement, jaxrsInterface, index);
+                // search this interface for default methods
+                // we could search for default methods in super interfaces too,
+                // but emitting the correct invokespecial instruction would become convoluted
+                // (as invokespecial may only reference a method from a _direct_ super interface)
+                for (MethodInfo method : jaxrsInterface.methods()) {
+                    boolean isDefault = !Modifier.isAbstract(method.flags());
+                    if (isDefault) {
+                        methodsToImplement.add(method);
+                    }
+                }
+                if (methodsToImplement.isEmpty()) {
                     continue;
                 }
 
@@ -384,11 +394,16 @@ void addRestClientBeans(Capabilities capabilities,
                     constructor.returnValue(null);
 
                     // METHODS:
-                    for (MethodInfo method : restMethods) {
+                    for (MethodInfo method : methodsToImplement) {
                         // for each method that corresponds to making a rest call, create a method like:
                         // public JsonArray get() {
                         //      return ((InterfaceClass)this.getDelegate()).get();
                         // }
+                        //
+                        // for each default method, create a method like:
+                        // public JsonArray get() {
+                        //     return InterfaceClass.super.get();
+                        // }
                         MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.of(method));
                         methodCreator.setSignature(AsmUtil.getSignatureIfRequired(method));
 
@@ -401,22 +416,25 @@ void addRestClientBeans(Capabilities capabilities,
                             }
                         }
 
-                        ResultHandle delegate = methodCreator.invokeVirtualMethod(
-                                MethodDescriptor.ofMethod(RestClientReactiveCDIWrapperBase.class, "getDelegate",
-                                        Object.class),
-                                methodCreator.getThis());
+                        ResultHandle result;
 
                         int parameterCount = method.parameters().size();
-                        ResultHandle result;
-                        if (parameterCount == 0) {
-                            result = methodCreator.invokeInterfaceMethod(method, delegate);
-                        } else {
-                            ResultHandle[] params = new ResultHandle[parameterCount];
-                            for (int i = 0; i < parameterCount; i++) {
-                                params[i] = methodCreator.getMethodParam(i);
-                            }
+                        ResultHandle[] params = new ResultHandle[parameterCount];
+                        for (int i = 0; i < parameterCount; i++) {
+                            params[i] = methodCreator.getMethodParam(i);
+                        }
+
+                        if (Modifier.isAbstract(method.flags())) { // RestClient method
+                            ResultHandle delegate = methodCreator.invokeVirtualMethod(
+                                    MethodDescriptor.ofMethod(RestClientReactiveCDIWrapperBase.class, "getDelegate",
+                                            Object.class),
+                                    methodCreator.getThis());
+
                             result = methodCreator.invokeInterfaceMethod(method, delegate, params);
+                        } else { // default method
+                            result = methodCreator.invokeSpecialInterfaceMethod(method, methodCreator.getThis(), params);
                         }
+
                         methodCreator.returnValue(result);
                     }
                 }
diff --git a/integration-tests/rest-client-reactive/pom.xml b/integration-tests/rest-client-reactive/pom.xml
index 415a74cc99558..8271d622234ff 100644
--- a/integration-tests/rest-client-reactive/pom.xml
+++ b/integration-tests/rest-client-reactive/pom.xml
@@ -12,7 +12,6 @@
     Quarkus - Integration Tests - REST Client Reactive
 
     
-    
 
     
         
@@ -26,6 +25,11 @@
             quarkus-rest-client-reactive-jackson
         
 
+        
+            io.quarkus
+            quarkus-smallrye-fault-tolerance
+        
+
         
         
             io.quarkus
@@ -78,6 +82,19 @@
                 
             
         
+        
+            io.quarkus
+            quarkus-smallrye-fault-tolerance-deployment
+            ${project.version}
+            pom
+            test
+            
+                
+                    *
+                    *
+                
+            
+        
         
             io.quarkus
             quarkus-vertx-http-deployment
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 2c67f67c4507c..0a9a432219503 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
@@ -38,6 +38,9 @@ public class ClientCallingResource {
     @RestClient
     ClientWithExceptionMapper clientWithExceptionMapper;
 
+    @RestClient
+    FaultToleranceClient faultToleranceClient;
+
     @Inject
     InMemorySpanExporter inMemorySpanExporter;
 
@@ -132,6 +135,10 @@ void init(@Observes Router router) {
                             .stream().filter(sd -> !sd.getName().contains("export"))
                             .collect(Collectors.toList())));
         });
+
+        router.route("/call-with-fault-tolerance").blockingHandler(rc -> {
+            rc.end(faultToleranceClient.helloWithFallback());
+        });
     }
 
     private Future success(RoutingContext rc, String body) {
diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceClient.java
new file mode 100644
index 0000000000000..ceab46764717b
--- /dev/null
+++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceClient.java
@@ -0,0 +1,28 @@
+package io.quarkus.it.rest.client.main;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@Path("/unprocessable")
+@RegisterRestClient(configKey = "w-fault-tolerance")
+public interface FaultToleranceClient {
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    @Consumes(MediaType.TEXT_PLAIN)
+    String hello();
+
+    @Fallback(fallbackMethod = "fallback")
+    default String helloWithFallback() {
+        return hello();
+    }
+
+    default String fallback() {
+        return "Hello fallback!";
+    }
+}
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 c8425d08b6e93..dca0a78fb80fc 100644
--- a/integration-tests/rest-client-reactive/src/main/resources/application.properties
+++ b/integration-tests/rest-client-reactive/src/main/resources/application.properties
@@ -1,2 +1,3 @@
 w-exception-mapper/mp-rest/url=${test.url}
+w-fault-tolerance/mp-rest/url=${test.url}
 io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url}
\ No newline at end of file
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 82f666e6bad9e..0f1d890c01da6 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
@@ -84,6 +84,14 @@ void shouldMapExceptionCdi() {
                 .statusCode(200);
     }
 
+    @Test
+    void shouldInterceptDefaultMethod() {
+        RestAssured.with().body(baseUrl).post("/call-with-fault-tolerance")
+                .then()
+                .statusCode(200)
+                .body(equalTo("Hello fallback!"));
+    }
+
     @Test
     void shouldCreateClientSpans() {
         // Reset captured traces