diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 02e0c07a22bdb..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 @@ -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/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(); + } } 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 { 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/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/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/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/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/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;
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/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) {
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
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)
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