diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 7c6d8e9a04dc8..c203fada3b809 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -39,7 +39,7 @@
2.01.21.0
- 1.13.1
+ 1.13.22.12.13.3.03.0.5
@@ -201,7 +201,7 @@
2.22.6
- 0.10.0
+ 0.11.09.25.60.0.60.1.1
@@ -6156,6 +6156,7 @@
io.quarkus.maven.javax.managedio.quarkus.maven.javax.versionsio.quarkus.jakarta-versions
+ io.quarkus.jakarta-elio.quarkus.jakarta-jaxrs-jaxbio.quarkus.jakarta-securityio.quarkus.smallrye
diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index 0f89a78fe3976..de90d71b6ab25 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -38,11 +38,6 @@
1.0.0
-
- 22.2.0
- 22.2
-
2.5.72.40.03.24.2
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java
index b93c2a21ab98f..204461779cb68 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java
@@ -1,30 +1,35 @@
package io.quarkus.deployment;
import java.io.IOException;
+import java.io.UncheckedIOException;
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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
+import java.util.StringJoiner;
import java.util.function.Consumer;
import org.eclipse.microprofile.config.Config;
-import io.quarkus.bootstrap.classloading.ClassPathElement;
-import io.quarkus.bootstrap.classloading.FilteredClassPathElement;
+import io.quarkus.bootstrap.classloading.MemoryClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.codegen.CodeGenData;
import io.quarkus.deployment.dev.DevModeContext;
import io.quarkus.deployment.dev.DevModeContext.ModuleInfo;
+import io.quarkus.maven.dependency.ResolvedDependency;
+import io.quarkus.paths.OpenPathTree;
import io.quarkus.paths.PathCollection;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigUtils;
+import io.quarkus.runtime.util.ClassPathUtils;
import io.smallrye.config.KeyMap;
import io.smallrye.config.KeyMapBackedConfigSource;
import io.smallrye.config.NameIterator;
@@ -37,7 +42,9 @@
*/
public class CodeGenerator {
- private static final String MP_CONFIG_SPI_CONFIG_SOURCE_PROVIDER = "META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider";
+ private static final List CONFIG_SOURCE_FACTORY_INTERFACES = List.of(
+ "META-INF/services/io.smallrye.config.ConfigSourceFactory",
+ "META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider");
// used by Gradle and Maven
public static void initAndRun(QuarkusClassLoader classLoader,
@@ -113,6 +120,7 @@ public static List init(ClassLoader deploymentClassLoader, Collecti
});
}
+ @SuppressWarnings("unchecked")
private static List loadCodeGenProviders(ClassLoader deploymentClassLoader)
throws CodeGenException {
Class extends CodeGenProvider> codeGenProviderClass;
@@ -174,42 +182,94 @@ public static boolean trigger(ClassLoader deploymentClassLoader,
public static Config getConfig(ApplicationModel appModel, LaunchMode launchMode, Properties buildSystemProps,
QuarkusClassLoader deploymentClassLoader) throws CodeGenException {
// Config instance that is returned by this method should be as close to the one built in the ExtensionLoader as possible
- if (appModel.getAppArtifact().getContentTree()
- .contains(MP_CONFIG_SPI_CONFIG_SOURCE_PROVIDER)) {
- final List allElements = ((QuarkusClassLoader) deploymentClassLoader).getAllElements(false);
- // we don't want to load config sources from the current module because they haven't been compiled yet
- final QuarkusClassLoader.Builder configClBuilder = QuarkusClassLoader
- .builder("CodeGenerator Config ClassLoader", QuarkusClassLoader.getSystemClassLoader(), false);
- final Collection appRoots = appModel.getAppArtifact().getContentTree().getRoots();
- for (ClassPathElement e : allElements) {
- if (appRoots.contains(e.getRoot())) {
- configClBuilder.addElement(new FilteredClassPathElement(e, List.of(MP_CONFIG_SPI_CONFIG_SOURCE_PROVIDER)));
+ final Map> appModuleConfigFactories = getConfigSourceFactoryImpl(appModel.getAppArtifact());
+ if (!appModuleConfigFactories.isEmpty()) {
+ final Map> allConfigFactories = new HashMap<>(appModuleConfigFactories.size());
+ final Map allowedConfigFactories = new HashMap<>(appModuleConfigFactories.size());
+ final Map bannedConfigFactories = new HashMap<>(appModuleConfigFactories.size());
+ for (Map.Entry> appModuleFactories : appModuleConfigFactories.entrySet()) {
+ final String factoryImpl = appModuleFactories.getKey();
+ try {
+ ClassPathUtils.consumeAsPaths(deploymentClassLoader, factoryImpl, p -> {
+ try {
+ allConfigFactories.computeIfAbsent(factoryImpl, k -> new ArrayList<>())
+ .addAll(Files.readAllLines(p));
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read " + p, e);
+ }
+ });
+ } catch (IOException e) {
+ throw new CodeGenException("Failed to read resources from classpath", e);
+ }
+ final List allFactories = allConfigFactories.getOrDefault(factoryImpl, List.of());
+ allFactories.removeAll(appModuleFactories.getValue());
+ if (allFactories.isEmpty()) {
+ bannedConfigFactories.put(factoryImpl, new byte[0]);
} else {
- configClBuilder.addElement(e);
+ final StringJoiner joiner = new StringJoiner(System.lineSeparator());
+ allFactories.forEach(f -> joiner.add(f));
+ allowedConfigFactories.put(factoryImpl, joiner.toString().getBytes());
}
}
+
+ // we don't want to load config source factories/providers from the current module because they haven't been compiled yet
+ QuarkusClassLoader.Builder configClBuilder = QuarkusClassLoader.builder("CodeGenerator Config ClassLoader",
+ deploymentClassLoader, false);
+ if (!allowedConfigFactories.isEmpty()) {
+ configClBuilder.addElement(new MemoryClassPathElement(allowedConfigFactories, true));
+ }
+ if (!bannedConfigFactories.isEmpty()) {
+ configClBuilder.addBannedElement(new MemoryClassPathElement(bannedConfigFactories, true));
+ }
deploymentClassLoader = configClBuilder.build();
}
- final SmallRyeConfigBuilder builder = ConfigUtils.configBuilder(false, launchMode)
- .forClassLoader(deploymentClassLoader);
- final PropertiesConfigSource pcs = new PropertiesConfigSource(buildSystemProps, "Build system");
- final SysPropConfigSource spcs = new SysPropConfigSource();
-
- final Map platformProperties = appModel.getPlatformProperties();
- if (platformProperties.isEmpty()) {
- builder.withSources(pcs, spcs);
- } else {
- final KeyMap props = new KeyMap<>(platformProperties.size());
- for (Map.Entry prop : platformProperties.entrySet()) {
- props.findOrAdd(new NameIterator(prop.getKey())).putRootValue(prop.getValue());
+ try {
+ final SmallRyeConfigBuilder builder = ConfigUtils.configBuilder(false, launchMode)
+ .forClassLoader(deploymentClassLoader);
+ final PropertiesConfigSource pcs = new PropertiesConfigSource(buildSystemProps, "Build system");
+ final SysPropConfigSource spcs = new SysPropConfigSource();
+
+ final Map platformProperties = appModel.getPlatformProperties();
+ if (platformProperties.isEmpty()) {
+ builder.withSources(pcs, spcs);
+ } else {
+ final KeyMap props = new KeyMap<>(platformProperties.size());
+ for (Map.Entry prop : platformProperties.entrySet()) {
+ props.findOrAdd(new NameIterator(prop.getKey())).putRootValue(prop.getValue());
+ }
+ final KeyMapBackedConfigSource platformConfigSource = new KeyMapBackedConfigSource("Quarkus platform",
+ // Our default value configuration source is using an ordinal of Integer.MIN_VALUE
+ // (see io.quarkus.deployment.configuration.DefaultValuesConfigurationSource)
+ Integer.MIN_VALUE + 1000, props);
+ builder.withSources(platformConfigSource, pcs, spcs);
}
- final KeyMapBackedConfigSource platformConfigSource = new KeyMapBackedConfigSource("Quarkus platform",
- // Our default value configuration source is using an ordinal of Integer.MIN_VALUE
- // (see io.quarkus.deployment.configuration.DefaultValuesConfigurationSource)
- Integer.MIN_VALUE + 1000, props);
- builder.withSources(platformConfigSource, pcs, spcs);
+ return builder.build();
+ } finally {
+ if (!appModuleConfigFactories.isEmpty()) {
+ deploymentClassLoader.close();
+ }
+ }
+ }
+
+ private static Map> getConfigSourceFactoryImpl(ResolvedDependency dep) throws CodeGenException {
+ final Map> configFactoryImpl = new HashMap<>(CONFIG_SOURCE_FACTORY_INTERFACES.size());
+ try (OpenPathTree openTree = dep.getContentTree().open()) {
+ for (String s : CONFIG_SOURCE_FACTORY_INTERFACES) {
+ openTree.accept(s, v -> {
+ if (v == null) {
+ return;
+ }
+ try {
+ configFactoryImpl.put(s, Files.readAllLines(v.getPath()));
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read " + v.getPath(), e);
+ }
+ });
+ }
+ } catch (IOException e) {
+ throw new CodeGenException("Failed to read " + dep.getResolvedPaths(), e);
}
- return builder.build();
+ return configFactoryImpl;
}
private static Path codeGenOutDir(Path generatedSourcesDir,
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetGAVGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetGAVGroup.java
index 5c45c4a64254a..04d083d811f8d 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetGAVGroup.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetGAVGroup.java
@@ -1,9 +1,15 @@
package io.quarkus.cli.create;
+import java.util.regex.Pattern;
+
import io.quarkus.devtools.commands.CreateProjectHelper;
import picocli.CommandLine;
+import picocli.CommandLine.TypeConversionException;
public class TargetGAVGroup {
+ static final String BAD_IDENTIFIER = "The specified %s identifier (%s) contains invalid characters. Valid characters are alphanumeric (A-Za-z), underscore, dash and dot.";
+ static final Pattern OK_ID = Pattern.compile("[0-9A-Za-z_.-]+");
+
static final String DEFAULT_GAV = CreateProjectHelper.DEFAULT_GROUP_ID + ":"
+ CreateProjectHelper.DEFAULT_ARTIFACT_ID + ":"
+ CreateProjectHelper.DEFAULT_VERSION;
@@ -52,6 +58,13 @@ void projectGav() {
}
}
}
+ if (artifactId != CreateProjectHelper.DEFAULT_ARTIFACT_ID && !OK_ID.matcher(artifactId).matches()) {
+ throw new TypeConversionException(String.format(BAD_IDENTIFIER, "artifactId", artifactId));
+ }
+ if (groupId != CreateProjectHelper.DEFAULT_GROUP_ID && !OK_ID.matcher(groupId).matches()) {
+ throw new TypeConversionException(String.format(BAD_IDENTIFIER, "groupId", groupId));
+ }
+
initialized = true;
}
}
diff --git a/devtools/cli/src/test/java/io/quarkus/cli/create/TargetGAVGroupTest.java b/devtools/cli/src/test/java/io/quarkus/cli/create/TargetGAVGroupTest.java
index a8714b43e238b..1602c0d7ac006 100644
--- a/devtools/cli/src/test/java/io/quarkus/cli/create/TargetGAVGroupTest.java
+++ b/devtools/cli/src/test/java/io/quarkus/cli/create/TargetGAVGroupTest.java
@@ -4,6 +4,7 @@
import org.junit.jupiter.api.Test;
import io.quarkus.devtools.commands.CreateProjectHelper;
+import picocli.CommandLine.TypeConversionException;
public class TargetGAVGroupTest {
@@ -92,4 +93,16 @@ void testOldParameters() {
Assertions.assertEquals("a", gav.getArtifactId());
Assertions.assertEquals("v", gav.getVersion());
}
+
+ @Test
+ void testBadArtifactId() {
+ gav.gav = "g:a/x:v";
+ Assertions.assertThrows(TypeConversionException.class, () -> gav.getArtifactId());
+ }
+
+ @Test
+ void testBadGroupId() {
+ gav.gav = "g,x:a:v";
+ Assertions.assertThrows(TypeConversionException.class, () -> gav.getGroupId());
+ }
}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java
index 44a5e3f82165f..21932062a482c 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java
@@ -12,6 +12,7 @@
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.maven.execution.MavenSession;
@@ -55,6 +56,8 @@
*/
@Mojo(name = "create", requiresProject = false)
public class CreateProjectMojo extends AbstractMojo {
+ static final String BAD_IDENTIFIER = "The specified %s identifier (%s) contains invalid characters. Valid characters are alphanumeric (A-Za-z), underscore, dash and dot.";
+ static final Pattern OK_ID = Pattern.compile("[0-9A-Za-z_.-]+");
private static final String DEFAULT_GROUP_ID = "org.acme";
private static final String DEFAULT_ARTIFACT_ID = "code-with-quarkus";
@@ -262,6 +265,13 @@ public void execute() throws MojoExecutionException {
}
askTheUserForMissingValues();
+ if (projectArtifactId != DEFAULT_ARTIFACT_ID && !OK_ID.matcher(projectArtifactId).matches()) {
+ throw new MojoExecutionException(String.format(BAD_IDENTIFIER, "artifactId", projectArtifactId));
+ }
+ if (projectGroupId != DEFAULT_GROUP_ID && !OK_ID.matcher(projectGroupId).matches()) {
+ throw new MojoExecutionException(String.format(BAD_IDENTIFIER, "groupId", projectGroupId));
+ }
+
projectRoot = new File(outputDirectory, projectArtifactId);
if (projectRoot.exists()) {
throw new MojoExecutionException("Unable to create the project, " +
diff --git a/docs/pom.xml b/docs/pom.xml
index ba48af4f52e47..be85311b46bcf 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -15,6 +15,11 @@
jar
+
+ 22.2
+ 22.2
+
2.0.01.5.0-beta.82.26.0.Final
diff --git a/docs/src/main/asciidoc/_attributes.adoc b/docs/src/main/asciidoc/_attributes.adoc
index 68551adfe147e..4af0060ec1754 100644
--- a/docs/src/main/asciidoc/_attributes.adoc
+++ b/docs/src/main/asciidoc/_attributes.adoc
@@ -3,9 +3,9 @@
:maven-version: ${proposed-maven-version}
:graalvm-version: ${graal-sdk.version-for-documentation}
-:graalvm-flavor: ${graal-sdk.version-for-documentation}-java11
+:graalvm-flavor: ${graal-sdk.version-for-documentation}-java17
:mandrel-version: ${mandrel.version-for-documentation}
-:mandrel-flavor: ${mandrel.version-for-documentation}-java11
+:mandrel-flavor: ${mandrel.version-for-documentation}-java17
:surefire-version: ${version.surefire.plugin}
:gradle-version: ${gradle-wrapper.version}
:elasticsearch-version: ${elasticsearch-server.version}
diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc
index ef223f510150c..b1e2a9ef71425 100644
--- a/docs/src/main/asciidoc/building-native-image.adoc
+++ b/docs/src/main/asciidoc/building-native-image.adoc
@@ -233,7 +233,7 @@ You can do so by prepending the flag with `-J` and passing it as additional nati
IMPORTANT: Fully static native executables support is experimental.
On Linux it's possible to package a native executable that doesn't depend on any system shared library.
-There are https://www.graalvm.org/22.1/reference-manual/native-image/StaticImages/#preparation[some system requirements] to be fulfilled and additional build arguments to be used along with the `native-image` invocation, a minimum is `-Dquarkus.native.additional-build-args="--static","--libc=musl"`.
+There are https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/guides/build-static-executables/#prerequisites-and-preparation[some system requirements] to be fulfilled and additional build arguments to be used along with the `native-image` invocation, a minimum is `-Dquarkus.native.additional-build-args="--static","--libc=musl"`.
Compiling fully static binaries is done by statically linking https://musl.libc.org/[musl] instead of `glibc` and should not be used in production without rigorous testing.
diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc
index 72b122ddb8643..f2f2087b72656 100644
--- a/docs/src/main/asciidoc/cdi-reference.adoc
+++ b/docs/src/main/asciidoc/cdi-reference.adoc
@@ -158,7 +158,7 @@ quarkus.arc.exclude-dependency.acme.artifact-id=acme-services <2>
== Native Executables and Private Members
Quarkus is using GraalVM to build a native executable.
-One of the limitations of GraalVM is the usage of https://www.graalvm.org/22.2/reference-manual/native-image/Reflection/[Reflection, window="_blank"].
+One of the limitations of GraalVM is the usage of https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/Reflection/[Reflection, window="_blank"].
Reflective operations are supported but all relevant members must be registered for reflection explicitly.
Those registrations result in a bigger native executable.
diff --git a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc
index 1a1417fc45841..bdf62b6ff05ce 100644
--- a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc
+++ b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc
@@ -53,8 +53,6 @@ follow the specific guides for more information on how to develop, package and d
== Deploying to Google App Engine Standard
-We will only cover the Java 11 runtime as the Java 8 runtime uses its own Servlet engine which is not compatible with Quarkus.
-
First, make sure to have an App Engine environment initialized for your Google Cloud project, if not, initialize one via `gcloud app create --project=[YOUR_PROJECT_ID]`.
Then, you will need to create a `src/main/appengine/app.yaml` file, let's keep it minimalistic with only the selected engine:
diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc
index 2ea9e3f06ce1e..00f248c1aebec 100644
--- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc
+++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc
@@ -625,7 +625,7 @@ containers:
memory: 64Mi
----
-=== Exposing with Secured Ingress
+=== Exposing your application in Kubernetes
Kubernetes exposes applications using https://kubernetes.io/docs/concepts/services-networking/ingress[Ingress resources]. To generate the Ingress resource, just apply the following configuration:
@@ -661,7 +661,109 @@ spec:
pathType: Prefix
----
-After deploying these resources to Kubernetes, the Ingress resource will allow unsecured connections to reach out your application.
+After deploying these resources to Kubernetes, the Ingress resource will allow unsecured connections to reach out your application.
+
+==== Adding Ingress rules
+
+To customize the default `host` and `path` properties of the generated Ingress resources, you need to apply the following configuration:
+
+[source]
+----
+quarkus.kubernetes.ingress.expose=true
+# To change the Ingress host. By default, it's empty.
+quarkus.kubernetes.ingress.host=prod.svc.url
+# To change the Ingress path of the generated Ingress rule. By default, it's "/".
+quarkus.kubernetes.ports.http.path=/prod
+----
+
+This would generate the following Ingress resource:
+
+[source, yaml]
+----
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ labels:
+ app.kubernetes.io/name: kubernetes-with-ingress
+ app.kubernetes.io/version: 0.1-SNAPSHOT
+ name: kubernetes-with-ingress
+spec:
+ rules:
+ - host: prod.svc.url
+ http:
+ paths:
+ - backend:
+ service:
+ name: kubernetes-with-ingress
+ port:
+ name: http
+ path: /prod
+ pathType: Prefix
+----
+
+Additionally, you can also add new Ingress rules by adding the following configuration:
+
+[source]
+----
+# Example to add a new rule
+quarkus.kubernetes.ingress.rules.1.host=dev.svc.url
+quarkus.kubernetes.ingress.rules.1.path=/dev
+quarkus.kubernetes.ingress.rules.1.path-type=ImplementationSpecific
+# by default, path type is Prefix
+
+# Exmple to add a new rule that use another service binding
+quarkus.kubernetes.ingress.rules.2.host=alt.svc.url
+quarkus.kubernetes.ingress.rules.2.path=/ea
+quarkus.kubernetes.ingress.rules.2.service-name=updated-service
+quarkus.kubernetes.ingress.rules.2.service-port-name=tcpurl
+----
+
+This would generate the following Ingress resource:
+
+[source, yaml]
+----
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ labels:
+ app.kubernetes.io/name: kubernetes-with-ingress
+ app.kubernetes.io/version: 0.1-SNAPSHOT
+ name: kubernetes-with-ingress
+spec:
+ rules:
+ - host: prod.svc.url
+ http:
+ paths:
+ - backend:
+ service:
+ name: kubernetes-with-ingress
+ port:
+ name: http
+ path: /prod
+ pathType: Prefix
+ - host: dev.svc.url
+ http:
+ paths:
+ - backend:
+ service:
+ name: kubernetes-with-ingress
+ port:
+ name: http
+ path: /dev
+ pathType: ImplementationSpecific
+ - host: alt.svc.url
+ http:
+ paths:
+ - backend:
+ service:
+ name: updated-service
+ port:
+ name: tcpurl
+ path: /ea
+ pathType: Prefix
+----
+
+==== Securing the Ingress resource
To secure the incoming connections, Kubernetes allows enabling https://kubernetes.io/docs/concepts/services-networking/ingress/#tls[TLS] within the Ingress resource by specifying a Secret that contains a TLS private key and certificate. You can generate a secured Ingress resource by simply adding the "tls.secret-name" properties:
@@ -1218,6 +1320,12 @@ To enable Service Binding for supported extensions, add the `quarkus-kubernetes-
* `quarkus-kafka-client`
* `quarkus-smallrye-reactive-messaging-kafka`
+
+* `quarkus-reactive-db2-client`
+* `quarkus-reactive-mssql-client`
+* `quarkus-reactive-mysql-client`
+* `quarkus-reactive-oracle-client`
+* `quarkus-reactive-pg-client`
====
diff --git a/docs/src/main/asciidoc/doc-reference.adoc b/docs/src/main/asciidoc/doc-reference.adoc
index cca2b5737ac5b..4bee5f0ee4b98 100644
--- a/docs/src/main/asciidoc/doc-reference.adoc
+++ b/docs/src/main/asciidoc/doc-reference.adoc
@@ -324,6 +324,6 @@ The complete list of externalized variables for use is given in the following ta
|\{quickstarts-tree-url}|{quickstarts-tree-url}| Quickstarts URL to main source tree root; used for referencing directories.
|\{graalvm-version}|{graalvm-version}| Recommended GraalVM version to use.
-|\{graalvm-flavor}|{graalvm-flavor}| The full version of GraalVM to use e.g. `19.3.1-java11`. Use a `java11` version.
+|\{graalvm-flavor}|{graalvm-flavor}| The builder image tag of GraalVM to use e.g. `22.2-java17`. Use a `java17` version.
|===
diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc
index 857fcf795c961..a41da1d4d991f 100644
--- a/docs/src/main/asciidoc/gradle-tooling.adoc
+++ b/docs/src/main/asciidoc/gradle-tooling.adoc
@@ -385,7 +385,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 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} (Make sure to use a Java 11 version of GraalVM).
Create a native executable using:
diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc
index 7de81548e2770..cde351f316dde 100644
--- a/docs/src/main/asciidoc/kotlin.adoc
+++ b/docs/src/main/asciidoc/kotlin.adoc
@@ -16,12 +16,6 @@ Quarkus provides first class support for using Kotlin as will be explained in th
include::{includes}/prerequisites.adoc[]
-[WARNING]
-====
-If building with Mandrel, make sure to use version Mandrel 22.1 or above, for example `ubi-quarkus-mandrel-builder-image:{mandrel-flavor}`.
-With older versions, you might encounter errors when trying to deserialize JSON documents that have null or missing fields, similar to the errors mentioned in the <> section.
-====
-
NB: For Gradle project setup please see below, and for further reference consult the guide in the xref:gradle-tooling.adoc[Gradle setup page].
== Creating the Maven project
diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc
index 1c48c6b211053..9aac9d4771223 100644
--- a/docs/src/main/asciidoc/maven-tooling.adoc
+++ b/docs/src/main/asciidoc/maven-tooling.adoc
@@ -374,7 +374,7 @@ This goal will resolve all the runtime, build time, test and dev mode dependenci
Native executables make Quarkus applications ideal for containers and serverless workloads.
-Make sure to have `GRAALVM_HOME` configured and pointing to 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}.
Verify that your `pom.xml` has the proper `native` profile (see <>).
Create a native executable using:
diff --git a/docs/src/main/asciidoc/native-and-ssl.adoc b/docs/src/main/asciidoc/native-and-ssl.adoc
index 7b126d560f2f0..c622261dc511e 100644
--- a/docs/src/main/asciidoc/native-and-ssl.adoc
+++ b/docs/src/main/asciidoc/native-and-ssl.adoc
@@ -22,7 +22,7 @@ To complete this guide, you need:
* less than 20 minutes
* an IDE
-* GraalVM (Java 11) installed with `JAVA_HOME` and `GRAALVM_HOME` configured appropriately
+* GraalVM installed with `JAVA_HOME` and `GRAALVM_HOME` configured appropriately
* Apache Maven {maven-version}
This guide is based on the REST client guide, so you should get this Maven project first.
@@ -253,7 +253,7 @@ The file containing the custom TrustStore does *not* (and probably should not) h
=== Run time configuration
-Using the runtime certificate configuration, supported by GraalVM since 21.3 does not require any special or additional configuration compared to regular java programs or Quarkus in jvm mode. See the https://www.graalvm.org/22.2/reference-manual/native-image/CertificateManagement/#run-time-options[GraalVM documentation] for more information.
+Using the runtime certificate configuration, supported by GraalVM since 21.3 does not require any special or additional configuration compared to regular java programs or Quarkus in jvm mode. See the https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/dynamic-features/CertificateManagement/#runtime-options[GraalVM documentation] for more information.
[#working-with-containers]
=== Working with containers
diff --git a/docs/src/main/asciidoc/native-reference.adoc b/docs/src/main/asciidoc/native-reference.adoc
index 5a5ab765b33bc..825609e20f156 100644
--- a/docs/src/main/asciidoc/native-reference.adoc
+++ b/docs/src/main/asciidoc/native-reference.adoc
@@ -12,14 +12,157 @@ xref:building-native-image.adoc[Building a Native Executable],
xref:native-and-ssl.adoc[Using SSL With Native Images],
and xref:writing-native-applications-tips.adoc[Writing Native Applications],
guides.
-It provides further details to debugging issues in Quarkus native executables that might arise during development or production.
+It explores advanced topics that help users diagnose issues,
+increase the reliability and improve the runtime performance of native executables.
+These are the high level sections to be found in this guide:
+
+* <>
+* <>
+* <>
+
+[[native-memory-management]]
+== Native Memory Management
+Memory management for Quarkus native executables is enabled by GraalVM’s SubstrateVM runtime system.
+The memory management component in GraalVM is explained in detail
+link:https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/optimizations-and-performance/MemoryManagement[here].
+This guide complements the information available in the GraalVM website with further observations particularly relevant to Quarkus applications.
+
+=== Garbage Collectors
+The garbage collectors available for Quarkus users are currently Serial GC and Epsilon GC.
+
+==== Serial GC
+Serial GC, the default option in GraalVM and Quarkus, is a single-threaded non-concurrent GC, just like HotSpot’s Serial GC.
+The implementation in GraalVM however is different from the HotSpot one,
+and there can be significant differences in the runtime behavior.
+
+One of the key differences between HotSpot’s Serial GC and GraalVM’s Serial GC is the way they perform full GC cycles.
+In HotSpot the algorithm used is mark-sweep-compact whereas in GraalVM it is mark-copy.
+Both need to traverse all live objects,
+but in mark-copy this traversal is also used to copy live objects to a secondary space or semi-space.
+As objects are copied from one semi-space to another they’re also compacted.
+In mark-sweep-compact, the compacting requires a second pass on the live objects.
+This makes full GCs in mark-copy more time efficient (in terms of time spent in each GC cycle) than mark-sweep-compact.
+The tradeoff mark-copy makes in order to make individual full GC cycles shorter is space.
+The use of semi-spaces means that for an application to maintain the same GC performance that mark-sweep achieves (in terms of allocated MB per second),
+it requires double the amount of memory.
+
+===== GC Collection Policy
+
+GraalVM's Serial GC implementation offers a choice between two different collection policies, the default is called "adaptive" and the alternative is called "space/time".
+
+The “adaptive” collection policy is based on HotSpot's ParallelGC adaptive size policy.
+The main difference with HotSpot is GraalVM's focus on memory footprint.
+This means that GraalVM’s adaptive GC policy tries to aggressively trigger GCs in order to keep memory consumption down.
+
+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,
+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].
+
+It is still possible to change the GC collection policy using GraalVM’s `-H:InitialCollectionPolicy` flag.
+Switching to the "space/time" policy can be done by passing the following via command line:
-This reference guide takes as input the application developed in the xref:getting-started.adoc[Getting Started Guide].
+[source,bash]
+----
+-Dquarkus.native.additional-build-args=-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy\$BySpaceAndTime
+----
+
+Or adding this to the `application.properties` file:
+
+[source,properties]
+----
+quarkus.native.additional-build-args=-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime
+----
+
+[NOTE]
+====
+Escaping the `$` character is required to configure the "space/time" GC collection policy if passing via command line in Bash.
+Other command line environments might have similar requirements.
+====
+
+==== Epsilon GC
+Epsilon GC is a no-op garbage collector which does not do any memory reclamation.
+From a Quarkus perspective, some of the most relevant use cases for this garbage collector are extremely short-lived jobs, e.g. serverless functions.
+To build Quarkus native with epsilon GC, pass the following argument at build time:
+
+[source,bash]
+----
+-Dquarkus.native.additional-build-args=--gc=epsilon
+----
+
+=== Memory Management Options
+Options to control maximum heap size, young space and other typical use cases found in the JVM can be found in
+https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/optimizations-and-performance/MemoryManagement[the GraalVM memory management guide].
+Setting the maximum heap size, either as a percentage or an explicit value, is generally recommended.
+
+[[gc-logging]]
+=== GC Logging
+Multiple options exist to print information about garbage collection cycles, depending on the level of detail required.
+The minimum detail is provided `-XX:+PrintGC`, which prints a message for each GC cycle that occurs:
+
+[source,bash]
+----
+$ quarkus-project-0.1-SNAPSHOT-runner -XX:+PrintGC -Xmx64m
+...
+[Incremental GC (CollectOnAllocation) 20480K->11264K, 0.0003223 secs]
+[Full GC (CollectOnAllocation) 19456K->5120K, 0.0031067 secs]
+----
+
+When you combine this option with `-XX:+VerboseGC` you still get a message per GC cycle,
+but it contains extra information.
+Also, adding this option shows the sizing decisions made by the GC algorithm at startup:
+
+[source,bash]
+----
+$ quarkus-project-0.1-SNAPSHOT-runner -XX:+PrintGC -XX:+VerboseGC -Xmx64m
+[Heap policy parameters:
+YoungGenerationSize: 25165824
+MaximumHeapSize: 67108864
+MinimumHeapSize: 33554432
+AlignedChunkSize: 1048576
+LargeArrayThreshold: 131072]
+...
+[[5378479783321 GC: before epoch: 8 cause: CollectOnAllocation]
+[Incremental GC (CollectOnAllocation) 16384K->9216K, 0.0003847 secs]
+[5378480179046 GC: after epoch: 8 cause: CollectOnAllocation policy: adaptive type: incremental
+collection time: 384755 nanoSeconds]]
+[[5379294042918 GC: before epoch: 9 cause: CollectOnAllocation]
+[Full GC (CollectOnAllocation) 17408K->5120K, 0.0030556 secs]
+[5379297109195 GC: after epoch: 9 cause: CollectOnAllocation policy: adaptive type: complete
+collection time: 3055697 nanoSeconds]]
+----
+
+Beyond these two options, `-XX:+PrintHeapShape` and `-XX:+TraceHeapChunks` provide even lower level details about memory chunks on top of which the different memory regions are constructed.
+
+The most up-to-date information on GC logging flags can be obtained by printing the list of flags that can be passed to native executables:
+
+[source,bash]
+----
+$ quarkus-project-0.1-SNAPSHOT-runner -XX:PrintFlags=
+...
+ -XX:±PrintGC Print summary GC information after each collection. Default: - (disabled).
+ -XX:±PrintGCSummary Print summary GC information after application main method returns. Default: - (disabled).
+ -XX:±PrintGCTimeStamps Print a time stamp at each collection, if +PrintGC or +VerboseGC. Default: - (disabled).
+ -XX:±PrintGCTimes Print the time for each of the phases of each collection, if +VerboseGC. Default: - (disabled).
+ -XX:±PrintHeapShape Print the shape of the heap before and after each collection, if +VerboseGC. Default: - (disabled).
+...
+ -XX:±TraceHeapChunks Trace heap chunks during collections, if +VerboseGC and +PrintHeapShape. Default: - (disabled).
+ -XX:±VerboseGC Print more information about the heap before and after each collection. Default: - (disabled).
+----
+
+[[inspecting-and-debugging]]
+== Inspecting and Debugging Native Executables
+This debugging guide provides further details on debugging issues in Quarkus native executables that might arise during development or production.
+
+It takes as input the application developed in the xref:getting-started.adoc[Getting Started Guide].
You can find instructions on how to quickly set up this application in this guide.
-== Requirements and Assumptions
+=== Requirements and Assumptions
-This guide has the following requirements:
+This debugging guide has the following requirements:
* JDK 11 installed with `JAVA_HOME` configured appropriately
* Apache Maven {maven-version}
@@ -40,7 +183,7 @@ A minimum of 4 CPUs and 4GB of memory is required.
Finally, this guide assumes the use of the link:https://github.com/graalvm/mandrel[Mandrel distribution] of GraalVM for building native executables,
and these are built within a container so there is no need for installing Mandrel on the host.
-== Bootstrapping the project
+=== Bootstrapping the project
Start by creating a new Quarkus project.
Open a terminal and run the following command:
@@ -57,9 +200,9 @@ For Windows users
- If using cmd , (don't use backward slash `\` and put everything on the same line)
- If using Powershell , wrap `-D` parameters in double quotes e.g. `"-DprojectArtifactId=debugging-native"`
-== Configure Quarkus properties
+=== Configure Quarkus properties
-Some Quarkus configuration options will be used constantly throughout this guide,
+Some Quarkus configuration options will be used constantly throughout this debugging guide,
so to help declutter command line invocations,
it's recommended to add these options to the `application.properties` file.
So, go ahead and add the following options to that file:
@@ -72,7 +215,7 @@ quarkus.container-image.build=true
quarkus.container-image.group=test
----
-== First Debugging Steps
+=== First Debugging Steps
As a first step, change to the project directory and build the native executable for the application:
@@ -190,7 +333,7 @@ Remember that if an argument for `-Dquarkus.native.additional-build-args` includ
it needs to be escaped to be processed correctly, e.g. `\\,`.
====
-== Inspecting Native Executables
+=== Inspecting Native Executables
Given a native executable, various Linux tools can be used to inspect it.
To allow supporting a variety of environments,
@@ -276,7 +419,7 @@ From there, you can either inspect the executable directly or use a tools contai
====
[[native-reports]]
-== Native Reports
+=== Native Reports
Optionally, the native build process can generate reports that show what goes into the binary:
@@ -289,7 +432,7 @@ Optionally, the native build process can generate reports that show what goes in
The reports will be created under `target/debugging-native-1.0.0-SNAPSHOT-native-image-source-jar/reports/`.
These reports are some of the most useful resources when encountering issues with missing methods/classes, or encountering forbidden methods by Mandrel.
-=== Call Tree Reports
+==== Call Tree Reports
`call_tree` csv file reports are some of the default reports generated when the `-Dquarkus.native.enable-reports` option is passed in.
These csv files can be imported into a graph database, such as Neo4j,
@@ -475,12 +618,12 @@ For further information, check out this
link:https://quarkus.io/blog/quarkus-native-neo4j-call-tree[blog post]
that explores the Quarkus Hibernate ORM quickstart using the techniques explained above.
-=== Used Packages/Classes/Methods Reports
+==== Used Packages/Classes/Methods Reports
`used_packages`, `used_classes` and `used_methods` text file reports come in handy when comparing different versions of the application,
e.g. why does the image take longer to build? Or why is the image bigger now?
-=== Further Reports
+==== Further Reports
Mandrel can produce further reports beyond the ones that are enabled with the `-Dquarkus.native.enable-reports` option.
These are called expert options and you can learn more about them by running:
@@ -498,7 +641,7 @@ so they might change anytime.
To use these expert options, add them comma separated to the `-Dquarkus.native.additional-build-args` parameter.
-== Build-time vs Run-time Initialization
+=== Build-time vs Run-time Initialization
Quarkus instructs Mandrel to initialize as much as possible at build time,
so that runtime startup can be as fast as possible.
@@ -705,9 +848,9 @@ hellomandrel
Additional information on which classes are initialized and why can be obtained by passing in the `-H:+PrintClassInitialization` flag via `-Dquarkus.native.additional-build-args`.
[[profiling]]
-== Profile Runtime Behaviour
+=== Profile Runtime Behaviour
-=== Single Thread
+==== Single Thread
In this exercise, we profile the runtime behaviour of some Quarkus application that was compiled to a native executable to determine where the bottleneck is.
Assume that you’re in a scenario where profiling the pure Java version is not possible, maybe because the issue only occurs with the native version of the application.
@@ -901,7 +1044,7 @@ The issue is that 1 million characters need to be shifted in very small incremen
image::native-reference-perf-flamegraph-symbols.svg[Perf flamegraph with symbols]
-=== Multi-Thread
+==== Multi-Thread
Multithreaded programs might require special attention when trying to understand their runtime behaviour.
To demonstrate this, add this `MulticastResource` code to your project
@@ -1036,7 +1179,7 @@ When you open the flamegraph, you will see all threads' work collapsed into a si
Then, you can clearly see that there's some locking that could affect performance.
[[debug-info]]
-== Debugging Native Crashes
+=== Debugging Native Crashes
One of the drawbacks of using native executables is that they cannot be debugged using the standard Java debuggers,
instead we need to debug them using `gdb`, the GNU Project debugger.
@@ -1339,8 +1482,9 @@ We can now examine line `169` and get a first hint of what might be wrong
(in this case we see that it fails at the first read from src which contains the address `0x0000`),
or walk up the stack using `gdb`’s `up` command to see what part of our code led to this situation.
To learn more about using gdb to debug native executables see
-https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/DebugInfo.md[here].
+https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/debugging-and-diagnostics/DebugInfo/[here].
+[[native-faq]]
== Frequently Asked Questions
=== Why is the process of generating a native executable slow?
@@ -1459,26 +1603,11 @@ com.oracle.svm.core.VM=GraalVM 22.0.0.2-Final Java 11 Mandrel Distribution
=== How do I enable GC logging in native executables?
-Executing the native executable with `-XX:PrintFlags=` prints a list of flags that can be passed to native executables.
-For various levels of GC logging one may use:
-
-[source,bash]
-----
-$ ./target/debugging-native-1.0.0-SNAPSHOT-runner -XX:PrintFlags=
-...
- -XX:±PrintGC Print summary GC information after each collection. Default: - (disabled).
- -XX:±PrintGCSummary Print summary GC information after application main method returns. Default: - (disabled).
- -XX:±PrintGCTimeStamps Print a time stamp at each collection, if +PrintGC or +VerboseGC. Default: - (disabled).
- -XX:±PrintGCTimes Print the time for each of the phases of each collection, if +VerboseGC. Default: - (disabled).
- -XX:±PrintHeapShape Print the shape of the heap before and after each collection, if +VerboseGC. Default: - (disabled).
-...
- -XX:±TraceHeapChunks Trace heap chunks during collections, if +VerboseGC and +PrintHeapShape. Default: - (disabled).
- -XX:±VerboseGC Print more information about the heap before and after each collection. Default: - (disabled).
-----
+See <> for details.
=== Can I get a heap dump of a native executable? e.g. if it runs out of memory
-Starting with GraalVM 22.2.0 it will be possible to heap dumps upon request,
+Starting with GraalVM 22.2.0 it is possible to create heap dumps upon request,
e.g. `kill -SIGUSR1 `.
Support for dumping the heap dump upon an out of memory error will follow up.
@@ -1616,7 +1745,7 @@ Once the image is compiled, enable and start JFR via runtime flags: `-XX:+Flight
-XX:StartFlightRecording="filename=recording.jfr"
----
-For more details on using JFR, see https://www.graalvm.org/22.2/reference-manual/native-image/debugging-and-diagnostics/JFR/[here].
+For more details on using JFR, see https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/debugging-and-diagnostics/JFR/[here].
=== How can we troubleshoot performance problems only reproducible in production?
diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc
index 32e89db2129e1..b9e12fd8393b9 100644
--- a/docs/src/main/asciidoc/qute-reference.adoc
+++ b/docs/src/main/asciidoc/qute-reference.adoc
@@ -1164,7 +1164,7 @@ The snippet above should render something like:
----
-TIP: In Quarkus, it is also possible to define a <> via the `@CheckedFragment` annotation.
+TIP: In Quarkus, it is also possible to define a <>.
You can also include a fragment with an `{#include}` section inside another template or the template that defines the fragment.
@@ -1175,11 +1175,15 @@ You can also include a fragment with an `{#include}` section inside another temp
This document contains a detailed info about a user.
-{#include item[item_aliases] aliases=user.aliases /} <1><2>
+{#include item$item_aliases aliases=user.aliases /} <1><2>
----
-<1> The `item[item_aliases]` parameter is translated as: _use the fragment `item_aliases` from the template `item`._
+<1> A template identifier that contains a dollar sign `$` denotes a fragment. The `item$item_aliases` value is translated as: _Use the fragment `item_aliases` from the template `item`._
<2> The `aliases` parameter is used to pass the relevant data. We need to make sure that the data are set correctly. In this particular case the fragment will use the expression `user.aliases` as the value of `aliases` in the `{#for alias in aliases}` section.
+TIP: If you want to reference a fragment from the same template you can skip the part before `$`, i.e. something like `{#include $item_aliases /}`.
+
+NOTE: You can specify `{#include item$item_aliases _ignoreFragments=true /}` in order to disable this feature, i.e. a dollar sign `$` in the template identifier does not result in a fragment lookup.
+
===== Hidden Fragments
By default, a fragment is normally rendered as a part of the original template.
@@ -1689,7 +1693,7 @@ public class ItemResource {
==== Type-safe Fragments
You can also define a type-safe <> in your Java code.
-A fragment method is annotated with `@io.quarkus.qute.CheckedFragment`.
+A _native static_ method with the name that contains a dollar sign `$` denotes a method that represents a fragment of a type-safe template.
The name of the fragment is derived from the annotated method name.
The part before the last occurence of a dollar sign `$` is the method name of the related type-safe template.
The part after the last occurence of a dollar sign is the fragment identifier.
@@ -1698,7 +1702,6 @@ The strategy defined by the relevant `CheckedTemplate#defaultName()` is honored
.Type-safe Fragment Example
[source,java]
----
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import org.acme.Item;
@@ -1709,7 +1712,6 @@ class Templates {
static native TemplateInstance items(List items);
// defines a fragment of Templates#items() with identifier "item"
- @CheckedFragment
static native TemplateInstance items$item(Item item); <1>
}
----
@@ -1742,6 +1744,8 @@ class ItemService {
}
----
+NOTE: You can specify `@CheckedTemplate#ignoreFragments=true` in order to disable this feature, i.e. a dollar sign `$` in the method name will not result in a checked fragment method.
+
[[template_extension_methods]]
=== Template Extension Methods
diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc
index 2b2609cfeef64..ff07bba6af0a0 100644
--- a/docs/src/main/asciidoc/resteasy-reactive.adoc
+++ b/docs/src/main/asciidoc/resteasy-reactive.adoc
@@ -578,6 +578,35 @@ public class Endpoint {
}
----
+Additionally, you can also manually append the parts of the form using the class `MultipartFormDataOutput` as:
+
+[source,java]
+----
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.reactive.server.core.multipart.MultipartFormDataOutput;
+
+@Path("multipart")
+public class Endpoint {
+
+ @GET
+ @Produces(MediaType.MULTIPART_FORM_DATA)
+ @Path("file")
+ public MultipartFormDataOutput getFile() {
+ MultipartFormDataOutput form = new MultipartFormDataOutput();
+ form.addFormData("person", new Person("John"), MediaType.APPLICATION_JSON_TYPE);
+ form.addFormData("status", "a status", MediaType.TEXT_PLAIN_TYPE)
+ .getHeaders().putSingle("extra-header", "extra-value");
+ return form;
+ }
+}
+----
+
+This last approach allows you adding extra headers to the output part.
+
WARNING: For the time being, returning Multipart data is limited to be blocking endpoints.
==== Handling malformed input
diff --git a/docs/src/main/asciidoc/security-getting-started.adoc b/docs/src/main/asciidoc/security-getting-started.adoc
index 4d94e34199cf3..d6a4502f4c363 100644
--- a/docs/src/main/asciidoc/security-getting-started.adoc
+++ b/docs/src/main/asciidoc/security-getting-started.adoc
@@ -374,7 +374,7 @@ As you can see in this code sample, you do not need to start the test container
[NOTE]
====
If you start your application in dev mode, `Dev Services for PostgreSQL` launches a `PostgreSQL` `devmode` container so that you can start developing your application.
-While developing your application, you can also start to add tests one by one and run them by using the xref:continuous-testing.adoc[Continous Testing] feature.
+While developing your application, you can also start to add tests one by one and run them by using the xref:continuous-testing.adoc[Continuous Testing] feature.
`Dev Services for PostgreSQL` supports testing while you develop by providing a separate `PostgreSQL` test container that does not conflict with the `devmode` container.
====
diff --git a/docs/src/main/asciidoc/spring-data-rest.adoc b/docs/src/main/asciidoc/spring-data-rest.adoc
index efabca09ab41c..09225c5361499 100644
--- a/docs/src/main/asciidoc/spring-data-rest.adoc
+++ b/docs/src/main/asciidoc/spring-data-rest.adoc
@@ -7,14 +7,10 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
include::_attributes.adoc[]
:categories: compatibility
:summary: Spring Data REST simplifies the creation of CRUD applications based on our Spring Data compatibility layer.
-:extension-status: preview
While users are encouraged to use REST Data with Panache for the REST data access endpoints generation,
Quarkus provides a compatibility layer for Spring Data REST in the form of the `spring-data-rest` extension.
-
-include::{includes}/extension-status.adoc[]
-
== Prerequisites
include::{includes}/prerequisites.adoc[]
diff --git a/docs/src/main/asciidoc/writing-native-applications-tips.adoc b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
index 997276d1cd413..9f980abf296d9 100644
--- a/docs/src/main/asciidoc/writing-native-applications-tips.adoc
+++ b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
@@ -76,7 +76,7 @@ Here we include all the XML files and JSON files into the native executable.
[NOTE]
====
-You can find more information about this topic in https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/Resources.md[the GraalVM documentation].
+You can find more information about this topic in https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/dynamic-features/Resources/[the GraalVM documentation].
====
The final order of business is to make the configuration file known to the `native-image` executable by adding the proper configuration to `application.properties`:
@@ -245,7 +245,7 @@ As an example, in order to register all methods of class `com.acme.MyClass` for
[NOTE]
====
-For more details on the format of this file, please refer to https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/Reflection.md[the GraalVM documentation].
+For more details on the format of this file, please refer to https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/dynamic-features/Reflection/[the GraalVM documentation].
====
The final order of business is to make the configuration file known to the `native-image` executable by adding the proper configuration to `application.properties`:
@@ -327,7 +327,7 @@ It should be added to the `native-image` configuration using the `quarkus.native
[NOTE]
====
-You can find more information about all this in https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ClassInitialization.md[the GraalVM documentation].
+You can find more information about all this in https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/optimizations-and-performance/ClassInitialization/[the GraalVM documentation].
====
[NOTE]
@@ -360,7 +360,7 @@ com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfac
----
Solving this issue requires adding the `-H:DynamicProxyConfigurationResources=` option and to provide a dynamic proxy configuration file.
-You can find all the information about the format of this file in https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/DynamicProxy.md#manual-configuration[the GraalVM documentation].
+You can find all the information about the format of this file in https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/guides/configure-dynamic-proxies/[the GraalVM documentation].
[[modularity-benefits]]
=== Modularity Benefits
@@ -612,7 +612,7 @@ public class SaxParserProcessor {
[NOTE]
====
-More information about reflection in GraalVM can be found https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/Reflection.md[here].
+More information about reflection in GraalVM can be found https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/dynamic-features/Reflection/[here].
====
=== Including resources
@@ -633,7 +633,7 @@ public class ResourcesProcessor {
[NOTE]
====
-For more information about GraalVM resource handling in native executables please refer to https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/Resources.md[the GraalVM documentation].
+For more information about GraalVM resource handling in native executables please refer to https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/dynamic-features/Resources/[the GraalVM documentation].
====
@@ -657,7 +657,7 @@ Using such a construct means that a `--initialize-at-run-time` option will autom
[NOTE]
====
-For more information about `--initialize-at-run-time`, please read https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ClassInitialization.md[the GraalVM documentation].
+For more information about `--initialize-at-run-time`, please read https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/optimizations-and-performance/ClassInitialization/[the GraalVM documentation].
====
=== Managing Proxy Classes
@@ -681,7 +681,7 @@ Using such a construct means that a `-H:DynamicProxyConfigurationResources` opti
[NOTE]
====
-For more information about Proxy Classes you can read https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/DynamicProxy.md[the GraalVM documentation].
+For more information about Proxy Classes you can read https://www.graalvm.org/{graalvm-version}/reference-manual/native-image/guides/configure-dynamic-proxies/[the GraalVM documentation].
====
=== Logging with Native Image
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
index a013842b0082b..9b5840817838c 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
@@ -156,6 +156,7 @@ AdditionalBeanBuildItem quarkusApplication(CombinedIndexBuildItem combinedIndex)
quarkusApplications.add(quarkusApplication.name().toString());
}
}
+
return AdditionalBeanBuildItem.builder().setUnremovable()
.setDefaultScope(DotName.createSimple(ApplicationScoped.class.getName()))
.addBeanClasses(quarkusApplications)
diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java
index 8a10598831305..d224a6a4e05f9 100644
--- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java
+++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java
@@ -62,13 +62,13 @@ public CacheInterceptionContext get() {
private Optional> getArcCacheInterceptionContext(
InvocationContext invocationContext, Class interceptorBindingClass) {
Set bindings = InterceptorBindings.getInterceptorBindings(invocationContext);
- if (bindings == null) {
+ if ((bindings == null) || bindings.isEmpty()) {
LOGGER.trace("Interceptor bindings not found in ArC");
// This should only happen when the interception is not managed by Arc.
return Optional.empty();
}
- List interceptorBindings = new ArrayList<>();
- List cacheKeyParameterPositions = new ArrayList<>();
+ List interceptorBindings = new ArrayList<>(bindings.size() / 2); // initial capacity is a heuristic here...
+ List cacheKeyParameterPositions = new ArrayList<>(bindings.size() / 2);
for (Annotation binding : bindings) {
if (binding instanceof CacheKeyParameterPositions) {
for (short position : ((CacheKeyParameterPositions) binding).value()) {
diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionValidateAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionValidateAtStartTest.java
new file mode 100644
index 0000000000000..9e685253a9bc4
--- /dev/null
+++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionValidateAtStartTest.java
@@ -0,0 +1,23 @@
+package io.quarkus.flyway.test;
+
+import org.flywaydb.core.api.exception.FlywayValidateException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class FlywayExtensionValidateAtStartTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addAsResource("db/migration/V1.0.0__Quarkus.sql")
+ .addAsResource("validate-at-start-config.properties", "application.properties"))
+ .setExpectedException(FlywayValidateException.class);
+
+ @Test
+ public void shouldNeverBeCalled() {
+
+ }
+
+}
diff --git a/extensions/flyway/deployment/src/test/resources/validate-at-start-config.properties b/extensions/flyway/deployment/src/test/resources/validate-at-start-config.properties
new file mode 100644
index 0000000000000..a0810ce17fb35
--- /dev/null
+++ b/extensions/flyway/deployment/src/test/resources/validate-at-start-config.properties
@@ -0,0 +1,7 @@
+quarkus.datasource.db-kind=h2
+quarkus.datasource.username=sa
+quarkus.datasource.password=sa
+quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test-quarkus-validate-at-start;DB_CLOSE_DELAY=-1
+
+# Flyway config properties
+quarkus.flyway.validate-at-start=true
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainer.java
index a42d295ad4771..0a956422d1a6b 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainer.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainer.java
@@ -8,17 +8,21 @@ public class FlywayContainer {
private final boolean cleanAtStart;
private final boolean migrateAtStart;
private final boolean repairAtStart;
+
+ private final boolean validateAtStart;
private final String dataSourceName;
private final boolean hasMigrations;
private final boolean createPossible;
private final String id;
public FlywayContainer(Flyway flyway, boolean cleanAtStart, boolean migrateAtStart, boolean repairAtStart,
+ boolean validateAtStart,
String dataSourceName, boolean hasMigrations, boolean createPossible) {
this.flyway = flyway;
this.cleanAtStart = cleanAtStart;
this.migrateAtStart = migrateAtStart;
this.repairAtStart = repairAtStart;
+ this.validateAtStart = validateAtStart;
this.dataSourceName = dataSourceName;
this.hasMigrations = hasMigrations;
this.createPossible = createPossible;
@@ -41,6 +45,10 @@ public boolean isRepairAtStart() {
return repairAtStart;
}
+ public boolean isValidateAtStart() {
+ return validateAtStart;
+ }
+
public String getDataSourceName() {
return dataSourceName;
}
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 d7f4c727e3a54..71e3a19cb2501 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
@@ -36,6 +36,7 @@ public FlywayContainer createFlyway(DataSource dataSource, String dataSourceName
final Flyway flyway = new FlywayCreator(matchingRuntimeConfig, matchingBuildTimeConfig).withCallbacks(callbacks)
.createFlyway(dataSource);
return new FlywayContainer(flyway, matchingRuntimeConfig.cleanAtStart, matchingRuntimeConfig.migrateAtStart,
- matchingRuntimeConfig.repairAtStart, dataSourceName, hasMigrations, createPossible);
+ matchingRuntimeConfig.repairAtStart, matchingRuntimeConfig.validateAtStart, dataSourceName, hasMigrations,
+ createPossible);
}
}
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 6162ab83fc048..9f9b05c9ada41 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
@@ -112,6 +112,13 @@ public static FlywayDataSourceRuntimeConfig defaultConfig() {
@ConfigItem
public boolean repairAtStart;
+ /**
+ * true to execute a Flyway validate command when the application starts, false otherwise.
+ *
+ */
+ @ConfigItem
+ public boolean validateAtStart;
+
/**
* Enable the creation of the history table if it does not exist already.
*/
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 cbfa3fb789e48..2bcaf79d62f87 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
@@ -72,6 +72,9 @@ public void doStartActions() {
if (flywayContainer.isCleanAtStart()) {
flywayContainer.getFlyway().clean();
}
+ if (flywayContainer.isValidateAtStart()) {
+ flywayContainer.getFlyway().validate();
+ }
if (flywayContainer.isRepairAtStart()) {
flywayContainer.getFlyway().repair();
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeIngressRuleDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeIngressRuleDecorator.java
new file mode 100644
index 0000000000000..860a4f6c8586f
--- /dev/null
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeIngressRuleDecorator.java
@@ -0,0 +1,180 @@
+package io.quarkus.kubernetes.deployment;
+
+import java.util.Optional;
+
+import io.dekorate.kubernetes.config.IngressRule;
+import io.dekorate.kubernetes.config.Port;
+import io.dekorate.kubernetes.decorator.AddIngressRuleDecorator;
+import io.dekorate.kubernetes.decorator.Decorator;
+import io.dekorate.kubernetes.decorator.NamedResourceDecorator;
+import io.dekorate.utils.Strings;
+import io.fabric8.kubernetes.api.builder.TypedVisitor;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPathBuilder;
+import io.fabric8.kubernetes.api.model.networking.v1.IngressRuleBuilder;
+import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackendBuilder;
+import io.fabric8.kubernetes.api.model.networking.v1.IngressSpecBuilder;
+import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort;
+import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPortBuilder;
+
+/**
+ * TODO: Workaround for https://github.com/quarkusio/quarkus/issues/28812
+ * We need to remove the duplicate paths of the generated Ingress. The following logic can be removed after
+ * bumping the next Dekorate version that includes the fix: https://github.com/dekorateio/dekorate/pull/1092.
+ */
+public class ChangeIngressRuleDecorator extends NamedResourceDecorator {
+
+ private static final String DEFAULT_PREFIX = "Prefix";
+
+ private final Optional defaultHostPort;
+ private final IngressRule rule;
+
+ public ChangeIngressRuleDecorator(String name, Optional defaultHostPort, IngressRule rule) {
+ super(name);
+ this.defaultHostPort = defaultHostPort;
+ this.rule = rule;
+ }
+
+ @Override
+ public void andThenVisit(IngressSpecBuilder spec, ObjectMeta meta) {
+ if (!spec.hasMatchingRule(existingRule -> Strings.equals(rule.getHost(), existingRule.getHost()))) {
+ spec.addNewRule()
+ .withHost(rule.getHost())
+ .withNewHttp()
+ .addNewPath()
+ .withPathType(pathType())
+ .withPath(path())
+ .withNewBackend()
+ .withNewService()
+ .withName(serviceName())
+ .withPort(createPort(defaultHostPort))
+ .endService()
+ .endBackend()
+ .endPath()
+ .endHttp()
+ .endRule();
+ } else {
+ spec.accept(new HostVisitor(defaultHostPort));
+ }
+ }
+
+ @Override
+ public Class extends Decorator>[] after() {
+ return new Class[] { AddIngressRuleDecorator.class, RemoveDuplicateIngressRuleDecorator.class };
+ }
+
+ private String serviceName() {
+ return Strings.defaultIfEmpty(rule.getServiceName(), name);
+ }
+
+ private String path() {
+ return Strings.defaultIfEmpty(rule.getPath(), defaultHostPort.map(p -> p.getPath()).orElse("/"));
+ }
+
+ private String pathType() {
+ return Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX);
+ }
+
+ private ServiceBackendPort createPort(Optional defaultHostPort) {
+ ServiceBackendPortBuilder builder = new ServiceBackendPortBuilder();
+ if (Strings.isNotNullOrEmpty(rule.getServicePortName())) {
+ builder.withName(rule.getServicePortName());
+ } else if (rule.getServicePortNumber() != null && rule.getServicePortNumber() >= 0) {
+ builder.withNumber(rule.getServicePortNumber());
+ } else if (Strings.isNullOrEmpty(rule.getServiceName()) || Strings.equals(rule.getServiceName(), name)) {
+ // Trying to get the port from the service
+ Port servicePort = defaultHostPort
+ .orElseThrow(() -> new RuntimeException(
+ "Could not find any matching port to configure the Ingress Rule. Specify the "
+ + "service port using `kubernetes.ingress.service-port-name`"));
+ builder.withName(servicePort.getName());
+ } else {
+ throw new RuntimeException("The service port for '" + rule.getServiceName() + "' was not set. Specify one "
+ + "using `kubernetes.ingress.service-port-name`");
+ }
+
+ return builder.build();
+ }
+
+ private class HostVisitor extends TypedVisitor {
+
+ private final Optional defaultHostPort;
+
+ public HostVisitor(Optional defaultHostPort) {
+ this.defaultHostPort = defaultHostPort;
+ }
+
+ @Override
+ public void visit(IngressRuleBuilder existingRule) {
+ if (Strings.equals(existingRule.getHost(), rule.getHost())) {
+ if (!existingRule.hasHttp()) {
+ existingRule.withNewHttp()
+ .addNewPath()
+ .withPathType(pathType())
+ .withPath(path())
+ .withNewBackend()
+ .withNewService()
+ .withName(serviceName())
+ .withPort(createPort(defaultHostPort))
+ .endService()
+ .endBackend()
+ .endPath().endHttp();
+ } else if (existingRule.getHttp().getPaths().stream()
+ .noneMatch(p -> Strings.equals(p.getPath(), path()) && Strings.equals(p.getPathType(), pathType()))) {
+ existingRule.editHttp()
+ .addNewPath()
+ .withPathType(pathType())
+ .withPath(path())
+ .withNewBackend()
+ .withNewService()
+ .withName(serviceName())
+ .withPort(createPort(defaultHostPort))
+ .endService()
+ .endBackend()
+ .endPath().endHttp();
+ } else {
+ existingRule.accept(new PathVisitor(defaultHostPort));
+ }
+ }
+ }
+ }
+
+ private class PathVisitor extends TypedVisitor {
+
+ private final Optional defaultHostPort;
+
+ public PathVisitor(Optional defaultHostPort) {
+ this.defaultHostPort = defaultHostPort;
+ }
+
+ @Override
+ public void visit(HTTPIngressPathBuilder existingPath) {
+ if (Strings.equals(existingPath.getPath(), rule.getPath())) {
+ if (!existingPath.hasBackend()) {
+ existingPath.withNewBackend()
+ .withNewService()
+ .withName(serviceName())
+ .withPort(createPort(defaultHostPort))
+ .endService()
+ .endBackend();
+ } else {
+ existingPath.accept(new ServiceVisitor(defaultHostPort));
+ }
+ }
+ }
+ }
+
+ private class ServiceVisitor extends TypedVisitor {
+
+ private final Optional defaultHostPort;
+
+ public ServiceVisitor(Optional defaultHostPort) {
+ this.defaultHostPort = defaultHostPort;
+ }
+
+ @Override
+ public void visit(IngressServiceBackendBuilder service) {
+ service.withName(Strings.defaultIfEmpty(rule.getServiceName(), name)).withPort(createPort(defaultHostPort));
+ }
+ }
+}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressConfig.java
index cfc18f2d69278..71072ff1cc935 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressConfig.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressConfig.java
@@ -33,4 +33,10 @@ public class IngressConfig {
@ConfigItem
Map tls;
+ /**
+ * Custom rules for the current ingress resource.
+ */
+ @ConfigItem
+ Map rules;
+
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressRuleConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressRuleConfig.java
new file mode 100644
index 0000000000000..d31723b586f24
--- /dev/null
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/IngressRuleConfig.java
@@ -0,0 +1,48 @@
+package io.quarkus.kubernetes.deployment;
+
+import java.util.Optional;
+
+import io.quarkus.runtime.annotations.ConfigGroup;
+import io.quarkus.runtime.annotations.ConfigItem;
+
+@ConfigGroup
+public class IngressRuleConfig {
+
+ /**
+ * The host under which the rule is going to be used.
+ */
+ @ConfigItem
+ String host;
+
+ /**
+ * The path under which the rule is going to be used. Default is "/".
+ */
+ @ConfigItem(defaultValue = "/")
+ String path;
+
+ /**
+ * The path type strategy to use by the Ingress rule. Default is "Prefix".
+ */
+ @ConfigItem(defaultValue = "Prefix")
+ String pathType;
+
+ /**
+ * The service name to be used by this Ingress rule. Default is the generated service name of the application.
+ */
+ @ConfigItem
+ Optional serviceName;
+
+ /**
+ * The service port name to be used by this Ingress rule. Default is the port name of the generated service of
+ * the application.
+ */
+ @ConfigItem
+ Optional servicePortName;
+
+ /**
+ * The service port number to be used by this Ingress rule. This is only used when the servicePortName is not set.
+ */
+ @ConfigItem
+ Optional servicePortNumber;
+
+}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RemoveDuplicateIngressRuleDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RemoveDuplicateIngressRuleDecorator.java
new file mode 100644
index 0000000000000..90a4c2c912b6e
--- /dev/null
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RemoveDuplicateIngressRuleDecorator.java
@@ -0,0 +1,37 @@
+package io.quarkus.kubernetes.deployment;
+
+import io.dekorate.kubernetes.decorator.AddIngressRuleDecorator;
+import io.dekorate.kubernetes.decorator.Decorator;
+import io.dekorate.kubernetes.decorator.NamedResourceDecorator;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.networking.v1.IngressSpecBuilder;
+
+/**
+ * TODO: Workaround for https://github.com/quarkusio/quarkus/issues/28812
+ * We need to remove the duplicate paths of the generated Ingress. The following logic can be removed after
+ * bumping the next Dekorate version that includes the fix: https://github.com/dekorateio/dekorate/pull/1092.
+ */
+public class RemoveDuplicateIngressRuleDecorator extends NamedResourceDecorator {
+
+ public RemoveDuplicateIngressRuleDecorator(String name) {
+ super(name);
+ }
+
+ @Override
+ public void andThenVisit(IngressSpecBuilder spec, ObjectMeta meta) {
+ if (spec.hasRules()) {
+ spec.editMatchingRule(rule -> {
+ rule.editHttp()
+ .removeMatchingFromPaths(path -> rule.getHttp().getPaths().stream()
+ .filter(p -> p.hashCode() == path.hashCode()).count() > 1)
+ .endHttp();
+ return true;
+ });
+ }
+ }
+
+ @Override
+ public Class extends Decorator>[] after() {
+ return new Class[] { AddIngressRuleDecorator.class };
+ }
+}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
index a62abce41fbfb..30da93f016091 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
@@ -1,6 +1,7 @@
package io.quarkus.kubernetes.deployment;
+import static io.dekorate.kubernetes.decorator.AddServiceResourceDecorator.distinct;
import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_HTTP_PORT;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_GROUP;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_VERSION;
@@ -19,6 +20,8 @@
import io.dekorate.kubernetes.annotation.ServiceType;
import io.dekorate.kubernetes.config.EnvBuilder;
import io.dekorate.kubernetes.config.IngressBuilder;
+import io.dekorate.kubernetes.config.IngressRuleBuilder;
+import io.dekorate.kubernetes.config.Port;
import io.dekorate.kubernetes.decorator.AddAnnotationDecorator;
import io.dekorate.kubernetes.decorator.AddEnvVarDecorator;
import io.dekorate.kubernetes.decorator.AddIngressTlsDecorator;
@@ -173,6 +176,25 @@ public List createDecorators(ApplicationInfoBuildItem applic
result.add(new DecoratorBuildItem(KUBERNETES,
new AddAnnotationDecorator(name, annotation.getKey(), annotation.getValue(), INGRESS)));
}
+ // TODO: Workaround for https://github.com/quarkusio/quarkus/issues/28812
+ // We need to remove the duplicate paths of the generated Ingress. The following logic can be removed after
+ // bumping the next Dekorate version that includes the fix: https://github.com/dekorateio/dekorate/pull/1092.
+ result.add(new DecoratorBuildItem(KUBERNETES, new RemoveDuplicateIngressRuleDecorator(name)));
+ Optional defaultHostPort = KubernetesCommonHelper.combinePorts(ports, config).values().stream()
+ .filter(distinct(p -> p.getName()))
+ .findFirst();
+
+ for (IngressRuleConfig rule : config.ingress.rules.values()) {
+ result.add(new DecoratorBuildItem(KUBERNETES, new ChangeIngressRuleDecorator(name, defaultHostPort,
+ new IngressRuleBuilder()
+ .withHost(rule.host)
+ .withPath(rule.path)
+ .withPathType(rule.pathType)
+ .withServiceName(rule.serviceName.orElse(null))
+ .withServicePortName(rule.servicePortName.orElse(null))
+ .withServicePortNumber(rule.servicePortNumber.orElse(-1))
+ .build())));
+ }
}
if (config.getReplicas() != 1) {
diff --git a/extensions/panache/hibernate-orm-panache-common/deployment/src/main/java/io/quarkus/hibernate/orm/panache/common/deployment/PanacheJpaCommonResourceProcessor.java b/extensions/panache/hibernate-orm-panache-common/deployment/src/main/java/io/quarkus/hibernate/orm/panache/common/deployment/PanacheJpaCommonResourceProcessor.java
index d39b568b528b2..e32cefc3f6ea9 100644
--- a/extensions/panache/hibernate-orm-panache-common/deployment/src/main/java/io/quarkus/hibernate/orm/panache/common/deployment/PanacheJpaCommonResourceProcessor.java
+++ b/extensions/panache/hibernate-orm-panache-common/deployment/src/main/java/io/quarkus/hibernate/orm/panache/common/deployment/PanacheJpaCommonResourceProcessor.java
@@ -17,13 +17,16 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.util.JandexUtil;
+import io.quarkus.hibernate.orm.deployment.HibernateOrmEnabled;
import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem;
import io.quarkus.hibernate.orm.panache.common.runtime.PanacheHibernateRecorder;
+@BuildSteps(onlyIf = HibernateOrmEnabled.class)
public final class PanacheJpaCommonResourceProcessor {
private static final DotName DOTNAME_NAMED_QUERY = DotName.createSimple(NamedQuery.class.getName());
diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/orm/panache/kotlin/deployment/test/MyEntity.kt b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/orm/panache/kotlin/deployment/test/MyEntity.kt
new file mode 100644
index 0000000000000..14a5e306ae467
--- /dev/null
+++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/orm/panache/kotlin/deployment/test/MyEntity.kt
@@ -0,0 +1,13 @@
+package io.quarkus.hibernate.orm.panache.kotlin.deployment.test
+
+import io.quarkus.hibernate.orm.panache.kotlin.PanacheCompanion
+import io.quarkus.hibernate.orm.panache.kotlin.PanacheEntity
+import javax.persistence.Entity
+
+@Entity
+class MyEntity : PanacheEntity() {
+ companion object: PanacheCompanion {
+ }
+
+ lateinit var name: String
+}
diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/orm/panache/kotlin/deployment/test/config/ConfigEnabledFalseTest.kt b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/orm/panache/kotlin/deployment/test/config/ConfigEnabledFalseTest.kt
new file mode 100644
index 0000000000000..b292b767d9b9a
--- /dev/null
+++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/orm/panache/kotlin/deployment/test/config/ConfigEnabledFalseTest.kt
@@ -0,0 +1,29 @@
+package io.quarkus.hibernate.orm.panache.kotlin.deployment.test.config
+
+import io.quarkus.arc.Arc
+import io.quarkus.hibernate.orm.panache.kotlin.deployment.test.MyEntity
+import io.quarkus.test.QuarkusUnitTest
+import org.jboss.shrinkwrap.api.spec.JavaArchive
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import javax.persistence.EntityManagerFactory
+
+class ConfigEnabledFalseTest {
+ companion object {
+ @RegisterExtension
+ val config = QuarkusUnitTest()
+ .withApplicationRoot { jar: JavaArchive -> jar.addClass(MyEntity::class.java) }
+ .withConfigurationResource("application-test.properties")
+ // We shouldn't get any build error caused by Panache consuming build items that are not produced
+ // See https://github.com/quarkusio/quarkus/issues/28842
+ .overrideConfigKey("quarkus.hibernate-orm.enabled", "false")
+ }
+
+ @Test
+ fun startsWithoutError() {
+ // Quarkus started without problem, even though the Panache extension is present.
+ // Just check that Hibernate ORM is disabled.
+ Assertions.assertNull(Arc.container().instance(EntityManagerFactory::class.java).get())
+ }
+}
\ No newline at end of file
diff --git a/extensions/panache/hibernate-orm-panache/deployment/pom.xml b/extensions/panache/hibernate-orm-panache/deployment/pom.xml
index 6175a737da04c..944df5b5c88b0 100644
--- a/extensions/panache/hibernate-orm-panache/deployment/pom.xml
+++ b/extensions/panache/hibernate-orm-panache/deployment/pom.xml
@@ -68,6 +68,11 @@
quarkus-resteasy-deploymenttest
+
+ org.assertj
+ assertj-core
+ test
+
diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/config/ConfigEnabledFalseTest.java b/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/config/ConfigEnabledFalseTest.java
new file mode 100644
index 0000000000000..300740555e537
--- /dev/null
+++ b/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/config/ConfigEnabledFalseTest.java
@@ -0,0 +1,31 @@
+package io.quarkus.hibernate.orm.panache.deployment.test.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.persistence.EntityManagerFactory;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.hibernate.orm.panache.deployment.test.MyEntity;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ConfigEnabledFalseTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(jar -> jar.addClass(MyEntity.class))
+ .withConfigurationResource("application-test.properties")
+ // We shouldn't get any build error caused by Panache consuming build items that are not produced
+ // See https://github.com/quarkusio/quarkus/issues/28842
+ .overrideConfigKey("quarkus.hibernate-orm.enabled", "false");
+
+ @Test
+ public void startsWithoutError() {
+ // Quarkus started without problem, even though the Panache extension is present.
+ // Just check that Hibernate ORM is disabled.
+ assertThat(Arc.container().instance(EntityManagerFactory.class).get())
+ .isNull();
+ }
+}
diff --git a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/deployment/PanacheJpaCommonResourceProcessor.java b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/deployment/PanacheJpaCommonResourceProcessor.java
index ff3b137fbf9c7..dd5719239404b 100644
--- a/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/deployment/PanacheJpaCommonResourceProcessor.java
+++ b/extensions/panache/hibernate-reactive-panache-common/deployment/src/main/java/io/quarkus/hibernate/reactive/panache/deployment/PanacheJpaCommonResourceProcessor.java
@@ -23,16 +23,19 @@
import io.quarkus.deployment.IsTest;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.gizmo.ClassCreator;
+import io.quarkus.hibernate.orm.deployment.HibernateOrmEnabled;
import io.quarkus.hibernate.orm.deployment.JpaModelBuildItem;
import io.quarkus.hibernate.reactive.panache.common.runtime.PanacheHibernateRecorder;
import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactionalInterceptor;
import io.quarkus.hibernate.reactive.panache.common.runtime.TestReactiveTransactionalInterceptor;
+@BuildSteps(onlyIf = HibernateOrmEnabled.class)
public final class PanacheJpaCommonResourceProcessor {
private static final DotName DOTNAME_NAMED_QUERY = DotName.createSimple(NamedQuery.class.getName());
diff --git a/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/reactive/panache/kotlin/deployment/test/MyEntity.kt b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/reactive/panache/kotlin/deployment/test/MyEntity.kt
new file mode 100644
index 0000000000000..07f3c1ee26b73
--- /dev/null
+++ b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/reactive/panache/kotlin/deployment/test/MyEntity.kt
@@ -0,0 +1,13 @@
+package io.quarkus.hibernate.reactive.panache.kotlin.deployment.test
+
+import io.quarkus.hibernate.reactive.panache.kotlin.PanacheCompanion
+import io.quarkus.hibernate.reactive.panache.kotlin.PanacheEntity
+import javax.persistence.Entity
+
+@Entity
+class MyEntity : PanacheEntity() {
+ companion object: PanacheCompanion {
+ }
+
+ lateinit var name: String
+}
diff --git a/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/reactive/panache/kotlin/deployment/test/config/ConfigEnabledFalseTest.kt b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/reactive/panache/kotlin/deployment/test/config/ConfigEnabledFalseTest.kt
new file mode 100644
index 0000000000000..bf749e2304a50
--- /dev/null
+++ b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/kotlin/io/quarkus/hibernate/reactive/panache/kotlin/deployment/test/config/ConfigEnabledFalseTest.kt
@@ -0,0 +1,30 @@
+package io.quarkus.hibernate.reactive.panache.kotlin.deployment.test.config
+
+import io.quarkus.arc.Arc
+import io.quarkus.hibernate.reactive.panache.kotlin.deployment.test.MyEntity
+import io.quarkus.test.QuarkusUnitTest
+import org.hibernate.reactive.mutiny.Mutiny
+import org.hibernate.reactive.mutiny.impl.MutinySessionFactoryImpl
+import org.jboss.shrinkwrap.api.spec.JavaArchive
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+class ConfigEnabledFalseTest {
+ companion object {
+ @RegisterExtension
+ val config = QuarkusUnitTest()
+ .withApplicationRoot { jar: JavaArchive -> jar.addClass(MyEntity::class.java) }
+ .withConfigurationResource("application.properties")
+ // We shouldn't get any build error caused by Panache consuming build items that are not produced
+ // See https://github.com/quarkusio/quarkus/issues/28842
+ .overrideConfigKey("quarkus.hibernate-orm.enabled", "false")
+ }
+
+ @Test
+ fun startsWithoutError() {
+ // Quarkus started without problem, even though the Panache extension is present.
+ // Just check that Hibernate Reactive is disabled.
+ Assertions.assertNull(Arc.container().instance(Mutiny.SessionFactory::class.java).get())
+ }
+}
\ No newline at end of file
diff --git a/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/resources/application.properties b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/resources/application.properties
new file mode 100644
index 0000000000000..707f58e5af781
--- /dev/null
+++ b/extensions/panache/hibernate-reactive-panache-kotlin/deployment/src/test/resources/application.properties
@@ -0,0 +1,7 @@
+quarkus.datasource.db-kind=postgresql
+quarkus.datasource.username=hibernate_orm_test
+quarkus.datasource.password=hibernate_orm_test
+quarkus.datasource.reactive.url=${postgres.reactive.url}
+quarkus.datasource.devservices.enabled=false
+
+quarkus.hibernate-orm.database.generation=drop-and-create
diff --git a/extensions/panache/hibernate-reactive-panache/deployment/pom.xml b/extensions/panache/hibernate-reactive-panache/deployment/pom.xml
index 443ba373006e1..2f2aafb53fb4f 100644
--- a/extensions/panache/hibernate-reactive-panache/deployment/pom.xml
+++ b/extensions/panache/hibernate-reactive-panache/deployment/pom.xml
@@ -55,6 +55,11 @@
rest-assuredtest
+
+ org.assertj
+ assertj-core
+ test
+
diff --git a/extensions/panache/hibernate-reactive-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/panache/test/config/ConfigEnabledFalseTest.java b/extensions/panache/hibernate-reactive-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/panache/test/config/ConfigEnabledFalseTest.java
new file mode 100644
index 0000000000000..461c1014552e4
--- /dev/null
+++ b/extensions/panache/hibernate-reactive-panache/deployment/src/test/java/io/quarkus/hibernate/reactive/panache/test/config/ConfigEnabledFalseTest.java
@@ -0,0 +1,30 @@
+package io.quarkus.hibernate.reactive.panache.test.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.hibernate.reactive.mutiny.Mutiny;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.hibernate.reactive.panache.test.MyEntity;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ConfigEnabledFalseTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(jar -> jar.addClass(MyEntity.class))
+ .withConfigurationResource("application.properties")
+ // We shouldn't get any build error caused by Panache consuming build items that are not produced
+ // See https://github.com/quarkusio/quarkus/issues/28842
+ .overrideConfigKey("quarkus.hibernate-orm.enabled", "false");
+
+ @Test
+ public void startsWithoutError() {
+ // Quarkus started without problem, even though the Panache extension is present.
+ // Just check that Hibernate ORM is disabled.
+ assertThat(Arc.container().instance(Mutiny.SessionFactory.class).get())
+ .isNull();
+ }
+}
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 8369b4ac0cfa5..5c82459d08232 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
@@ -159,7 +159,7 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv
}
Map localeToInterface = new HashMap<>();
for (ClassInfo localizedInterface : localized) {
- String locale = localizedInterface.classAnnotation(Names.LOCALIZED).value().asString();
+ String locale = localizedInterface.declaredAnnotation(Names.LOCALIZED).value().asString();
if (defaultLocale.equals(locale)) {
throw new MessageBundleException(
String.format(
@@ -234,7 +234,7 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv
beanRegistration.getContext().configure(bundleInterface.name()).addType(bundle.getDefaultBundleInterface().name())
// The default message bundle - add both @Default and @Localized
.addQualifier(DotNames.DEFAULT).addQualifier().annotation(Names.LOCALIZED)
- .addValue("value", getDefaultLocale(bundleInterface.classAnnotation(Names.BUNDLE), locales)).done()
+ .addValue("value", getDefaultLocale(bundleInterface.declaredAnnotation(Names.BUNDLE), locales)).done()
.unremovable()
.scope(Singleton.class).creator(mc -> {
// Just create a new instance of the generated class
@@ -247,7 +247,7 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv
for (ClassInfo localizedInterface : bundle.getLocalizedInterfaces().values()) {
beanRegistration.getContext().configure(localizedInterface.name())
.addType(bundle.getDefaultBundleInterface().name())
- .addQualifier(localizedInterface.classAnnotation(Names.LOCALIZED))
+ .addQualifier(localizedInterface.declaredAnnotation(Names.LOCALIZED))
.unremovable()
.scope(Singleton.class).creator(mc -> {
// Just create a new instance of the generated class
@@ -770,8 +770,8 @@ private String generateImplementation(ClassInfo defaultBundleInterface, String d
ClassInfo bundleInterface = bundleInterfaceWrapper.getClassInfo();
LOGGER.debugf("Generate bundle implementation for %s", bundleInterface);
AnnotationInstance bundleAnnotation = defaultBundleInterface != null
- ? defaultBundleInterface.classAnnotation(Names.BUNDLE)
- : bundleInterface.classAnnotation(Names.BUNDLE);
+ ? defaultBundleInterface.declaredAnnotation(Names.BUNDLE)
+ : bundleInterface.declaredAnnotation(Names.BUNDLE);
AnnotationValue nameValue = bundleAnnotation.value();
String bundleName = nameValue != null ? nameValue.asString() : MessageBundle.DEFAULT_NAME;
AnnotationValue defaultKeyValue = bundleAnnotation.value(BUNDLE_DEFAULT_KEY);
@@ -864,7 +864,7 @@ private String generateImplementation(ClassInfo defaultBundleInterface, String d
if (messageTemplate.contains("}")) {
if (defaultBundleInterface != null) {
if (locale == null) {
- AnnotationInstance localizedAnnotation = bundleInterface.classAnnotation(Names.LOCALIZED);
+ AnnotationInstance localizedAnnotation = bundleInterface.declaredAnnotation(Names.LOCALIZED);
locale = localizedAnnotation.value().asString();
}
templateId = bundleName + "_" + locale + "_" + key;
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Names.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Names.java
index ec63c94dca3d6..400fe6ba49a35 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Names.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Names.java
@@ -44,7 +44,6 @@ final class Names {
static final DotName LOCATES = DotName.createSimple(Locates.class.getName());
static final DotName CHECKED_TEMPLATE = DotName.createSimple(io.quarkus.qute.CheckedTemplate.class.getName());
static final DotName TEMPLATE_ENUM = DotName.createSimple(TemplateEnum.class.getName());
- static final DotName CHECKED_FRAGMENT = DotName.createSimple(io.quarkus.qute.CheckedFragment.class.getName());
private Names() {
}
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 973576be7d134..d9317ba57960e 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
@@ -151,6 +151,7 @@ public class QuteProcessor {
private static final String CHECKED_TEMPLATE_REQUIRE_TYPE_SAFE = "requireTypeSafeExpressions";
private static final String CHECKED_TEMPLATE_BASE_PATH = "basePath";
private static final String CHECKED_TEMPLATE_DEFAULT_NAME = "defaultName";
+ private static final String IGNORE_FRAGMENTS = "ignoreFragments";
private static final String BASE_PATH = "templates";
private static final Set ITERATION_METADATA_KEYS = Set.of("count", "index", "indexParity", "hasNext", "odd",
@@ -312,11 +313,7 @@ List collectCheckedTemplates(BeanArchiveIndexBuildItem
throw new TemplateException("Incompatible checked template return type: " + methodInfo.returnType()
+ " only " + supportedAdaptors);
}
- String fragmentId = null;
- if (methodInfo.hasDeclaredAnnotation(Names.CHECKED_FRAGMENT)) {
- fragmentId = getCheckedFragmentId(methodInfo, annotation);
- }
-
+ String fragmentId = getCheckedFragmentId(methodInfo, annotation);
StringBuilder templatePathBuilder = new StringBuilder();
AnnotationValue basePathValue = annotation.value(CHECKED_TEMPLATE_BASE_PATH);
if (basePathValue != null && !basePathValue.asString().equals(CheckedTemplate.DEFAULTED)) {
@@ -344,7 +341,8 @@ && isNotLocatedByCustomTemplateLocator(locatorPatternsBuildItem.getLocationPatte
templatePath)) {
List startsWith = new ArrayList<>();
for (String filePath : filePaths.getFilePaths()) {
- if (filePath.startsWith(templatePath)) {
+ if (filePath.startsWith(templatePath)
+ && filePath.charAt(templatePath.length()) == '.') {
startsWith.add(filePath);
}
}
@@ -402,6 +400,16 @@ private String getCheckedTemplateName(MethodInfo method, AnnotationInstance chec
}
private String getCheckedFragmentId(MethodInfo method, AnnotationInstance checkedTemplateAnnotation) {
+ AnnotationValue ignoreFragmentsValue = checkedTemplateAnnotation.value(IGNORE_FRAGMENTS);
+ if (ignoreFragmentsValue != null && ignoreFragmentsValue.asBoolean()) {
+ return null;
+ }
+ String methodName = method.name();
+ // the id is the part after the last occurence of a dollar sign
+ int idx = methodName.lastIndexOf('$');
+ if (idx == -1 || idx == methodName.length()) {
+ return null;
+ }
AnnotationValue nameValue = checkedTemplateAnnotation.value(CHECKED_TEMPLATE_DEFAULT_NAME);
String defaultName;
if (nameValue == null) {
@@ -409,14 +417,6 @@ private String getCheckedFragmentId(MethodInfo method, AnnotationInstance checke
} else {
defaultName = nameValue.asString();
}
- String methodName = method.name();
- // the id is the part after the last occurence of a dollar sign
- int idx = methodName.lastIndexOf('$');
- if (idx == -1 || idx == methodName.length()) {
- throw new TemplateException(
- "[" + method.name() + "] is not a valid name of a checked fragment method: "
- + method.declaringClass().name().withoutPackagePrefix() + "." + method.name() + "()");
- }
return defaultedName(defaultName, methodName.substring(idx + 1, methodName.length()));
}
@@ -1998,6 +1998,17 @@ private static Type resolveType(AnnotationTarget member, Match match, IndexView
}
// If needed attempt to resolve the type variables using the declaring type
if (Types.containsTypeVariable(matchType)) {
+
+ if (match.clazz == null) {
+ if (member.kind() == Kind.METHOD && match.isPrimitive()) {
+ final Type wrapperType = Types.box(match.type.asPrimitiveType());
+ match.setValues(index.getClassByName(wrapperType.name()), wrapperType);
+ } else {
+ // we can't resolve type without class
+ return matchType;
+ }
+ }
+
// First get the type closure of the current match type
Set closure = Types.getTypeClosure(match.clazz,
Types.buildResolvedMap(
@@ -2046,11 +2057,18 @@ private static Type resolveType(AnnotationTarget member, Match match, IndexView
for (int i = 1; i < params.size() && (i - 1) < paramExpressions.size(); i++) {
// whether params.get(i) has same type as the extension base (e.g. T)
if (params.get(i).name().equals(extensionMatchBase.name())) {
- final var paramMatch = results.get(paramExpressions.get(i - 1).toOriginalString());
- // if all T params are not of exactly same type, we do not try to determine
- // right superclass/interface as it's expensive
- if (paramMatch != null && !match.type().equals(paramMatch.type())) {
- return matchType;
+ var paramMatch = results.get(paramExpressions.get(i - 1).toOriginalString());
+ if (paramMatch != null) {
+ Type paramMatchType = paramMatch.type();
+ if (paramMatch.isPrimitive()) {
+ // use boxed type
+ paramMatchType = Types.box(paramMatch.type());
+ }
+ // if all T params are not of exactly same type, we do not try to determine
+ // right superclass/interface as it's expensive
+ if (!match.type().equals(paramMatchType)) {
+ return matchType;
+ }
}
}
}
@@ -2744,7 +2762,7 @@ void collectTemplateDataAnnotations(BeanArchiveIndexBuildItem beanArchiveIndex,
targetEnum);
continue;
}
- if (targetEnum.classAnnotation(ValueResolverGenerator.TEMPLATE_DATA) != null) {
+ if (targetEnum.declaredAnnotation(ValueResolverGenerator.TEMPLATE_DATA) != null) {
LOGGER.debugf("@TemplateEnum declared on %s is ignored: enum is annotated with @TemplateData", targetEnum);
continue;
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Item.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Item.java
index 82a0900080fe8..1e54e24aceed2 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Item.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Item.java
@@ -19,4 +19,8 @@ public OtherItem[] getOtherItems() {
return otherItems;
}
+ public int getPrimitiveId() {
+ return 9;
+ }
+
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/ItemWithName.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/ItemWithName.java
index 73b8204c960e8..06284db56da43 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/ItemWithName.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/ItemWithName.java
@@ -11,6 +11,10 @@ public Integer getId() {
return 2;
}
+ public int getPrimitiveId() {
+ return getId() * -1;
+ }
+
public Name getName() {
return name;
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionFailureTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionFailureTest.java
index d8f0885a4a699..229a863fbd64e 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionFailureTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionFailureTest.java
@@ -23,6 +23,7 @@ public class OrOperatorTemplateExtensionFailureTest {
"{@io.quarkus.qute.deployment.typesafe.Item item}\n" +
" {data:item.name.or(item2.name).pleaseMakeMyCaseUpper}\n" +
" {item.name.or(item2.name).pleaseMakeMyCaseUpper}\n" +
+ " {item.getPrimitiveId().or(item2.getPrimitiveId()).missingMethod()}\n" +
"{/for}\n"),
"templates/item.html"))
.assertException(t -> {
@@ -43,6 +44,9 @@ public class OrOperatorTemplateExtensionFailureTest {
assertTrue(te.getMessage().contains(
"{data:item.name.or(item2.name).pleaseMakeMyCaseUpper}: Property/method [pleaseMakeMyCaseUpper] not found on class [java.lang.String]"),
te.getMessage());
+ assertTrue(te.getMessage().contains(
+ "{item.getPrimitiveId().or(item2.getPrimitiveId()).missingMethod()}: Property/method [missingMethod()]"),
+ te.getMessage());
});
@Test
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionTest.java
index f2219c17a9e5d..76280ac44ff6d 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OrOperatorTemplateExtensionTest.java
@@ -16,6 +16,8 @@ public class OrOperatorTemplateExtensionTest {
public static final String ITEM_NAME = "Test Name";
public static final String ITEM_WITH_NAME = "itemWithName";
public static final String ITEM = "item";
+ public static final String ITEMS = "items";
+ public static final String ITEMS_2 = "items2";
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
@@ -24,10 +26,14 @@ public class OrOperatorTemplateExtensionTest {
.addAsResource(new StringAsset(
"{@io.quarkus.qute.deployment.typesafe.ItemWithName itemWithName}\n" +
"{@io.quarkus.qute.deployment.typesafe.Item item}\n" +
+ "{@io.quarkus.qute.deployment.typesafe.Item[] items}\n" +
+ "{@io.quarkus.qute.deployment.typesafe.Item[] items2}\n" +
"{#for otherItem in item.otherItems}\n" +
- "{missing.or(alsoMissing.or('item id is: ')).toLowerCase}" +
+ "{missing.or(alsoMissing.or('result is: ')).toLowerCase}" +
"{item.name.or(itemWithName.name).toUpperCase}" +
- "{otherItem.id.or(itemWithName.id).longValue()}" +
+ "{items.or(items2).length}" + // test arrays
+ "{otherItem.id.or(itemWithName.id).longValue()}" + // tests boxed type
+ "{otherItem.getPrimitiveId().or(itemWithName.getPrimitiveId()).longValue()}" + // tests primitive type
"{/for}\n"),
"templates/item.html"));
@@ -36,18 +42,22 @@ public class OrOperatorTemplateExtensionTest {
@Test
public void test() {
- final String expected = "item id is: " + ITEM_NAME.toUpperCase();
+ final String expected = "result is: " + ITEM_NAME.toUpperCase();
final ItemWithName itemWithName = new ItemWithName(new ItemWithName.Name());
- // id comes from OtherItem, name is String and toUpperCase is method from String
- assertEquals(expected + OtherItem.ID,
- item.data(ITEM, new Item(ITEM_NAME.toUpperCase(), new OtherItem()), ITEM_WITH_NAME, itemWithName).render()
+ // ids comes from OtherItem, name is String and toUpperCase is method from String
+ Item[] items = new Item[4];
+ assertEquals(expected + items.length + OtherItem.ID + OtherItem.PRIMITIVE_ID,
+ item.data(ITEM, new Item(ITEM_NAME.toUpperCase(), new OtherItem()), ITEM_WITH_NAME, itemWithName,
+ ITEMS, items, ITEMS_2, null).render()
.trim());
- // id comes from ItemWithName, name comes from ItemWithName.Name and toUpperCase is regular method
+ // ids comes from ItemWithName, name comes from ItemWithName.Name and toUpperCase is regular method
+ Item[] items2 = new Item[2];
assertEquals(
- expected + itemWithName.getId(),
- item.data(ITEM, new Item(null, (OtherItem) null), ITEM_WITH_NAME, itemWithName).render().trim());
+ expected + items2.length + itemWithName.getId() + itemWithName.getPrimitiveId(),
+ item.data(ITEM, new Item(null, (OtherItem) null), ITEM_WITH_NAME, itemWithName,
+ ITEMS, null, ITEMS_2, items2).render().trim());
}
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OtherItem.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OtherItem.java
index d3d6d0caa33e5..26cabb8f224f3 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OtherItem.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/OtherItem.java
@@ -3,9 +3,14 @@
public class OtherItem {
static final int ID = 1;
+ static final int PRIMITIVE_ID = ID * -1;
public Integer getId() {
return ID;
}
+ public int getPrimitiveId() {
+ return PRIMITIVE_ID;
+ }
+
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java
index 1c418fcb80bbe..6f7f7ba7af2cb 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java
@@ -8,7 +8,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.test.QuarkusUnitTest;
@@ -35,7 +34,6 @@ public static class Templates {
static native TemplateInstance items(List items);
- @CheckedFragment
static native TemplateInstance items$item(Item it);
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateIgnoreFragmentsTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateIgnoreFragmentsTest.java
new file mode 100644
index 0000000000000..81714d85e55cc
--- /dev/null
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateIgnoreFragmentsTest.java
@@ -0,0 +1,41 @@
+package io.quarkus.qute.deployment.typesafe.fragment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.qute.CheckedTemplate;
+import io.quarkus.qute.TemplateInstance;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class CheckedTemplateIgnoreFragmentsTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(root -> root
+ .addClasses(Templates.class, Item.class)
+ .addAsResource(new StringAsset(
+ "{#each items}{it.name}{/each}"),
+ "templates/CheckedTemplateIgnoreFragmentsTest/items.html")
+ .addAsResource(new StringAsset("{it.name}"),
+ "templates/CheckedTemplateIgnoreFragmentsTest/items$item.html"));
+
+ @Test
+ public void testFragment() {
+ assertEquals("Foo", Templates.items(List.of(new Item("Foo"))).render());
+ assertEquals("Foo", Templates.items$item(new Item("Foo")).render());
+ }
+
+ @CheckedTemplate(ignoreFragments = true)
+ public static class Templates {
+
+ static native TemplateInstance items(List items);
+
+ static native TemplateInstance items$item(Item it);
+ }
+
+}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/ComplexCheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/ComplexCheckedTemplateFragmentTest.java
index 5ce4250291ab7..07e63075eba1b 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/ComplexCheckedTemplateFragmentTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/ComplexCheckedTemplateFragmentTest.java
@@ -8,7 +8,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.test.QuarkusUnitTest;
@@ -42,10 +41,8 @@ public static class Templates {
static native TemplateInstance items(List items);
- @CheckedFragment
static native TemplateInstance items$item_b(List foo, int size);
- @CheckedFragment
static native TemplateInstance items$item_a(List foo);
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidMethodNameCheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidMethodNameCheckedTemplateFragmentTest.java
index 5266dfce2cb32..220dc85a51674 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidMethodNameCheckedTemplateFragmentTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidMethodNameCheckedTemplateFragmentTest.java
@@ -10,7 +10,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateInstance;
@@ -36,7 +35,7 @@ public class InvalidMethodNameCheckedTemplateFragmentTest {
}
assertNotNull(te, t.getMessage());
assertEquals(
- "[item] is not a valid name of a checked fragment method: InvalidMethodNameCheckedTemplateFragmentTest$Templates.item()",
+ "No template matching the path InvalidMethodNameCheckedTemplateFragmentTest/item could be found for: io.quarkus.qute.deployment.typesafe.fragment.InvalidMethodNameCheckedTemplateFragmentTest$Templates.item",
te.getMessage());
});;
@@ -50,7 +49,6 @@ public static class Templates {
static native TemplateInstance items(List items);
- @CheckedFragment
static native TemplateInstance item(Item it);
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidParamTypeCheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidParamTypeCheckedTemplateFragmentTest.java
index 64d2da127d2be..881136fe9f410 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidParamTypeCheckedTemplateFragmentTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/InvalidParamTypeCheckedTemplateFragmentTest.java
@@ -10,7 +10,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateInstance;
@@ -50,7 +49,6 @@ public static class Templates {
static native TemplateInstance items(List items);
- @CheckedFragment
static native TemplateInstance items$item(String it);
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingCheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingCheckedTemplateFragmentTest.java
index dced8cbdceaad..ad9e732cd17b6 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingCheckedTemplateFragmentTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingCheckedTemplateFragmentTest.java
@@ -10,7 +10,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateInstance;
@@ -49,7 +48,6 @@ public static class Templates {
static native TemplateInstance items(List items);
- @CheckedFragment
static native TemplateInstance items$item(Item it);
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingParamCheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingParamCheckedTemplateFragmentTest.java
index 37597b1092a10..1d72574b497d7 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingParamCheckedTemplateFragmentTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/MissingParamCheckedTemplateFragmentTest.java
@@ -10,7 +10,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.qute.CheckedFragment;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateInstance;
@@ -50,7 +49,6 @@ public static class Templates {
static native TemplateInstance items(List items);
- @CheckedFragment
static native TemplateInstance items$item();
}
diff --git a/extensions/redis-client/runtime/pom.xml b/extensions/redis-client/runtime/pom.xml
index 93c70cbe866e9..623959b846e62 100644
--- a/extensions/redis-client/runtime/pom.xml
+++ b/extensions/redis-client/runtime/pom.xml
@@ -107,12 +107,18 @@
test-containers
+
+ redis/redis-stack:7.0.2-RC2
+ maven-surefire-pluginfalse
+
+ ${redis.base.image}
+
@@ -124,5 +130,19 @@
+
+
+ redis-5
+
+ redis:5
+
+
+
+
+ redis-6
+
+ redis:6
+
+
diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/AbstractSortedSetCommands.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/AbstractSortedSetCommands.java
index a23664dec32c6..d0e63dee9960c 100644
--- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/AbstractSortedSetCommands.java
+++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/AbstractSortedSetCommands.java
@@ -28,6 +28,7 @@
import io.smallrye.mutiny.helpers.ParameterValidation;
import io.vertx.mutiny.redis.client.Command;
import io.vertx.mutiny.redis.client.Response;
+import io.vertx.redis.client.ResponseType;
class AbstractSortedSetCommands extends ReactiveSortable {
@@ -733,22 +734,37 @@ protected String getScoreAsString(double score) {
final List> decodeAsListOfScoredValues(Response response) {
List> list = new ArrayList<>();
-
- for (Response r : response) {
- list.add(decodeAsScoredValue(r));
+ if (response.iterator().next().type() == ResponseType.BULK) {
+ // Redis 5
+ V current = null;
+ for (Response nested : response) {
+ if (current == null) {
+ current = decodeV(nested);
+ } else {
+ list.add(ScoredValue.of(current, nested.toDouble()));
+ current = null;
+ }
+ }
+ return list;
+ } else {
+ for (Response r : response) {
+ list.add(decodeAsScoredValue(r));
+ }
+ return list;
}
- return list;
}
ScoredValue decodeAsScoredValue(Response r) {
if (r == null || r.getDelegate() == null) {
return null;
}
+
if (r.size() == 0) {
return ScoredValue.empty();
}
return ScoredValue.of(decodeV(r.get(0)), r.get(1).toDouble());
+
}
Double decodeAsDouble(Response r) {
diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/Marshaller.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/Marshaller.java
index dca44f726685c..3eb7b39df0228 100644
--- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/Marshaller.java
+++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/datasource/Marshaller.java
@@ -97,15 +97,30 @@ final T decode(Class clazz, byte[] r) {
}
public Map decodeAsMap(Response response, Class typeOfField, Class typeOfValue) {
- if (response == null) {
+ if (response == null || response.size() == 0) {
return Collections.emptyMap();
}
Map map = new LinkedHashMap<>();
- for (Response member : response) {
- for (String key : member.getKeys()) {
- F field = decode(typeOfField, key.getBytes(StandardCharsets.UTF_8));
- V val = decode(typeOfValue, response.get(key));
- map.put(field, val);
+ if (response.iterator().next().type() == ResponseType.BULK) {
+ // Redis 5
+ F current = null; // Just in case it's Redis 5.
+ for (Response member : response) {
+ if (current == null) {
+ current = decode(typeOfField, member.toString().getBytes(StandardCharsets.UTF_8));
+ } else {
+ V val = decode(typeOfValue, member);
+ map.put(current, val);
+ current = null;
+ }
+ }
+ } else {
+ // MULTI - Redis 6+
+ for (Response member : response) {
+ for (String key : member.getKeys()) {
+ F field = decode(typeOfField, key.getBytes(StandardCharsets.UTF_8));
+ V val = decode(typeOfValue, response.get(key));
+ map.put(field, val);
+ }
}
}
return map;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/BloomCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/BloomCommandsTest.java
index ed695c15437ad..4bfc034777d61 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/BloomCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/BloomCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.datasource.bloom.BloomCommands;
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
+@RequiresCommand("bf.add")
public class BloomCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CountMinCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CountMinCommandsTest.java
index 5c3f20a934559..b02bbc2b8550e 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CountMinCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CountMinCommandsTest.java
@@ -15,6 +15,7 @@
import io.quarkus.redis.datasource.countmin.CountMinCommands;
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
+@RequiresCommand("cms.query")
public class CountMinCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CuckooCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CuckooCommandsTest.java
index be44f0cfad1c8..a90f502ef315f 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CuckooCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/CuckooCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.datasource.cuckoo.CuckooCommands;
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
+@RequiresCommand("cf.add")
public class CuckooCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/DatasourceTestBase.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/DatasourceTestBase.java
index 4d7765cc83626..2ef0556e1e808 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/DatasourceTestBase.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/DatasourceTestBase.java
@@ -1,65 +1,27 @@
package io.quarkus.redis.datasource;
-import java.util.ArrayList;
-import java.util.List;
import java.util.UUID;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.utility.DockerImageName;
+import org.junit.jupiter.api.extension.ExtendWith;
import io.vertx.mutiny.core.Vertx;
-import io.vertx.mutiny.redis.client.Command;
import io.vertx.mutiny.redis.client.Redis;
import io.vertx.mutiny.redis.client.RedisAPI;
-import io.vertx.mutiny.redis.client.Request;
-import io.vertx.mutiny.redis.client.Response;
+@ExtendWith(RedisServerExtension.class)
public class DatasourceTestBase {
final String key = UUID.randomUUID().toString();
-
- public static Vertx vertx;
- public static Redis redis;
- public static RedisAPI api;
-
- static GenericContainer> server = new GenericContainer<>(
- DockerImageName.parse(System.getProperty("redis.base.image", "redis/redis-stack:7.0.2-RC2")))
- .withExposedPorts(6379);
+ static Redis redis;
+ static Vertx vertx;
+ static RedisAPI api;
@BeforeAll
static void init() {
- vertx = Vertx.vertx();
- server.start();
- redis = Redis.createClient(vertx, "redis://" + server.getHost() + ":" + server.getFirstMappedPort());
- // If you want to use a local redis: redis = Redis.createClient(vertx, "redis://localhost:" + 6379);
- api = RedisAPI.api(redis);
- }
-
- @AfterAll
- static void cleanup() {
- redis.close();
- server.close();
- vertx.closeAndAwait();
- }
-
- public static List getAvailableCommands() {
- List commands = new ArrayList<>();
- Response list = redis.send(Request.cmd(Command.COMMAND)).await().indefinitely();
- for (Response response : list) {
- commands.add(response.get(0).toString());
- }
- return commands;
-
- }
-
- public static String getRedisVersion() {
- String info = redis.send(Request.cmd(Command.INFO)).await().indefinitely().toString();
- // Look for the redis_version line
- return info.lines().filter(s -> s.startsWith("redis_version")).findAny()
- .map(line -> line.split(":")[1])
- .orElseThrow();
+ redis = RedisServerExtension.redis;
+ vertx = RedisServerExtension.vertx;
+ api = RedisServerExtension.api;
}
}
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java
index ea34b4ef21c71..beb076f528d75 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java
@@ -88,8 +88,13 @@ void geoadd() {
added = geo.geoadd(key, GeoItem.of(Place.suze, SUZE_LONGITUDE, SUZE_LATITUDE));
assertThat(added).isTrue();
+ }
- added = geo.geoadd(key, GeoItem.of(new Place("foo", 1), CRUSSOL_LONGITUDE, CRUSSOL_LATITUDE), new GeoAddArgs().nx());
+ @Test
+ @RequiresRedis6OrHigher
+ void geoaddWithNx() {
+ boolean added = geo.geoadd(key, GeoItem.of(new Place("foo", 1), CRUSSOL_LONGITUDE, CRUSSOL_LATITUDE),
+ new GeoAddArgs().nx());
assertThat(added).isTrue();
}
@@ -106,6 +111,7 @@ void geoaddValue() {
}
@Test
+ @RequiresRedis6OrHigher
void geoAddWithXXorCH() {
boolean added = geo.geoadd(key, 44.9396, CRUSSOL_LATITUDE, Place.crussol, new GeoAddArgs().xx());
@@ -363,6 +369,7 @@ void georadiusbymember() {
}
@Test
+ @RequiresRedis6OrHigher
void georadiusbymemberStoreDistWithCountAndSort() {
populate();
String resultKey = key + "-2";
@@ -376,6 +383,19 @@ void georadiusbymemberStoreDistWithCountAndSort() {
assertThat(dist.get(0).score()).isBetween(55d, 60d);
}
+ @Test
+ void georadiusbymemberStoreDistWithSort() {
+ populate();
+ String resultKey = key + "-2";
+ long result = geo.georadiusbymember(key, Place.crussol, 100, GeoUnit.KM,
+ new GeoRadiusStoreArgs().descending().storeDistKey(resultKey));
+ assertThat(result).isEqualTo(3);
+
+ SortedSetCommands commands = ds.sortedSet(String.class);
+ List> dist = commands.zrangeWithScores(resultKey, 0, -1);
+ assertThat(dist).hasSize(3);
+ }
+
@Test
void georadiusbymemberWithArgs() {
populate();
@@ -442,6 +462,7 @@ void georadiusbymemberWithNullArgs() {
}
@Test
+ @RequiresRedis6OrHigher
void geosearchWithCountAndSort() {
populate();
@@ -460,6 +481,7 @@ void geosearchWithCountAndSort() {
}
@Test
+ @RequiresRedis6OrHigher
void geosearchWithArgs() {
populate();
@@ -508,6 +530,7 @@ void geosearchWithArgs() {
}
@Test
+ @RequiresRedis6OrHigher
void geosearchStoreWithCountAndSort() {
populate();
String resultKey = key + "-2";
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GraphCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GraphCommandsTest.java
index 9d901d8ca9c59..6de00ca275841 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GraphCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GraphCommandsTest.java
@@ -15,6 +15,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.vertx.core.json.JsonObject;
+@RequiresCommand("graph.query")
public class GraphCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/HashCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/HashCommandsTest.java
index a5dd256e798c6..1c799b2e67e24 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/HashCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/HashCommandsTest.java
@@ -11,6 +11,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -97,6 +98,22 @@ public void hgetall() {
assertThat(hash.hgetall("missing")).isEmpty();
}
+ /**
+ * Reproducer for #28837.
+ */
+ @Test
+ public void hgetallUsingIntegers() {
+ var cmd = ds.hash(Integer.class);
+ String key = UUID.randomUUID().toString();
+ assertThat(cmd.hgetall(key).isEmpty()).isTrue();
+
+ cmd.hset(key, Map.of("a", 1, "b", 2, "c", 3));
+
+ Map map = cmd.hgetall(key);
+
+ assertThat(map).hasSize(3);
+ }
+
@Test
void hincrby() {
assertThat(hash.hincrby(key, "one", 1)).isEqualTo(1);
@@ -171,6 +188,7 @@ void hmsetWithNulls() {
}
@Test
+ @RequiresRedis7OrHigher
void hrandfield() {
hash.hset(key, Map.of("one", Person.person1, "two", Person.person2, "three", Person.person3));
@@ -179,6 +197,7 @@ void hrandfield() {
}
@Test
+ @RequiresRedis7OrHigher
void hrandfieldWithValues() {
Map map = Map.of("one", Person.person1, "two", Person.person2, "three", Person.person3);
hash.hset(key, map);
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/JsonCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/JsonCommandsTest.java
index e1332920cd2ee..73e68ea63ea14 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/JsonCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/JsonCommandsTest.java
@@ -19,6 +19,7 @@
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
+@RequiresCommand("json.get")
public class JsonCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/KeyCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/KeyCommandsTest.java
index 90949c1cfc581..4c68e0fa65b57 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/KeyCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/KeyCommandsTest.java
@@ -75,6 +75,7 @@ void unlink() {
}
@Test
+ @RequiresRedis6OrHigher
void copy() {
values.set(key, Person.person7);
assertThat(keys.copy(key, key + "2")).isTrue();
@@ -83,6 +84,7 @@ void copy() {
}
@Test
+ @RequiresRedis6OrHigher
void copyWithReplace() {
values.set(key, Person.person7);
values.set(key + 2, Person.person1);
@@ -91,6 +93,7 @@ void copyWithReplace() {
}
@Test
+ @RequiresRedis6OrHigher
void copyWithDestinationDb() {
ds.withConnection(connection -> {
connection.value(String.class, Person.class).set(key, Person.person7);
@@ -398,6 +401,7 @@ void scanWithArgs() {
}
@Test
+ @RequiresRedis6OrHigher
void scanWithType() {
values.set("key1", Person.person7);
ds.list(Person.class).lpush("key2", Person.person7);
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ListCommandTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ListCommandTest.java
index c05422f3a2a36..d07f0b84e823b 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ListCommandTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ListCommandTest.java
@@ -157,6 +157,7 @@ void lmpopMany() {
}
@Test
+ @RequiresRedis6OrHigher
void lpopCount() {
assertThat(lists.lpop(key, 1)).isEqualTo(List.of());
lists.rpush(key, Person.person1, Person.person2);
@@ -164,6 +165,7 @@ void lpopCount() {
}
@Test
+ @RequiresRedis6OrHigher
void lpos() {
lists.rpush(key, Person.person4, Person.person5, Person.person6, Person.person1, Person.person2, Person.person3,
@@ -259,6 +261,7 @@ void rpop() {
}
@Test
+ @RequiresRedis6OrHigher
void rpopCount() {
assertThat(lists.rpop(key, 1)).isEqualTo(List.of());
lists.rpush(key, Person.person1, Person.person2);
@@ -301,6 +304,7 @@ void rpushxMultiple() {
}
@Test
+ @RequiresRedis6OrHigher
void lmove() {
String list1 = key;
String list2 = key + "-2";
@@ -313,6 +317,7 @@ void lmove() {
}
@Test
+ @RequiresRedis6OrHigher
void blmove() {
String list1 = key;
String list2 = key + "-2";
@@ -325,6 +330,7 @@ void blmove() {
}
@Test
+ @RequiresRedis6OrHigher
void sort() {
ListCommands commands = ds.list(String.class, String.class);
commands.rpush(key, "9", "5", "1", "3", "5", "8", "7", "6", "2", "4");
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis6OrHigherCondition.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis6OrHigherCondition.java
new file mode 100644
index 0000000000000..9f21573655845
--- /dev/null
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis6OrHigherCondition.java
@@ -0,0 +1,35 @@
+package io.quarkus.redis.datasource;
+
+import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled;
+import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled;
+
+import java.util.Optional;
+
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.util.AnnotationUtils;
+
+class Redis6OrHigherCondition implements ExecutionCondition {
+
+ private static final ConditionEvaluationResult ENABLED_BY_DEFAULT = enabled("@RequiresRedis6OrHigher is not present");
+
+ @Override
+ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
+ Optional optional = AnnotationUtils.findAnnotation(context.getElement(),
+ RequiresRedis6OrHigher.class);
+
+ if (optional.isPresent()) {
+ String version = RedisServerExtension.getRedisVersion();
+
+ return isRedis6orHigher(version) ? enabled("Redis " + version + " >= 6")
+ : disabled("Disabled, Redis " + version + " < 6");
+ }
+
+ return ENABLED_BY_DEFAULT;
+ }
+
+ public static boolean isRedis6orHigher(String version) {
+ return Integer.parseInt(version.split("\\.")[0]) >= 6;
+ }
+}
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis7OrHigherCondition.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis7OrHigherCondition.java
index bcdbc6a6fb5f4..96c63ed4cffec 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis7OrHigherCondition.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/Redis7OrHigherCondition.java
@@ -20,7 +20,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
RequiresRedis7OrHigher.class);
if (optional.isPresent()) {
- String version = DatasourceTestBase.getRedisVersion();
+ String version = RedisServerExtension.getRedisVersion();
return isRedis7orHigher(version) ? enabled("Redis " + version + " >= 7")
: disabled("Disabled, Redis " + version + " < 7");
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisCommandCondition.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisCommandCondition.java
index 4bbb799ccf909..d3bd0f464041c 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisCommandCondition.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisCommandCondition.java
@@ -22,7 +22,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
if (optional.isPresent()) {
String[] cmd = optional.get().value();
- List commands = DatasourceTestBase.getAvailableCommands();
+ List commands = RedisServerExtension.getAvailableCommands();
for (String c : cmd) {
if (!commands.contains(c.toLowerCase())) {
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisServerExtension.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisServerExtension.java
new file mode 100644
index 0000000000000..387beb2c6c6f1
--- /dev/null
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RedisServerExtension.java
@@ -0,0 +1,93 @@
+package io.quarkus.redis.datasource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import io.vertx.mutiny.core.Vertx;
+import io.vertx.mutiny.redis.client.Command;
+import io.vertx.mutiny.redis.client.Redis;
+import io.vertx.mutiny.redis.client.RedisAPI;
+import io.vertx.mutiny.redis.client.Request;
+import io.vertx.mutiny.redis.client.Response;
+
+@SuppressWarnings("resource")
+public class RedisServerExtension implements BeforeAllCallback, AfterAllCallback {
+
+ static GenericContainer> server = new GenericContainer<>(
+ DockerImageName.parse(System.getProperty("redis.base.image", "redis/redis-stack:7.0.2-RC2")))
+ .withExposedPorts(6379);
+ static Redis redis;
+ static RedisAPI api;
+ static Vertx vertx;
+
+ public static String getHost() {
+ return server.getHost();
+ }
+
+ public static Integer getFirstMappedPort() {
+ return server.getFirstMappedPort();
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) {
+ init();
+ }
+
+ private static boolean init() {
+ if (!server.isRunning()) {
+ server.start();
+ vertx = Vertx.vertx();
+ redis = Redis.createClient(vertx,
+ "redis://" + RedisServerExtension.getHost() + ":" + RedisServerExtension.getFirstMappedPort());
+ // If you want to use a local redis: redis = Redis.createClient(vertx, "redis://localhost:" + 6379);
+ api = RedisAPI.api(redis);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) {
+ cleanup();
+ }
+
+ private static void cleanup() {
+ redis.close();
+ vertx.closeAndAwait();
+ server.stop();
+ }
+
+ public static List getAvailableCommands() {
+ boolean mustcleanup = init();
+ List commands = new ArrayList<>();
+ Response list = redis.send(Request.cmd(Command.COMMAND)).await().indefinitely();
+ for (Response response : list) {
+ commands.add(response.get(0).toString());
+ }
+ if (mustcleanup) {
+ cleanup();
+ }
+ return commands;
+
+ }
+
+ public static String getRedisVersion() {
+ boolean mustcleanup = init();
+ String info = redis.send(Request.cmd(Command.INFO)).await().indefinitely().toString();
+ if (mustcleanup) {
+ cleanup();
+ }
+ // Look for the redis_version line
+ return info.lines().filter(s -> s.startsWith("redis_version")).findAny()
+ .map(line -> line.split(":")[1])
+ .orElseThrow();
+
+ }
+
+}
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresCommand.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresCommand.java
index b06160555ba15..d0117ae553b69 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresCommand.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresCommand.java
@@ -8,7 +8,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
-@Target(ElementType.METHOD)
+@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@ExtendWith(RedisCommandCondition.class)
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresRedis6OrHigher.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresRedis6OrHigher.java
new file mode 100644
index 0000000000000..3408440f5b592
--- /dev/null
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/RequiresRedis6OrHigher.java
@@ -0,0 +1,18 @@
+package io.quarkus.redis.datasource;
+
+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 org.junit.jupiter.api.extension.ExtendWith;
+
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@ExtendWith(Redis6OrHigherCondition.class)
+@interface RequiresRedis6OrHigher {
+
+ // Important the class must extend DatasourceTestBase.
+}
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SetCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SetCommandsTest.java
index f862478a92939..9a11440bdc311 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SetCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SetCommandsTest.java
@@ -114,6 +114,7 @@ void smembers() {
}
@Test
+ @RequiresRedis6OrHigher
void smismember() {
assertThat(sets.smismember(key, person1)).isEqualTo(List.of(false));
sets.sadd(key, person1);
@@ -309,6 +310,7 @@ private void populate() {
}
@Test
+ @RequiresRedis6OrHigher
void sort() {
SetCommands commands = ds.set(String.class, String.class);
commands.sadd(key, "9", "5", "1", "3", "5", "8", "7", "6", "2", "4");
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SortedSetCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SortedSetCommandsTest.java
index b70c037f86ff7..bb59e5f8acd19 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SortedSetCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/SortedSetCommandsTest.java
@@ -104,6 +104,7 @@ void zaddxx() {
}
@Test
+ @RequiresRedis6OrHigher
void zaddch() {
assertThat(setOfPlaces.zadd(key, 1.0, Place.crussol)).isTrue();
assertThat(setOfPlaces.zadd(key, new ZAddArgs().ch().xx(), 2.0, Place.crussol)).isTrue();
@@ -114,6 +115,7 @@ void zaddch() {
}
@Test
+ @RequiresRedis6OrHigher
void zaddincr() {
assertThat(setOfPlaces.zadd(key, 1.0, Place.crussol)).isTrue();
assertThat(setOfPlaces.zaddincr(key, 2.0, Place.crussol)).isEqualTo(3.0);
@@ -138,6 +140,7 @@ void zaddincrxx() {
}
@Test
+ @RequiresRedis6OrHigher
void zaddgt() {
assertThat(setOfPlaces.zadd(key, 1.0, Place.crussol)).isTrue();
// new score less than the current score
@@ -155,6 +158,7 @@ void zaddgt() {
}
@Test
+ @RequiresRedis6OrHigher
void zaddlt() {
assertThat(setOfPlaces.zadd(key, 2.0, Place.crussol)).isTrue();
// new score greater than the current score
@@ -195,6 +199,7 @@ void zcount() {
}
@Test
+ @RequiresRedis6OrHigher
void zdiff() {
String zset1 = "zset1";
String zset2 = "zset2";
@@ -213,6 +218,7 @@ void zdiff() {
}
@Test
+ @RequiresRedis6OrHigher
void zdiffstore() {
String zset1 = "zset1";
String zset2 = "zset2";
@@ -255,6 +261,7 @@ void zinterstore() {
}
@Test
+ @RequiresRedis6OrHigher
void zinterstoreWithArgs() {
setOfPlaces.zadd("zset1", Map.of(Place.crussol, 1.0, Place.grignan, 2.0));
setOfPlaces.zadd("zset2", Map.of(Place.crussol, 2.0, Place.grignan, 3.0, Place.suze, 4.0));
@@ -301,6 +308,7 @@ void zpopmax() {
}
@Test
+ @RequiresRedis6OrHigher
void zrandmember() {
setOfPlaces.zadd("zset", Map.of(Place.crussol, 2.0, Place.grignan, 3.0, Place.suze, 4.0));
assertThat(setOfPlaces.zrandmember("zset")).isIn(Place.crussol, Place.grignan, Place.suze);
@@ -326,6 +334,7 @@ void zrangeWithScores() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangebyscore() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
@@ -343,6 +352,7 @@ void zrangebyscore() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangebyscoreWithScores() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
@@ -366,6 +376,7 @@ void zrangebyscoreWithScores() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangebyscoreWithScoresInfinity() {
setOfPlaces.zadd(key, Map.of(Place.crussol, Double.POSITIVE_INFINITY, Place.grignan, Double.NEGATIVE_INFINITY));
assertThat(setOfPlaces.zrangebyscoreWithScores(key, new ScoreRange<>(null, null))).hasSize(2);
@@ -373,6 +384,7 @@ void zrangebyscoreWithScoresInfinity() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangestorebylex() {
setOfStrings.zadd(key, Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0));
assertThat(setOfStrings.zrangestorebylex("key1", key, new Range<>("b", "d"), new ZRangeArgs().limit(0, 4)))
@@ -384,6 +396,7 @@ void zrangestorebylex() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangestorebyscore() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
assertThat(setOfPlaces.zrangestorebyscore("key1", key, new ScoreRange<>(0.0, 2.0),
@@ -395,6 +408,7 @@ void zrangestorebyscore() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangestore() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
assertThat(setOfPlaces.zrangestore("key1", key, 0, -1)).isEqualTo(4);
@@ -447,6 +461,7 @@ void zremrangebyrank() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrange() {
populate();
assertThat(setOfPlaces.zrange(key, 0, -1, new ZRangeArgs().rev()))
@@ -454,6 +469,7 @@ void zrevrange() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrangeWithScores() {
populate();
assertThat(setOfPlaces.zrangeWithScores(key, 0, -1, new ZRangeArgs().rev()))
@@ -462,6 +478,7 @@ void zrevrangeWithScores() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrangebylex() {
populateManyStringEntries();
assertThat(setOfStrings.zrangebylex(key, Range.unbounded(), new ZRangeArgs().rev())).hasSize(100);
@@ -474,6 +491,7 @@ void zrevrangebylex() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrangebyscore() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
ZRangeArgs rev = new ZRangeArgs().rev();
@@ -492,6 +510,7 @@ void zrevrangebyscore() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrangebyscoreWithScores() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
ZRangeArgs rev = new ZRangeArgs().rev();
@@ -523,6 +542,7 @@ void zrevrank() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrangestorebylex() {
setOfStrings.zadd(key, Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0));
assertThat(setOfStrings.zrangestorebylex("key1", key, new Range<>("c", "-"),
@@ -531,6 +551,7 @@ void zrevrangestorebylex() {
}
@Test
+ @RequiresRedis6OrHigher
void zrevrangestorebyscore() {
setOfPlaces.zadd(key, Map.of(Place.crussol, 1.0, Place.grignan, 2.0, Place.suze, 3.0, Place.adhemar, 4.0));
assertThat(
@@ -728,6 +749,7 @@ void zlexcount() {
}
@Test
+ @RequiresRedis6OrHigher
public void zmscore() {
setOfPlaces.zadd("zset1", Map.of(Place.crussol, 1.0, Place.grignan, 2.0));
assertThat(setOfPlaces.zmscore("zset1", Place.crussol, Place.suze, Place.grignan))
@@ -821,6 +843,7 @@ public void bzmpopMax() {
}
@Test
+ @RequiresRedis6OrHigher
void zrangebylex() {
populateManyStringEntries();
@@ -836,6 +859,7 @@ void zrangebylex() {
}
@Test
+ @RequiresRedis6OrHigher
void zremrangebylex() {
populateManyStringEntries();
assertThat(setOfStrings.zremrangebylex(key, new Range<>("aaa", false, "zzz", true))).isEqualTo(100);
@@ -845,6 +869,7 @@ void zremrangebylex() {
}
@Test
+ @RequiresRedis6OrHigher
void zunion() {
String zset1 = "zset1";
String zset2 = "zset2";
@@ -868,6 +893,7 @@ void zunion() {
}
@Test
+ @RequiresRedis6OrHigher
void zinter() {
String zset1 = "zset1";
String zset2 = "zset2";
@@ -887,6 +913,7 @@ void zinter() {
}
@Test
+ @RequiresRedis6OrHigher
void zinterWithScores() {
String zset1 = "zset1";
String zset2 = "zset2";
@@ -904,6 +931,7 @@ void zinterWithScores() {
}
@Test
+ @RequiresRedis6OrHigher
void zinterWithArgs() {
String zset1 = "zset1";
String zset2 = "zset2";
@@ -931,6 +959,7 @@ private void populateManyStringEntries() {
}
@Test
+ @RequiresRedis6OrHigher
void sort() {
SortedSetCommands commands = ds.sortedSet(String.class, String.class);
commands.zadd(key, Map.of("9", 9.0, "1", 1.0, "3", 3.0, "5", 5.0,
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/StringCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/StringCommandsTest.java
index 3e0e16ca628eb..120f580c58181 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/StringCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/StringCommandsTest.java
@@ -22,6 +22,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
@SuppressWarnings("deprecation")
+@RequiresRedis6OrHigher // The ValueCommandsTest verify the behavior with Redis 5
public class StringCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TopKCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TopKCommandsTest.java
index 016ca82905a3a..0f952ff6fd29c 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TopKCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TopKCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.datasource.topk.TopKCommands;
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
+@RequiresCommand("topk.add")
public class TopKCommandsTest extends DatasourceTestBase {
private RedisDataSource ds;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalBloomCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalBloomCommandsTest.java
index ce9fecdf1ae71..8982f9f50560f 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalBloomCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalBloomCommandsTest.java
@@ -17,6 +17,7 @@
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
@SuppressWarnings("unchecked")
+@RequiresCommand("bf.add")
public class TransactionalBloomCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCountMinCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCountMinCommandsTest.java
index 6ea305b5c35ed..de133ca981e90 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCountMinCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCountMinCommandsTest.java
@@ -17,6 +17,7 @@
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
@SuppressWarnings({ "unchecked", "ConstantConditions" })
+@RequiresCommand("cms.query")
public class TransactionalCountMinCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCuckooCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCuckooCommandsTest.java
index be8a1cc7b31ef..53ee325df57d2 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCuckooCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalCuckooCommandsTest.java
@@ -17,6 +17,7 @@
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
@SuppressWarnings("unchecked")
+@RequiresCommand("cf.add")
public class TransactionalCuckooCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGeoCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGeoCommandsTest.java
index 503de2254a51a..9422bc9da9505 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGeoCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGeoCommandsTest.java
@@ -20,6 +20,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalGeoCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGraphCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGraphCommandsTest.java
index eb40c8a9b4f2d..bdd13499510d1 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGraphCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalGraphCommandsTest.java
@@ -18,6 +18,7 @@
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
@SuppressWarnings("unchecked")
+@RequiresCommand("graph.query")
public class TransactionalGraphCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHashCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHashCommandsTest.java
index 76145f2f90d1e..786a90799428a 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHashCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHashCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalHashCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHyperLogLogCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHyperLogLogCommandsTest.java
index 13d77c1b66a33..7c265414d3aae 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHyperLogLogCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalHyperLogLogCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalHyperLogLogCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalJsonCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalJsonCommandsTest.java
index 9944b1764c83e..ad50d48e42423 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalJsonCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalJsonCommandsTest.java
@@ -19,6 +19,7 @@
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
+@RequiresCommand("json.get")
public class TransactionalJsonCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalKeyTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalKeyTest.java
index 4043004f019c6..9c11473c8c7a6 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalKeyTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalKeyTest.java
@@ -18,6 +18,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalKeyTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalListCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalListCommandsTest.java
index 3d00167c5dfd5..61fa8059007e5 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalListCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalListCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalListCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSetCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSetCommandsTest.java
index 24c172c2c4dfe..5a2925243e498 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSetCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSetCommandsTest.java
@@ -14,6 +14,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalSetCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSortedSetCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSortedSetCommandsTest.java
index bfae930579803..b1ef77d5ba07d 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSortedSetCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalSortedSetCommandsTest.java
@@ -17,6 +17,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalSortedSetCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalStringCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalStringCommandsTest.java
index c9d8f6864fde6..35c2088fdea01 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalStringCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalStringCommandsTest.java
@@ -16,6 +16,7 @@
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
@SuppressWarnings("deprecation")
+@RequiresRedis6OrHigher
public class TransactionalStringCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalTopKCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalTopKCommandsTest.java
index eedd37d320668..1ad2f1a74d359 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalTopKCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalTopKCommandsTest.java
@@ -18,6 +18,7 @@
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
@SuppressWarnings({ "unchecked", "ConstantConditions" })
+@RequiresCommand("topk.add")
public class TransactionalTopKCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalValueCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalValueCommandsTest.java
index 7745073b19ba4..735bd3b8e390a 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalValueCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/TransactionalValueCommandsTest.java
@@ -15,6 +15,7 @@
import io.quarkus.redis.runtime.datasource.BlockingRedisDataSourceImpl;
import io.quarkus.redis.runtime.datasource.ReactiveRedisDataSourceImpl;
+@RequiresRedis6OrHigher
public class TransactionalValueCommandsTest extends DatasourceTestBase {
private RedisDataSource blocking;
diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ValueCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ValueCommandsTest.java
index fff497be12008..4fab036f15263 100644
--- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ValueCommandsTest.java
+++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/ValueCommandsTest.java
@@ -68,6 +68,7 @@ void getbit() {
}
@Test
+ @RequiresRedis6OrHigher
void getdel() {
values.set(key, value);
assertThat(values.getdel(key)).isEqualTo(value);
@@ -75,6 +76,7 @@ void getdel() {
}
@Test
+ @RequiresRedis6OrHigher
void getex() {
values.set(key, value);
assertThat(values.getex(key, new GetExArgs().ex(Duration.ofSeconds(100)))).isEqualTo(value);
@@ -176,6 +178,7 @@ void set() {
}
@Test
+ @RequiresRedis6OrHigher
void setExAt() {
KeyCommands keys = ds.key(String.class);
@@ -187,6 +190,7 @@ void setExAt() {
}
@Test
+ @RequiresRedis6OrHigher
void setKeepTTL() {
KeyCommands keys = ds.key(String.class);
@@ -207,6 +211,7 @@ void setNegativePX() {
}
@Test
+ @RequiresRedis7OrHigher
void setGet() {
assertThat(values.setGet(key, value)).isNull();
assertThat(values.setGet(key, "value2")).isEqualTo(value);
@@ -214,6 +219,7 @@ void setGet() {
}
@Test
+ @RequiresRedis7OrHigher
void setGetWithArgs() {
KeyCommands keys = ds.key(String.class);
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/MultipartOutputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/MultipartOutputTest.java
index 3a4e2977ed6d0..0a6a7cfcd8f2b 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/MultipartOutputTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/MultipartOutputTest.java
@@ -38,7 +38,6 @@ public void testSimple() {
.then()
.contentType(ContentType.MULTIPART)
.statusCode(200)
- .log().all()
.extract().asString();
assertContains(response, "name", MediaType.TEXT_PLAIN, EXPECTED_RESPONSE_NAME);
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/runtime/src/main/kotlin/org/jboss/resteasy/reactive/server/runtime/kotlin/ApplicationCoroutineScope.kt b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/runtime/src/main/kotlin/org/jboss/resteasy/reactive/server/runtime/kotlin/ApplicationCoroutineScope.kt
index 8f8f76acd7b08..5616f89ed3ab7 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/runtime/src/main/kotlin/org/jboss/resteasy/reactive/server/runtime/kotlin/ApplicationCoroutineScope.kt
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin/runtime/src/main/kotlin/org/jboss/resteasy/reactive/server/runtime/kotlin/ApplicationCoroutineScope.kt
@@ -42,7 +42,6 @@ class VertxDispatcher(private val vertxContext: Context, private val requestScop
try {
block.run()
} finally {
- CurrentRequestManager.set(null)
requestScope.deactivate()
}
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/FormDataOutputMapperGenerator.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/FormDataOutputMapperGenerator.java
deleted file mode 100644
index e591725ae5ed6..0000000000000
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/FormDataOutputMapperGenerator.java
+++ /dev/null
@@ -1,265 +0,0 @@
-package io.quarkus.resteasy.reactive.server.deployment;
-
-import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.FORM_PARAM;
-import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM;
-
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.ws.rs.core.MediaType;
-
-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.jandex.Type;
-import org.jboss.logging.Logger;
-import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
-import org.jboss.resteasy.reactive.server.core.multipart.MultipartMessageBodyWriter;
-import org.jboss.resteasy.reactive.server.core.multipart.MultipartOutputInjectionTarget;
-import org.jboss.resteasy.reactive.server.core.multipart.PartItem;
-
-import io.quarkus.deployment.bean.JavaBeanUtil;
-import io.quarkus.gizmo.AssignableResultHandle;
-import io.quarkus.gizmo.ClassCreator;
-import io.quarkus.gizmo.ClassOutput;
-import io.quarkus.gizmo.FieldDescriptor;
-import io.quarkus.gizmo.MethodCreator;
-import io.quarkus.gizmo.MethodDescriptor;
-import io.quarkus.gizmo.ResultHandle;
-
-final class FormDataOutputMapperGenerator {
-
- private static final Logger LOGGER = Logger.getLogger(FormDataOutputMapperGenerator.class);
-
- private static final String TRANSFORM_METHOD_NAME = "mapFrom";
- private static final String ARRAY_LIST_ADD_METHOD_NAME = "add";
-
- private FormDataOutputMapperGenerator() {
- }
-
- /**
- * Returns true whether the returning type uses either {@link org.jboss.resteasy.reactive.RestForm}
- * or {@link org.jboss.resteasy.reactive.server.core.multipart.FormData} annotations.
- */
- public static boolean isReturnTypeCompatible(ClassInfo returnTypeClassInfo, IndexView index) {
- // go up the class hierarchy until we reach Object
- ClassInfo currentClassInHierarchy = returnTypeClassInfo;
- while (true) {
- List fields = currentClassInHierarchy.fields();
- for (FieldInfo field : fields) {
- if (Modifier.isStatic(field.flags())) { // nothing we need to do about static fields
- continue;
- }
-
- if (field.annotation(REST_FORM_PARAM) != null || field.annotation(FORM_PARAM) != null) {
- // Found either @RestForm or @FormParam in returning class, it's compatible.
- return true;
- }
- }
-
- DotName superClassDotName = currentClassInHierarchy.superName();
- if (superClassDotName.equals(DotNames.OBJECT_NAME)) {
- break;
- }
- ClassInfo newCurrentClassInHierarchy = index.getClassByName(superClassDotName);
- if (newCurrentClassInHierarchy == null) {
- printWarningMessageForMissingJandexIndex(currentClassInHierarchy, superClassDotName);
- break;
- }
-
- currentClassInHierarchy = newCurrentClassInHierarchy;
- }
-
- // if we reach this point then the returning type is not compatible.
- return false;
- }
-
- /**
- * Generates a class that map a Pojo into {@link PartItem} that is then used by {@link MultipartMessageBodyWriter}.
- *
- *
- */
- static String generate(ClassInfo returnTypeClassInfo, ClassOutput classOutput, IndexView index) {
- String returnClassName = returnTypeClassInfo.name().toString();
- String generateClassName = MultipartMessageBodyWriter.getGeneratedMapperClassNameFor(returnClassName);
- String interfaceClassName = MultipartOutputInjectionTarget.class.getName();
- try (ClassCreator cc = new ClassCreator(classOutput, generateClassName, null, Object.class.getName(),
- interfaceClassName)) {
- MethodCreator populate = cc.getMethodCreator(TRANSFORM_METHOD_NAME, List.class.getName(),
- Object.class);
- populate.setModifiers(Modifier.PUBLIC);
-
- ResultHandle listPartItemListInstanceHandle = populate.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
- ResultHandle inputInstanceHandle = populate.checkCast(populate.getMethodParam(0), returnClassName);
-
- // go up the class hierarchy until we reach Object
- ClassInfo currentClassInHierarchy = returnTypeClassInfo;
- while (true) {
- List fields = currentClassInHierarchy.fields();
- for (FieldInfo field : fields) {
- if (Modifier.isStatic(field.flags())) { // nothing we need to do about static fields
- continue;
- }
-
- AnnotationInstance formParamInstance = field.annotation(REST_FORM_PARAM);
- if (formParamInstance == null) {
- formParamInstance = field.annotation(FORM_PARAM);
- }
- if (formParamInstance == null) { // fields not annotated with @RestForm or @FormParam are completely ignored
- continue;
- }
-
- boolean useFieldAccess = false;
- String getterName = JavaBeanUtil.getGetterName(field.name(), field.type().name());
- Type fieldType = field.type();
- DotName fieldDotName = fieldType.name();
- MethodInfo getter = currentClassInHierarchy.method(getterName);
- if (getter == null) {
- // even if the field is private, it will be transformed to be made public
- useFieldAccess = true;
- }
- if (!useFieldAccess && !Modifier.isPublic(getter.flags())) {
- throw new IllegalArgumentException(
- "Getter '" + getterName + "' of class '" + returnTypeClassInfo + "' must be public");
- }
-
- String formAttrName = field.name();
- AnnotationValue formParamValue = formParamInstance.value();
- if (formParamValue != null) {
- formAttrName = formParamValue.asString();
- }
-
- // TODO: not sure if this is correct, but it seems to be what RESTEasy does and it also makes most sense in the context of a POJO
- String partType = MediaType.TEXT_PLAIN;
- AnnotationInstance partTypeInstance = field.annotation(ResteasyReactiveDotNames.PART_TYPE_NAME);
- if (partTypeInstance != null) {
- AnnotationValue partTypeValue = partTypeInstance.value();
- if (partTypeValue != null) {
- partType = partTypeValue.asString();
- }
- }
-
- // Cast part type to MediaType.
- AssignableResultHandle partTypeHandle = populate.createVariable(MediaType.class);
- populate.assign(partTypeHandle,
- populate.invokeStaticMethod(
- MethodDescriptor.ofMethod(MediaType.class, "valueOf", MediaType.class, String.class),
- populate.load(partType)));
-
- // Continue with the value
- AssignableResultHandle resultHandle = populate.createVariable(Object.class);
-
- if (useFieldAccess) {
- populate.assign(resultHandle,
- populate.readInstanceField(
- FieldDescriptor.of(currentClassInHierarchy.name().toString(), field.name(),
- fieldDotName.toString()),
- inputInstanceHandle));
- } else {
- populate.assign(resultHandle,
- populate.invokeVirtualMethod(
- MethodDescriptor.ofMethod(currentClassInHierarchy.name().toString(),
- getterName, fieldDotName.toString()),
- inputInstanceHandle));
- }
-
- // Get parameterized type if field type is a parameterized class
- String firstParamType = "";
- if (fieldType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
- List argumentTypes = fieldType.asParameterizedType().arguments();
- if (argumentTypes.size() > 0) {
- firstParamType = argumentTypes.get(0).name().toString();
- }
- }
-
- // Create Part Item instance
- ResultHandle partItemInstanceHandle = populate.newInstance(
- MethodDescriptor.ofConstructor(PartItem.class,
- String.class, MediaType.class, Object.class, String.class),
- populate.load(formAttrName), partTypeHandle, resultHandle, populate.load(firstParamType));
-
- // Add it to the list
- populate.invokeVirtualMethod(
- MethodDescriptor.ofMethod(ArrayList.class, ARRAY_LIST_ADD_METHOD_NAME, boolean.class, Object.class),
- listPartItemListInstanceHandle, partItemInstanceHandle);
- }
-
- DotName superClassDotName = currentClassInHierarchy.superName();
- if (superClassDotName.equals(DotNames.OBJECT_NAME)) {
- break;
- }
- ClassInfo newCurrentClassInHierarchy = index.getClassByName(superClassDotName);
- if (newCurrentClassInHierarchy == null) {
- printWarningMessageForMissingJandexIndex(currentClassInHierarchy, superClassDotName);
- break;
- }
- currentClassInHierarchy = newCurrentClassInHierarchy;
- }
-
- populate.returnValue(listPartItemListInstanceHandle);
- }
- return generateClassName;
- }
-
- private static void printWarningMessageForMissingJandexIndex(ClassInfo currentClassInHierarchy, DotName superClassDotName) {
- if (!superClassDotName.toString().startsWith("java.")) {
- LOGGER.warn("Class '" + superClassDotName + "' which is a parent class of '"
- + currentClassInHierarchy.name()
- + "' is not part of the Jandex index so its fields will be ignored. If you intended to include these fields, consider making the dependency part of the Jandex index by following the advice at: https://quarkus.io/guides/cdi-reference#bean_discovery");
- }
- }
-}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusMultipartReturnTypeHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusMultipartReturnTypeHandler.java
index 6c3c095096f20..5c309395931ee 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusMultipartReturnTypeHandler.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusMultipartReturnTypeHandler.java
@@ -11,6 +11,7 @@
import org.jboss.resteasy.reactive.common.processor.AdditionalWriters;
import org.jboss.resteasy.reactive.common.processor.EndpointIndexer;
import org.jboss.resteasy.reactive.server.core.multipart.MultipartMessageBodyWriter;
+import org.jboss.resteasy.reactive.server.processor.generation.multipart.FormDataOutputMapperGenerator;
import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputResource.java
index 6ebcdeab6174c..f1ce3eb26dd5e 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputResource.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputResource.java
@@ -11,6 +11,7 @@
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.core.multipart.MultipartFormDataOutput;
@Path("/multipart/output")
public class MultipartOutputResource {
@@ -51,6 +52,20 @@ public RestResponse restResponse() {
return RestResponse.ResponseBuilder.ok(response).header("foo", "bar").build();
}
+ @GET
+ @Path("/with-form-data")
+ @Produces(MediaType.MULTIPART_FORM_DATA)
+ public RestResponse withFormDataOutput() {
+ MultipartFormDataOutput form = new MultipartFormDataOutput();
+ form.addFormData("name", RESPONSE_NAME, MediaType.TEXT_PLAIN_TYPE);
+ form.addFormData("custom-surname", RESPONSE_SURNAME, MediaType.TEXT_PLAIN_TYPE);
+ form.addFormData("custom-status", RESPONSE_STATUS, MediaType.TEXT_PLAIN_TYPE)
+ .getHeaders().putSingle("extra-header", "extra-value");
+ form.addFormData("values", RESPONSE_VALUES, MediaType.TEXT_PLAIN_TYPE);
+ form.addFormData("active", RESPONSE_ACTIVE, MediaType.TEXT_PLAIN_TYPE);
+ return RestResponse.ResponseBuilder.ok(form).header("foo", "bar").build();
+ }
+
@GET
@Path("/string")
@Produces(MediaType.MULTIPART_FORM_DATA)
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputUsingBlockingEndpointsTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputUsingBlockingEndpointsTest.java
index 5795599df878c..3e8bb225da17f 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputUsingBlockingEndpointsTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartOutputUsingBlockingEndpointsTest.java
@@ -63,6 +63,22 @@ public void testRestResponse() {
assertContainsValue(response, "num", MediaType.TEXT_PLAIN, "0");
}
+ @Test
+ public void testWithFormData() {
+ String response = RestAssured.get("/multipart/output/with-form-data")
+ .then()
+ .log().all()
+ .contentType(ContentType.MULTIPART)
+ .statusCode(200)
+ .extract().asString();
+
+ assertContainsValue(response, "name", MediaType.TEXT_PLAIN, MultipartOutputResource.RESPONSE_NAME);
+ assertContainsValue(response, "custom-surname", MediaType.TEXT_PLAIN, MultipartOutputResource.RESPONSE_SURNAME);
+ assertContainsValue(response, "custom-status", MediaType.TEXT_PLAIN, MultipartOutputResource.RESPONSE_STATUS);
+ assertContainsValue(response, "active", MediaType.TEXT_PLAIN, MultipartOutputResource.RESPONSE_ACTIVE);
+ assertContainsValue(response, "values", MediaType.TEXT_PLAIN, "[one, two]");
+ }
+
@Test
public void testString() {
RestAssured.get("/multipart/output/string")
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusContextProducers.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusContextProducers.java
index 07bbcb5aae75e..1f9b04bbbba7a 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusContextProducers.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusContextProducers.java
@@ -1,16 +1,19 @@
package io.quarkus.resteasy.reactive.server.runtime;
+import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;
+import javax.ws.rs.ext.Providers;
import org.jboss.resteasy.reactive.server.core.CurrentRequestManager;
+import org.jboss.resteasy.reactive.server.jaxrs.ProvidersImpl;
import io.vertx.core.http.HttpServerResponse;
/**
* Provides CDI producers for objects that can be injected via @Context
- * In quarkus-rest this works because @Context is considered an alias for @Inject
+ * In RESTEasy Reactive this works because @Context is considered an alias for @Inject
* through the use of {@code AutoInjectAnnotationBuildItem}
*/
@Singleton
@@ -21,4 +24,10 @@ public class QuarkusContextProducers {
HttpServerResponse httpServerResponse() {
return CurrentRequestManager.get().serverRequest().unwrap(HttpServerResponse.class);
}
+
+ @ApplicationScoped
+ @Produces
+ Providers providers() {
+ return new ProvidersImpl(ResteasyReactiveRecorder.getCurrentDeployment());
+ }
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java
new file mode 100644
index 0000000000000..47abd07a4f4c9
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanparam/BeanParamTest.java
@@ -0,0 +1,252 @@
+package io.quarkus.rest.client.reactive.beanparam;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URI;
+
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.jboss.resteasy.reactive.RestCookie;
+import org.jboss.resteasy.reactive.RestForm;
+import org.jboss.resteasy.reactive.RestHeader;
+import org.jboss.resteasy.reactive.RestPath;
+import org.jboss.resteasy.reactive.RestQuery;
+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 BeanParamTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest();
+
+ @TestHTTPResource
+ URI baseUri;
+
+ @Test
+ void shouldPassPathParamFromBeanParam() {
+ Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
+ assertThat(client.beanParamWithFields(new MyBeanParamWithFields()))
+ .isEqualTo("restPathDefault/restPathOverridden/pathParam"
+ + "/restHeaderDefault/restHeaderOverridden/headerParam"
+ + "/restFormDefault/restFormOverridden/formParam"
+ + "/restCookieDefault/restCookieOverridden/cookieParam"
+ + "/restQueryDefault/restQueryOverridden/queryParam");
+ assertThat(client.regularParameters(
+ "restPathDefault", "restPathOverridden", "pathParam",
+ "restHeaderDefault", "restHeaderOverridden", "headerParam",
+ "restCookieDefault", "restCookieOverridden", "cookieParam",
+ "restFormDefault", "restFormOverridden", "formParam",
+ "restQueryDefault", "restQueryOverridden", "queryParam"))
+ .isEqualTo("restPathDefault/restPathOverridden/pathParam"
+ + "/restHeaderDefault/restHeaderOverridden/headerParam"
+ + "/restFormDefault/restFormOverridden/formParam"
+ + "/restCookieDefault/restCookieOverridden/cookieParam"
+ + "/restQueryDefault/restQueryOverridden/queryParam");
+ assertThat(client.beanParamWithProperties(new MyBeanParamWithProperties())).isEqualTo("null/null/pathParam"
+ + "/null/null/headerParam"
+ + "/null/null/formParam"
+ + "/null/null/cookieParam"
+ + "/null/null/queryParam");
+ }
+
+ public interface Client {
+ @Path("/{restPathDefault}/{restPath_Overridden}/{pathParam}")
+ @POST
+ String beanParamWithFields(MyBeanParamWithFields beanParam);
+
+ @Path("/{restPathDefault}/{restPath_Overridden}/{pathParam}")
+ @POST
+ String regularParameters(@RestPath String restPathDefault,
+ @RestPath("restPath_Overridden") String restPathOverridden,
+ @PathParam("pathParam") String pathParam,
+
+ @RestHeader String restHeaderDefault,
+ @RestHeader("restHeader_Overridden") String restHeaderOverridden,
+ @HeaderParam("headerParam") String headerParam,
+
+ @RestCookie String restCookieDefault,
+ @RestCookie("restCookie_Overridden") String restCookieOverridden,
+ @CookieParam("cookieParam") String cookieParam,
+
+ @RestForm String restFormDefault,
+ @RestForm("restForm_Overridden") String restFormOverridden,
+ @FormParam("formParam") String formParam,
+
+ @RestQuery String restQueryDefault,
+ @RestQuery("restQuery_Overridden") String restQueryOverridden,
+ @QueryParam("queryParam") String queryParam);
+
+ @Path("/{pathParam}")
+ @POST
+ String beanParamWithProperties(MyBeanParamWithProperties beanParam);
+ }
+
+ public static class MyBeanParamWithFields {
+ @RestPath
+ private String restPathDefault = "restPathDefault";
+ @RestPath("restPath_Overridden")
+ private String restPathOverridden = "restPathOverridden";
+ @PathParam("pathParam")
+ private String pathParam = "pathParam";
+
+ @RestHeader
+ private String restHeaderDefault = "restHeaderDefault";
+ @RestHeader("restHeader_Overridden")
+ private String restHeaderOverridden = "restHeaderOverridden";
+ @HeaderParam("headerParam")
+ private String headerParam = "headerParam";
+
+ @RestCookie
+ private String restCookieDefault = "restCookieDefault";
+ @RestCookie("restCookie_Overridden")
+ private String restCookieOverridden = "restCookieOverridden";
+ @CookieParam("cookieParam")
+ private String cookieParam = "cookieParam";
+
+ @RestForm
+ private String restFormDefault = "restFormDefault";
+ @RestForm("restForm_Overridden")
+ private String restFormOverridden = "restFormOverridden";
+ @FormParam("formParam")
+ private String formParam = "formParam";
+
+ @RestQuery
+ private String restQueryDefault = "restQueryDefault";
+ @RestQuery("restQuery_Overridden")
+ private String restQueryOverridden = "restQueryOverridden";
+ @QueryParam("queryParam")
+ private String queryParam = "queryParam";
+
+ // FIXME: Matrix not supported
+ }
+
+ public static class MyBeanParamWithProperties {
+ // FIXME: not allowed yet
+ // @RestPath
+ // public String getRestPathDefault(){
+ // return "restPathDefault";
+ // }
+ // @RestPath("restPath_Overridden")
+ // public String getRestPathOverridden(){
+ // return "restPathOverridden";
+ // }
+ @PathParam("pathParam")
+ public String getPathParam() {
+ return "pathParam";
+ }
+
+ // @RestHeader
+ // public String getRestHeaderDefault(){
+ // return "restHeaderDefault";
+ // }
+ // @RestHeader("restHeader_Overridden")
+ // public String getRestHeaderOverridden(){
+ // return "restHeaderOverridden";
+ // }
+ @HeaderParam("headerParam")
+ public String getHeaderParam() {
+ return "headerParam";
+ }
+
+ // @RestCookie
+ // public String getRestCookieDefault(){
+ // return "restCookieDefault";
+ // }
+ // @RestCookie("restCookie_Overridden")
+ // public String getRestCookieOverridden(){
+ // return "restCookieOverridden";
+ // }
+ @CookieParam("cookieParam")
+ public String getCookieParam() {
+ return "cookieParam";
+ }
+
+ // @RestForm
+ // public String getRestFormDefault(){
+ // return "restFormDefault";
+ // }
+ // @RestForm("restForm_Overridden")
+ // public String getRestFormOverridden(){
+ // return "restFormOverridden";
+ // }
+ @FormParam("formParam")
+ public String getFormParam() {
+ return "formParam";
+ }
+
+ // @RestQuery
+ // public String getRestQueryDefault(){
+ // return "restQueryDefault";
+ // }
+ // @RestQuery("restQuery_Overridden")
+ // public String getRestQueryOverridden(){
+ // return "restQueryOverridden";
+ // }
+ @QueryParam("queryParam")
+ public String getQueryParam() {
+ return "queryParam";
+ }
+
+ // FIXME: Matrix not supported
+ }
+
+ @Path("/")
+ public static class Resource {
+ @Path("/{restPathDefault}/{restPath_Overridden}/{pathParam}")
+ @POST
+ public String beanParamWithFields(@RestPath String restPathDefault,
+ @RestPath String restPath_Overridden,
+ @RestPath String pathParam,
+ @RestHeader String restHeaderDefault,
+ @RestHeader("restHeader_Overridden") String restHeader_Overridden,
+ @RestHeader("headerParam") String headerParam,
+ @RestForm String restFormDefault,
+ @RestForm String restForm_Overridden,
+ @RestForm String formParam,
+ @RestCookie String restCookieDefault,
+ @RestCookie String restCookie_Overridden,
+ @RestCookie String cookieParam,
+ @RestQuery String restQueryDefault,
+ @RestQuery String restQuery_Overridden,
+ @RestQuery String queryParam) {
+ return restPathDefault + "/" + restPath_Overridden + "/" + pathParam
+ + "/" + restHeaderDefault + "/" + restHeader_Overridden + "/" + headerParam
+ + "/" + restFormDefault + "/" + restForm_Overridden + "/" + formParam
+ + "/" + restCookieDefault + "/" + restCookie_Overridden + "/" + cookieParam
+ + "/" + restQueryDefault + "/" + restQuery_Overridden + "/" + queryParam;
+ }
+
+ @Path("/{pathParam}")
+ @POST
+ public String beanParamWithProperties(@RestPath String restPathDefault,
+ @RestPath String restPath_Overridden,
+ @RestPath String pathParam,
+ @RestHeader String restHeaderDefault,
+ @RestHeader("restHeader_Overridden") String restHeader_Overridden,
+ @RestHeader("headerParam") String headerParam,
+ @RestForm String restFormDefault,
+ @RestForm String restForm_Overridden,
+ @RestForm String formParam,
+ @RestCookie String restCookieDefault,
+ @RestCookie String restCookie_Overridden,
+ @RestCookie String cookieParam,
+ @RestQuery String restQueryDefault,
+ @RestQuery String restQuery_Overridden,
+ @RestQuery String queryParam) {
+ return restPathDefault + "/" + restPath_Overridden + "/" + pathParam
+ + "/" + restHeaderDefault + "/" + restHeader_Overridden + "/" + headerParam
+ + "/" + restFormDefault + "/" + restForm_Overridden + "/" + formParam
+ + "/" + restCookieDefault + "/" + restCookie_Overridden + "/" + cookieParam
+ + "/" + restQueryDefault + "/" + restQuery_Overridden + "/" + queryParam;
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
index af444efd2556b..22d902c8e8406 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java
@@ -8,8 +8,11 @@
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.jboss.resteasy.reactive.RestHeader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -27,13 +30,14 @@ public class HeaderTest {
@Test
void testHeadersWithSubresource() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
- assertThat(client.cookieSub("bar", "bar2").send("bar3", "bar4")).isEqualTo("bar:bar2:bar3:bar4");
+ assertThat(client.cookieSub("bar", "bar2").send("bar3", "bar4", "dummy"))
+ .isEqualTo("bar:bar2:bar3:bar4:X-My-Header/dummy");
}
@Test
void testNullHeaders() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
- assertThat(client.cookieSub("bar", null).send(null, "bar4")).isEqualTo("bar:null:null:bar4");
+ assertThat(client.cookieSub("bar", null).send(null, "bar4", "dummy")).isEqualTo("bar:null:null:bar4:X-My-Header/dummy");
}
@Path("/")
@@ -41,8 +45,11 @@ void testNullHeaders() {
public static class Resource {
@GET
public String returnHeaders(@HeaderParam("foo") String header, @HeaderParam("foo2") String header2,
- @HeaderParam("foo3") String header3, @HeaderParam("foo4") String header4) {
- return header + ":" + header2 + ":" + header3 + ":" + header4;
+ @HeaderParam("foo3") String header3, @HeaderParam("foo4") String header4, @Context HttpHeaders headers) {
+ String myHeaderName = headers.getRequestHeaders().keySet().stream().filter(s -> s.equalsIgnoreCase("X-My-Header"))
+ .findFirst().orElse("");
+ return header + ":" + header2 + ":" + header3 + ":" + header4 + ":" + myHeaderName + "/"
+ + headers.getHeaderString("X-My-Header");
}
}
@@ -55,7 +62,7 @@ public interface Client {
public interface SubClient {
@GET
- String send(@HeaderParam("foo3") String cookie3, @HeaderParam("foo4") String cookie4);
+ String send(@HeaderParam("foo3") String cookie3, @HeaderParam("foo4") String cookie4, @RestHeader String xMyHeader);
}
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/ComputedHeader.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/ComputedHeader.java
new file mode 100644
index 0000000000000..3a796b6a0988d
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/ComputedHeader.java
@@ -0,0 +1,9 @@
+package io.quarkus.rest.client.reactive.registerclientheaders;
+
+public final class ComputedHeader {
+
+ public static String get() {
+ return "From " + ComputedHeader.class.getName();
+ }
+
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java
new file mode 100644
index 0000000000000..7a4e8012169dd
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java
@@ -0,0 +1,20 @@
+package io.quarkus.rest.client.reactive.registerclientheaders;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.Path;
+
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient
+@RegisterClientHeaders(MyHeadersFactory.class)
+@ClientHeaderParam(name = "my-header", value = "constant-header-value")
+@ClientHeaderParam(name = "computed-header", value = "{io.quarkus.rest.client.reactive.registerclientheaders.ComputedHeader.get}")
+public interface MultipleHeadersBindingClient {
+ @GET
+ @Path("/describe-request")
+ @ClientHeaderParam(name = "header-from-properties", value = "${header.value}")
+ RequestData call(@HeaderParam("jaxrs-style-header") String headerValue);
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MyHeadersFactory.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MyHeadersFactory.java
index 8572362e62d2e..79068e1a0695f 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MyHeadersFactory.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MyHeadersFactory.java
@@ -19,8 +19,8 @@ public class MyHeadersFactory implements ClientHeadersFactory {
public MultivaluedMap update(MultivaluedMap incomingHeaders,
MultivaluedMap clientOutgoingHeaders) {
assertNotNull(beanManager);
- incomingHeaders.add("foo", "bar");
- return incomingHeaders;
+ clientOutgoingHeaders.add("foo", "bar");
+ return clientOutgoingHeaders;
}
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java
index 670e5213d8f3c..7d9488644b613 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java
@@ -1,9 +1,13 @@
package io.quarkus.rest.client.reactive.registerclientheaders;
import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass;
+import static io.quarkus.rest.client.reactive.registerclientheaders.HeaderSettingClient.HEADER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.List;
+import java.util.Map;
+
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
@@ -26,8 +30,9 @@ public class RegisterClientHeadersTest {
setUrlForClass(HeaderSettingClient.class) +
setUrlForClass(HeaderPassingClient.class) +
setUrlForClass(HeaderNoPassingClient.class) +
- "org.eclipse.microprofile.rest.client.propagateHeaders="
- + HeaderSettingClient.HEADER),
+ setUrlForClass(MultipleHeadersBindingClient.class) +
+ "org.eclipse.microprofile.rest.client.propagateHeaders=" + HEADER + "\n" +
+ "header.value=from property file"),
"application.properties");
});
@@ -37,6 +42,9 @@ public class RegisterClientHeadersTest {
@RestClient
HeaderSettingClient headerSettingClient;
+ @RestClient
+ MultipleHeadersBindingClient multipleHeadersBindingClient;
+
@Test
public void shouldUseHeadersFactory() {
assertEquals("ping:bar", client.echo("ping:"));
@@ -47,14 +55,30 @@ public void shouldUseHeadersFactory() {
public void shouldPassIncomingHeaders() {
String headerValue = "my-header-value";
RequestData requestData = headerSettingClient.setHeaderValue(headerValue);
- assertThat(requestData.getHeaders().get(HeaderSettingClient.HEADER).get(0)).isEqualTo(headerValue);
+ assertThat(requestData.getHeaders().get(HEADER).get(0)).isEqualTo(headerValue);
}
@Test
public void shouldNotPassIncomingHeaders() {
String headerValue = "my-header-value";
RequestData requestData = headerSettingClient.setHeaderValueNoPassing(headerValue);
- assertThat(requestData.getHeaders().get(HeaderSettingClient.HEADER)).isNull();
+ assertThat(requestData.getHeaders().get(HEADER)).isNull();
+ }
+
+ @Test
+ public void shouldSetHeadersFromMultipleBindings() {
+ String headerValue = "my-header-value";
+ Map> headers = multipleHeadersBindingClient.call(headerValue).getHeaders();
+ // Verify: @RegisterClientHeaders(MyHeadersFactory.class)
+ assertThat(headers.get("foo")).containsExactly("bar");
+ // Verify: @ClientHeaderParam(name = "my-header", value = "constant-header-value")
+ assertThat(headers.get("my-header")).containsExactly("constant-header-value");
+ // Verify: @ClientHeaderParam(name = "computed-header", value = "{...ComputedHeader.get}")
+ assertThat(headers.get("computed-header")).containsExactly("From " + ComputedHeader.class.getName());
+ // Verify: @ClientHeaderParam(name = "header-from-properties", value = "${header.value}")
+ assertThat(headers.get("header-from-properties")).containsExactly("from property file");
+ // Verify: @HeaderParam("jaxrs-style-header")
+ assertThat(headers.get("jaxrs-style-header")).containsExactly(headerValue);
}
}
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 3a6c26f8405ed..89982a4c8caaa 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
@@ -10,7 +10,6 @@
import javax.ws.rs.core.MultivaluedMap;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
-import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter;
@@ -55,12 +54,9 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
}
}
- if (clientHeadersFactory instanceof DefaultClientHeadersFactoryImpl) {
- // When using the default factory, pass the proposed outgoing headers onto the request context.
- // Propagation with the default factory will then overwrite any values if required.
- for (Map.Entry> headerEntry : headers.entrySet()) {
- requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue()));
- }
+ // Propagation with the default factory will then overwrite any values if required.
+ for (Map.Entry> headerEntry : headers.entrySet()) {
+ requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue()));
}
if (clientHeadersFactory != null) {
@@ -82,10 +78,6 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue()));
}
}
- } else {
- for (Map.Entry> headerEntry : headers.entrySet()) {
- requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue()));
- }
}
}
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 422f5317a2c55..60ca6948c5742 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
@@ -795,13 +795,17 @@ private static void fetchType(Type type, BeanDeployment beanDeployment) {
private static void collectCallbacks(ClassInfo clazz, List callbacks, DotName annotation, IndexView index,
Set knownMethods) {
for (MethodInfo method : clazz.methods()) {
- if (method.returnType().kind() == Kind.VOID && method.parameterTypes().isEmpty()) {
- if (method.hasAnnotation(annotation) && !knownMethods.contains(method.name())) {
+ if (method.hasAnnotation(annotation) && !knownMethods.contains(method.name())) {
+ if (method.returnType().kind() == Kind.VOID && method.parameterTypes().isEmpty()) {
callbacks.add(method);
+ } else {
+ // invalid signature - build a meaningful message.
+ throw new DefinitionException("Invalid signature for the method `" + method + "` from class `"
+ + method.declaringClass() + "`. Methods annotated with `" + annotation + "` must return" +
+ " `void` and cannot have parameters.");
}
- knownMethods.add(method.name());
}
-
+ knownMethods.add(method.name());
}
if (clazz.superName() != null) {
ClassInfo superClass = getClassByName(index, clazz.superName());
@@ -866,7 +870,7 @@ private static Integer initAlternativePriority(AnnotationTarget target, Integer
if (computedPriority != null) {
if (alternativePriority != null) {
LOGGER.infof(
- "Computed priority [%s] overrides the priority [%s] declared via @Priority or @AlernativePriority",
+ "Computed priority [%s] overrides the priority [%s] declared via @Priority or @AlternativePriority",
computedPriority, alternativePriority);
}
alternativePriority = computedPriority;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/lifecycle/inheritance/BeanLifecycleMethodsOverridenTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/lifecycle/inheritance/BeanLifecycleMethodsOverridenTest.java
index c549693afa73e..9bdbdd4283700 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/lifecycle/inheritance/BeanLifecycleMethodsOverridenTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/bean/lifecycle/inheritance/BeanLifecycleMethodsOverridenTest.java
@@ -19,7 +19,7 @@ public class BeanLifecycleMethodsOverridenTest {
ArcTestContainer container = new ArcTestContainer(Bird.class, Eagle.class, Falcon.class);
@Test
- public void testOverridenMethodWithNoAnnotation() {
+ public void testOverriddenMethodWithNoAnnotation() {
resetAll();
InstanceHandle falconInstanceHandle = Arc.container().instance(Falcon.class);
falconInstanceHandle.get().ping();
@@ -31,7 +31,7 @@ public void testOverridenMethodWithNoAnnotation() {
}
@Test
- public void testOverridenMethodWithLifecycleAnnotation() {
+ public void testOverriddenMethodWithLifecycleAnnotation() {
resetAll();
InstanceHandle eagleInstanceHandle = Arc.container().instance(Eagle.class);
eagleInstanceHandle.get().ping();
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPostConstructTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPostConstructTest.java
new file mode 100644
index 0000000000000..0994fc10c014d
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPostConstructTest.java
@@ -0,0 +1,38 @@
+package io.quarkus.arc.test.validation;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Unremovable;
+import io.quarkus.arc.test.ArcTestContainer;
+import io.smallrye.mutiny.Uni;
+
+public class InvalidPostConstructTest {
+
+ @RegisterExtension
+ public ArcTestContainer container = ArcTestContainer.builder().beanClasses(InvalidBean.class).shouldFail().build();
+
+ @Test
+ public void testFailure() {
+ Throwable error = container.getFailure();
+ assertNotNull(error);
+ assertTrue(error instanceof DefinitionException);
+ }
+
+ @ApplicationScoped
+ @Unremovable
+ public static class InvalidBean {
+
+ @PostConstruct
+ public Uni invalid() {
+ return Uni.createFrom().nullItem();
+ }
+ }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPostConstructWithParametersTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPostConstructWithParametersTest.java
new file mode 100644
index 0000000000000..cab640a82436a
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPostConstructWithParametersTest.java
@@ -0,0 +1,41 @@
+package io.quarkus.arc.test.validation;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Unremovable;
+import io.quarkus.arc.test.ArcTestContainer;
+
+public class InvalidPostConstructWithParametersTest {
+
+ @RegisterExtension
+ public ArcTestContainer container = ArcTestContainer.builder().beanClasses(InvalidBean.class).shouldFail().build();
+
+ @Test
+ public void testFailure() {
+ Throwable error = container.getFailure();
+ assertNotNull(error);
+ assertTrue(error instanceof DefinitionException);
+ Assertions.assertTrue(error.getMessage().contains("invalid(java.lang.String ignored)"));
+ Assertions.assertTrue(error.getMessage().contains("$InvalidBean"));
+ Assertions.assertTrue(error.getMessage().contains("PostConstruct"));
+ }
+
+ @ApplicationScoped
+ @Unremovable
+ public static class InvalidBean {
+
+ @PostConstruct
+ public void invalid(String ignored) {
+
+ }
+ }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPreDestroyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPreDestroyTest.java
new file mode 100644
index 0000000000000..26e2b7a46fcb0
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/InvalidPreDestroyTest.java
@@ -0,0 +1,42 @@
+package io.quarkus.arc.test.validation;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.DefinitionException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Unremovable;
+import io.quarkus.arc.test.ArcTestContainer;
+import io.smallrye.mutiny.Multi;
+
+public class InvalidPreDestroyTest {
+
+ @RegisterExtension
+ public ArcTestContainer container = ArcTestContainer.builder().beanClasses(InvalidBean.class).shouldFail().build();
+
+ @Test
+ public void testFailure() {
+ Throwable error = container.getFailure();
+ assertNotNull(error);
+ assertTrue(error instanceof DefinitionException);
+ Assertions.assertTrue(error.getMessage().contains("invalid()"));
+ Assertions.assertTrue(error.getMessage().contains("$InvalidBean"));
+ Assertions.assertTrue(error.getMessage().contains("PreDestroy"));
+ }
+
+ @ApplicationScoped
+ @Unremovable
+ public static class InvalidBean {
+
+ @PreDestroy
+ public Multi invalid() {
+ return null;
+ }
+ }
+}
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
index a9ae080e0da6c..82b30beec69df 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java
@@ -20,37 +20,131 @@ static PathTree ofDirectoryOrFile(Path p) {
}
}
+ /**
+ * Creates a new {@link PathTree} for a given existing path which is expected to be
+ * either a directory or a ZIP-based archive.
+ *
+ * @param p path to a directory or an archive
+ * @return an instance of {@link PathTree} for a given existing directory or an archive
+ */
static PathTree ofDirectoryOrArchive(Path p) {
+ return ofDirectoryOrArchive(p, null);
+ }
+
+ /**
+ * Creates a new {@link PathTree} for a given existing path which is expected to be
+ * either a directory or a ZIP-based archive applying a provided {@link PathFilter}
+ * unless it is {@code null}.
+ *
+ * @param p path to a directory or an archive
+ * @param filter path filter to apply, could be {@code null}
+ * @return an instance of {@link PathTree} for a given existing directory or an archive
+ */
+ static PathTree ofDirectoryOrArchive(Path p, PathFilter filter) {
try {
final BasicFileAttributes fileAttributes = Files.readAttributes(p, BasicFileAttributes.class);
- return fileAttributes.isDirectory() ? new DirectoryPathTree(p) : new ArchivePathTree(p);
+ return fileAttributes.isDirectory() ? new DirectoryPathTree(p, filter) : new ArchivePathTree(p, filter);
} catch (IOException e) {
throw new IllegalArgumentException(p + " does not exist", e);
}
}
+ /**
+ * Creates a new {@link PathTree} for an existing path that is expected to be
+ * a ZIP-based archive.
+ *
+ * @param archive path to an archive
+ * @return an instance of {@link PathTree} for a given archive
+ */
static PathTree ofArchive(Path archive) {
+ return ofArchive(archive, null);
+ }
+
+ /**
+ * Creates a new {@link PathTree} for an existing path that is expected to be
+ * a ZIP-based archive applying a provided {@link PathFilter} unless it is {@code null}.
+ *
+ * @param archive path to an archive
+ * @param filter path filter to apply, could be {@code null}
+ * @return an instance of {@link PathTree} for a given archive
+ */
+ static PathTree ofArchive(Path archive, PathFilter filter) {
if (!Files.exists(archive)) {
throw new IllegalArgumentException(archive + " does not exist");
}
- return new ArchivePathTree(archive);
+ return new ArchivePathTree(archive, filter);
}
+ /**
+ * The roots of the path tree.
+ *
+ * @return roots of the path tree
+ */
Collection getRoots();
+ /**
+ * Checks whether the tree is empty
+ *
+ * @return true, if the tree is empty, otherwise - false
+ */
default boolean isEmpty() {
return getRoots().isEmpty();
}
+ /**
+ * If {@code META-INF/MANIFEST.MF} found, reads it and returns an instance of {@link java.util.jar.Manifest},
+ * otherwise returns null.
+ *
+ * @return parsed {@code META-INF/MANIFEST.MF} if it's found, otherwise {@code null}
+ */
Manifest getManifest();
+ /**
+ * Walks the tree.
+ *
+ * @param visitor path visitor
+ */
void walk(PathVisitor visitor);
+ /**
+ * Applies a function to a given path relative to the root of the tree.
+ * If the path isn't found in the tree, the {@link PathVisit} argument
+ * passed to the function will be {@code null}.
+ *
+ * @param resulting type
+ * @param relativePath relative path to process
+ * @param func processing function
+ * @return result of the function
+ */
T apply(String relativePath, Function func);
+ /**
+ * Consumes a given path relative to the root of the tree.
+ * If the path isn't found in the tree, the {@link PathVisit} argument
+ * passed to the consumer will be {@code null}.
+ *
+ * @param relativePath relative path to consume
+ * @param consumer path consumer
+ */
void accept(String relativePath, Consumer consumer);
+ /**
+ * Checks whether the tree contains a relative path.
+ *
+ * @param relativePath path relative to the root of the tree
+ * @return true, in case the tree contains the path, otherwise - false
+ */
boolean contains(String relativePath);
+ /**
+ * Returns an {@link OpenPathTree} for this tree, which is supposed to be
+ * closed at the end of processing. It is meant to be an optimization when
+ * processing multiple paths of path trees that represent archives.
+ * If a path tree does not represent an archive but a directory, for example,
+ * this method is expected to be a no-op, returning the original tree as an
+ * instance of {@link OpenPathTree}.
+ *
+ * @return an instance of {@link OpenPathTree} for this path tree
+ */
OpenPathTree open();
}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
index 5b182c0b2f654..cba6b34e0c08b 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java
@@ -13,6 +13,7 @@
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
@@ -182,6 +183,26 @@ public void close() throws IOException {
}
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getName()).append("[");
+ if (getDependencyKey() != null) {
+ sb.append(getDependencyKey().toGacString()).append(" ");
+ }
+ final Iterator i = pathTree.getRoots().iterator();
+ if (i.hasNext()) {
+ sb.append(i.next());
+ while (i.hasNext()) {
+ sb.append(",").append(i.next());
+ }
+ }
+ sb.append(" runtime=").append(isRuntime());
+ final Set resources = this.resources;
+ sb.append(" resources=").append(resources == null ? "null" : resources.size());
+ return sb.append(']').toString();
+ }
+
private class Resource implements ClassPathResource {
private final String name;
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 d269b949db945..75321822a384e 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
@@ -361,7 +361,12 @@ public URL getResource(String unsanitisedName) {
if (name.endsWith(".class") && !endsWithTrailingSlash) {
ClassPathElement[] providers = state.loadableResources.get(name);
if (providers != null) {
- return providers[0].getResource(name).getUrl();
+ final ClassPathResource resource = providers[0].getResource(name);
+ if (resource == null) {
+ throw new IllegalStateException(providers[0] + " from " + getName() + " (closed=" + this.isClosed()
+ + ") was expected to provide " + name + " but failed");
+ }
+ return resource.getUrl();
}
} else {
for (ClassPathElement i : elements) {
diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml
index 2b3a8f72bece4..a5515274a6908 100644
--- a/independent-projects/bootstrap/pom.xml
+++ b/independent-projects/bootstrap/pom.xml
@@ -64,7 +64,7 @@
1.7.3622.2.02.6.0
- 1.13.1
+ 1.13.27.5.10.0.90.1.1
diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/CheckedFragment.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/CheckedFragment.java
deleted file mode 100644
index 6469c65f5d249..0000000000000
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/CheckedFragment.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.quarkus.qute;
-
-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;
-
-/**
- * Denotes a method that represents a fragment of a type-safe template.
- *
- * The name of the fragment is derived from the annotated method name. The part before the last occurence of a dollar sign
- * {@code $} is
- * the method name of the related type-safe template. The part after the last occurence of a dollar sign is the fragment
- * identifier - the strategy defined by the relevant {@link CheckedTemplate#defaultName()} is used.
- *
- * Parameters of the annotated method are validated. The required names and types are derived from the relevant fragment
- * template.
- *
- *
- * @CheckedTemplate
- * class Templates {
- *
- * // defines a type-safe template
- * static native TemplateInstance items(List<Item> items);
- *
- * // defines a fragment of Templates#items() with identifier "item"
- * @CheckedFragment
- * static native TemplateInstance items$item(Item item);
- * }
- *
+ *
+ * By default, a native static method with the name that contains a dollar sign {@code $} denotes a method that
+ * represents a fragment of a type-safe template. It's possible to ignore the fragments and effectively disable this
+ * feature via {@link CheckedTemplate#ignoreFragments()}.
+ *
+ * The name of the fragment is derived from the annotated method name. The part before the last occurence of a dollar sign
+ * {@code $} is the method name of the related type-safe template. The part after the last occurence of a dollar sign is the
+ * fragment identifier - the strategy defined by the relevant {@link CheckedTemplate#defaultName()} is used.
+ *
+ * Parameters of the annotated method are validated. The required names and types are derived from the relevant fragment
+ * template.
+ *
+ *
+ * @CheckedTemplate
+ * class Templates {
+ *
+ * // defines a type-safe template
+ * static native TemplateInstance items(List<Item> items);
+ *
+ * // defines a fragment of Templates#items() with identifier "item"
+ * @CheckedFragment
+ * static native TemplateInstance items$item(Item item);
+ * }
+ *
+ *
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@@ -113,4 +140,14 @@
*/
String defaultName() default ELEMENT_NAME;
+ /**
+ * By default, a native static method with the name that contains a dollar sign {@code $} denotes a method that
+ * represents a fragment of a type-safe template. It's possible to ignore the fragments and effectively disable this
+ * feature.
+ *
+ * @return {@code true} if no method should be interpreted as a fragment, {@code false} otherwise
+ * @see Template#getFragment(String)
+ */
+ boolean ignoreFragments() default false;
+
}
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 a1ca039c882da..a4438a030fae9 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
@@ -113,7 +113,11 @@ protected boolean ignoreParameterInit(String key, String value) {
// {#include foo _isolated=true /}
|| key.equals(ISOLATED)
// {#include foo _isolated /}
- || value.equals(ISOLATED);
+ || value.equals(ISOLATED)
+ // {#include foo _ignoreFragments=true /}
+ || key.equals(IGNORE_FRAGMENTS)
+ // {#include foo _ignoreFragments /}
+ || value.equals(IGNORE_FRAGMENTS);
}
@Override
@@ -136,6 +140,7 @@ protected IncludeSectionHelper newHelper(Supplier template, Map implements SectionHelperFactory {
static final String ISOLATED = "_isolated";
+ static final String IGNORE_FRAGMENTS = "_ignoreFragments";
static final String ISOLATED_DEFAULT_VALUE = "false";
@Override
@@ -153,6 +158,14 @@ public boolean test(String v) {
return ISOLATED.equals(v);
}
}).build())
+ .addParameter(Parameter.builder(IGNORE_FRAGMENTS).defaultValue(ISOLATED_DEFAULT_VALUE).optional()
+ .valuePredicate(new Predicate() {
+
+ @Override
+ public boolean test(String v) {
+ return IGNORE_FRAGMENTS.equals(v);
+ }
+ }).build())
.build();
}
@@ -180,6 +193,7 @@ public Scope initializeBlock(Scope outerScope, BlockInfo block) {
public T initialize(SectionInitContext context) {
boolean isEmpty = context.getBlocks().size() == 1 && context.getBlocks().get(0).isEmpty();
boolean isolatedValue = false;
+ boolean ignoreFragments = false;
Map extendingBlocks;
if (isEmpty) {
@@ -210,19 +224,28 @@ public T initialize(SectionInitContext context) {
isolatedValue = true;
continue;
}
+ if (value.equals(IGNORE_FRAGMENTS)) {
+ ignoreFragments = true;
+ continue;
+ }
handleParamInit(key, value, context, params);
}
}
// foo - no fragment
- // foo[bar] - template "foo" and fragment "bar"
- // [fragment_01] - current template and fragment "fragment_01"
+ // foo$bar - template "foo" and fragment "bar"
+ // $fragment_01 - current template and fragment "fragment_01"
String templateId = getTemplateId(context);
- final String fragmentId = getFragmentId(templateId, context);
+
+ //
+ if (!ignoreFragments) {
+ ignoreFragments = Boolean.parseBoolean(context.getParameterOrDefault(IGNORE_FRAGMENTS, ISOLATED_DEFAULT_VALUE));
+ }
+ final String fragmentId = ignoreFragments ? null : getFragmentId(templateId, context);
Supplier currentTemplate;
if (fragmentId != null) {
// remove the trailing fragment part
- templateId = templateId.substring(0, templateId.lastIndexOf('['));
+ templateId = templateId.substring(0, templateId.lastIndexOf('$'));
if (templateId.isEmpty()) {
// use the current template
currentTemplate = context.getCurrentTemplate();
@@ -274,20 +297,18 @@ public Template get() {
protected abstract String getTemplateId(SectionInitContext context);
protected String getFragmentId(String templateId, SectionInitContext context) {
- if (templateId.endsWith("]")) {
- // the fragment id can be found in the square brackets at the end of the template id
- int lastLeftSquareBracket = templateId.lastIndexOf('[');
- if (lastLeftSquareBracket != -1) {
- String fragmentId = templateId.substring(lastLeftSquareBracket + 1, templateId.length() - 1);
- if (FragmentSectionHelper.Factory.FRAGMENT_PATTERN.matcher(fragmentId).matches()) {
- return fragmentId;
- } else {
- throw context.getEngine().error("invalid fragment identifier [{fragmentId}]")
- .code(Code.INVALID_FRAGMENT_ID)
- .argument("fragmentId", fragmentId)
- .origin(context.getOrigin())
- .build();
- }
+ int idx = templateId.lastIndexOf('$');
+ if (idx != -1) {
+ // the part after the last occurence of a dollar sign is the fragment identifier
+ String fragmentId = templateId.substring(idx + 1, templateId.length());
+ if (FragmentSectionHelper.Factory.FRAGMENT_PATTERN.matcher(fragmentId).matches()) {
+ return fragmentId;
+ } else {
+ throw context.getEngine().error("invalid fragment identifier [{fragmentId}]")
+ .code(Code.INVALID_FRAGMENT_ID)
+ .argument("fragmentId", fragmentId)
+ .origin(context.getOrigin())
+ .build();
}
}
return null;
diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java
index 832929bf1bdd5..29b96f7b62b8e 100644
--- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java
+++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java
@@ -48,7 +48,7 @@ public void testNonUniqueIds() {
public void testInvisibleFragment() {
Engine engine = Engine.builder().addDefaults().build();
Template foo = engine.parse(
- "PREFIX::{#fragment foo rendered=false}FOO{/fragment}::{#include [foo] /}::{#include [foo] /}", null, "foo");
+ "PREFIX::{#fragment foo rendered=false}FOO{/fragment}::{#include $foo /}::{#include $foo /}", null, "foo");
assertEquals("PREFIX::::FOO::FOO", foo.render());
assertEquals("FOO", foo.getFragment("foo").render());
}
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 277c0d8db5dbc..1848e70789c8a 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
@@ -191,24 +191,33 @@ public void testFragment() {
Engine engine = Engine.builder().addDefaults().build();
engine.putTemplate("foo", engine.parse("---{#fragment bar}{val}{/fragment}---"));
Template baz = engine.parse(
- "{#fragment nested}NESTED{/fragment} {#include foo[bar] val=1 /} {#include baz[nested] /}");
+ "{#fragment nested}NESTED{/fragment} {#include foo$bar val=1 /} {#include baz$nested /}");
engine.putTemplate("baz", baz);
assertEquals("NESTED 1 NESTED", baz.render());
}
+ @Test
+ public void testIgnoreFragments() {
+ Engine engine = Engine.builder().addDefaults().build();
+ engine.putTemplate("foo$bar", engine.parse("{val}"));
+ Template baz = engine.parse("{#include foo$bar val=1 _ignoreFragments=true /}");
+ engine.putTemplate("baz", baz);
+ assertEquals("1", baz.render());
+ }
+
@Test
public void testInvalidFragment() {
Engine engine = Engine.builder().addDefaults().build();
engine.putTemplate("foo", engine.parse("foo"));
TemplateException expected = assertThrows(TemplateException.class,
- () -> engine.parse("{#include foo[foo_and_bar] /}", null, "bum.html").render());
+ () -> engine.parse("{#include foo$foo_and_bar /}", null, "bum.html").render());
assertEquals(IncludeSectionHelper.Code.FRAGMENT_NOT_FOUND, expected.getCode());
assertEquals(
"Rendering error in template [bum.html] line 1: fragment [foo_and_bar] not found in the included template [foo]",
expected.getMessage());
expected = assertThrows(TemplateException.class,
- () -> engine.parse("{#include foo[foo-and_bar] /}", null, "bum.html").render());
+ () -> engine.parse("{#include foo$foo-and_bar /}", null, "bum.html").render());
assertEquals(IncludeSectionHelper.Code.INVALID_FRAGMENT_ID, expected.getCode());
assertEquals(
"Rendering error in template [bum.html] line 1: invalid fragment identifier [foo-and_bar]",
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 965884288136f..1325f3ad1f793 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
@@ -6,6 +6,11 @@
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEADER_PARAM;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH_PARAM;
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.QUERY_PARAM;
+import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_COOKIE_PARAM;
+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_PATH_PARAM;
+import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_QUERY_PARAM;
import java.util.ArrayList;
import java.util.Collections;
@@ -27,7 +32,9 @@
import org.jboss.jandex.Type;
import org.jboss.resteasy.reactive.common.processor.AsmUtil;
import org.jboss.resteasy.reactive.common.processor.JandexUtil;
+import org.jboss.resteasy.reactive.common.processor.JavaBeanUtil;
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
+import org.jboss.resteasy.reactive.common.processor.StringUtil;
public class BeanParamParser {
@@ -60,6 +67,16 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde
(annotationValue, getterMethod) -> new QueryParamItem(annotationValue, new GetterExtractor(getterMethod),
getterMethod.returnType())));
+ resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_QUERY_PARAM,
+ (annotationValue, fieldInfo) -> new QueryParamItem(
+ annotationValue != null ? annotationValue : fieldInfo.name(),
+ new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()),
+ fieldInfo.type()),
+ (annotationValue, getterMethod) -> new QueryParamItem(
+ annotationValue != null ? annotationValue : getterName(getterMethod),
+ new GetterExtractor(getterMethod),
+ getterMethod.returnType())));
+
resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, BEAN_PARAM,
(annotationValue, fieldInfo) -> {
Type type = fieldInfo.type();
@@ -89,6 +106,16 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde
(annotationValue, getterMethod) -> new CookieParamItem(annotationValue,
new GetterExtractor(getterMethod), getterMethod.returnType().name().toString())));
+ resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_COOKIE_PARAM,
+ (annotationValue, fieldInfo) -> new CookieParamItem(
+ annotationValue != null ? annotationValue : fieldInfo.name(),
+ new FieldExtractor(null, fieldInfo.name(),
+ fieldInfo.declaringClass().name().toString()),
+ fieldInfo.type().name().toString()),
+ (annotationValue, getterMethod) -> new CookieParamItem(
+ annotationValue != null ? annotationValue : getterName(getterMethod),
+ new GetterExtractor(getterMethod), getterMethod.returnType().name().toString())));
+
resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, HEADER_PARAM,
(annotationValue, fieldInfo) -> new HeaderParamItem(annotationValue,
new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()),
@@ -96,6 +123,18 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde
(annotationValue, getterMethod) -> new HeaderParamItem(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 != null ? annotationValue
+ : StringUtil.hyphenateWithCapitalFirstLetter(fieldInfo.name()),
+ new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()),
+ fieldInfo.type().name().toString()),
+ (annotationValue, getterMethod) -> new HeaderParamItem(
+ 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(),
new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())),
@@ -103,6 +142,15 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde
getterMethod.returnType().name().toString(),
new GetterExtractor(getterMethod))));
+ resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_PATH_PARAM,
+ (annotationValue, fieldInfo) -> new PathParamItem(
+ annotationValue != null ? annotationValue : fieldInfo.name(), fieldInfo.type().name().toString(),
+ new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())),
+ (annotationValue, getterMethod) -> new PathParamItem(
+ annotationValue != null ? annotationValue : getterName(getterMethod),
+ getterMethod.returnType().name().toString(),
+ new GetterExtractor(getterMethod))));
+
resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, FORM_PARAM,
(annotationValue, fieldInfo) -> new FormParamItem(annotationValue,
fieldInfo.type().name().toString(), AsmUtil.getSignature(fieldInfo.type(), arg -> arg),
@@ -116,6 +164,21 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde
partType(getterMethod), fileName(getterMethod),
new GetterExtractor(getterMethod))));
+ resultList.addAll(paramItemsForFieldsAndMethods(beanParamClass, REST_FORM_PARAM,
+ (annotationValue, fieldInfo) -> new FormParamItem(
+ annotationValue != null ? annotationValue : fieldInfo.name(),
+ fieldInfo.type().name().toString(), AsmUtil.getSignature(fieldInfo.type(), arg -> arg),
+ fieldInfo.name(),
+ partType(fieldInfo), fileName(fieldInfo),
+ new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString())),
+ (annotationValue, getterMethod) -> new FormParamItem(
+ annotationValue != null ? annotationValue : getterName(getterMethod),
+ getterMethod.returnType().name().toString(),
+ AsmUtil.getSignature(getterMethod.returnType(), arg -> arg),
+ getterMethod.name(),
+ partType(getterMethod), fileName(getterMethod),
+ new GetterExtractor(getterMethod))));
+
return resultList;
} finally {
@@ -123,6 +186,10 @@ private static List parseInternal(ClassInfo beanParamClass, IndexView inde
}
}
+ private static String getterName(MethodInfo getterMethod) {
+ return JavaBeanUtil.getPropertyNameFromGetter(getterMethod.name());
+ }
+
private static String partType(FieldInfo annotated) {
return partType(annotated.annotation(ResteasyReactiveDotNames.PART_TYPE_NAME));
}
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 eb0d2f2ad0807..60c3f3f529729 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
@@ -1181,7 +1181,7 @@ public PARAM extractParameterInfo(ClassInfo currentClassInfo, ClassInfo actualEn
convertible = true;
} else if (restHeaderParam != null) {
if (restHeaderParam.value() == null || restHeaderParam.value().asString().isEmpty()) {
- builder.setName(StringUtil.hyphenate(sourceName));
+ builder.setName(StringUtil.hyphenateWithCapitalFirstLetter(sourceName));
} else {
builder.setName(restHeaderParam.value().asString());
}
diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/JavaBeanUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JavaBeanUtil.java
similarity index 97%
rename from independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/JavaBeanUtil.java
rename to independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JavaBeanUtil.java
index 6dc4e4ada191d..7e41204ba9282 100644
--- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/JavaBeanUtil.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JavaBeanUtil.java
@@ -1,4 +1,4 @@
-package org.jboss.resteasy.reactive.server.processor.util;
+package org.jboss.resteasy.reactive.common.processor;
import org.jboss.jandex.DotName;
diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/StringUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/StringUtil.java
index 2898c52a40aef..2cdbcc7c0e14b 100644
--- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/StringUtil.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/StringUtil.java
@@ -1,6 +1,7 @@
package org.jboss.resteasy.reactive.common.processor;
import java.util.Iterator;
+import java.util.Locale;
import java.util.NoSuchElementException;
public class StringUtil {
@@ -82,4 +83,25 @@ public String next() {
public static String hyphenate(final String orig) {
return String.join("-", (Iterable) () -> camelHumpsIterator(orig));
}
-}
\ No newline at end of file
+
+ /**
+ * Hyphenates every part of the original string, but also converts the first letter
+ * of every part into a capital letter
+ */
+ public static String hyphenateWithCapitalFirstLetter(final String orig) {
+ StringBuilder sb = new StringBuilder();
+ boolean isFirst = true;
+ Iterator it = StringUtil.camelHumpsIterator(orig);
+ while (it.hasNext()) {
+ String part = it.next();
+ part = part.substring(0, 1).toUpperCase(Locale.ROOT) + part.substring(1);
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ sb.append("-");
+ }
+ sb.append(part);
+ }
+ return sb.toString();
+ }
+}
diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveParameterContainerScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveParameterContainerScanner.java
index 59be140fa0e7c..0e4b43f451e6e 100644
--- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveParameterContainerScanner.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveParameterContainerScanner.java
@@ -15,12 +15,17 @@ public static Set scanParameterContainers(IndexView index, ApplicationS
Set res = new HashSet();
for (DotName fieldAnnotation : ResteasyReactiveDotNames.JAX_RS_ANNOTATIONS_FOR_FIELDS) {
for (AnnotationInstance annotationInstance : index.getAnnotations(fieldAnnotation)) {
- // FIXME: this only supports fields, not properties, but not sure beanparam supports them anyway
+ // these annotations can be on fields or properties
if (annotationInstance.target().kind() == Kind.FIELD) {
ClassInfo klass = annotationInstance.target().asField().declaringClass();
if (result.keepClass(klass.name().toString())) {
res.add(klass.name());
}
+ } else if (annotationInstance.target().kind() == Kind.METHOD) {
+ ClassInfo klass = annotationInstance.target().asMethod().declaringClass();
+ if (result.keepClass(klass.name().toString())) {
+ res.add(klass.name());
+ }
}
}
}
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/AbstractResponseBuilder.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/AbstractResponseBuilder.java
index 591867ed4b82f..27a43da67ef28 100644
--- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/AbstractResponseBuilder.java
+++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/AbstractResponseBuilder.java
@@ -131,9 +131,11 @@ public T populateResponse(T response, boolean copyHeade
}
public void setAllHeaders(MultivaluedMap values) {
- for (Map.Entry> i : values.entrySet()) {
- for (String v : i.getValue()) {
- metadata.add(i.getKey(), v);
+ if (values != null) {
+ for (Map.Entry> i : values.entrySet()) {
+ for (String v : i.getValue()) {
+ metadata.add(i.getKey(), v);
+ }
}
}
}
diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml
index ac1bccbf87c93..126d69fd82437 100644
--- a/independent-projects/resteasy-reactive/pom.xml
+++ b/independent-projects/resteasy-reactive/pom.xml
@@ -56,7 +56,7 @@
2.0.1.Final1.1.61.7.0
- 1.13.1
+ 1.13.24.3.44.5.11.0.0.Final
diff --git a/independent-projects/resteasy-reactive/server/jsonb/src/main/java/org/jboss/resteasy/reactive/server/jsonb/JsonbMessageBodyWriter.java b/independent-projects/resteasy-reactive/server/jsonb/src/main/java/org/jboss/resteasy/reactive/server/jsonb/JsonbMessageBodyWriter.java
index 7c92d8ce15425..e4ce7e30f5e4d 100644
--- a/independent-projects/resteasy-reactive/server/jsonb/src/main/java/org/jboss/resteasy/reactive/server/jsonb/JsonbMessageBodyWriter.java
+++ b/independent-projects/resteasy-reactive/server/jsonb/src/main/java/org/jboss/resteasy/reactive/server/jsonb/JsonbMessageBodyWriter.java
@@ -13,6 +13,7 @@
import javax.ws.rs.core.MultivaluedMap;
import org.jboss.resteasy.reactive.common.providers.serialisers.JsonMessageBodyWriterUtil;
+import org.jboss.resteasy.reactive.server.NoopCloseAndFlushOutputStream;
import org.jboss.resteasy.reactive.server.StreamingOutputStream;
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
@@ -51,40 +52,4 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte
// we don't use try-with-resources because that results in writing to the http output without the exception mapping coming into play
originalStream.close();
}
-
- /**
- * This class is needed because Yasson doesn't give us a way to control if the output stream is going to be closed or not
- */
- private static class NoopCloseAndFlushOutputStream extends OutputStream {
- private final OutputStream delegate;
-
- public NoopCloseAndFlushOutputStream(OutputStream delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public void flush() {
-
- }
-
- @Override
- public void close() {
-
- }
-
- @Override
- public void write(int b) throws IOException {
- delegate.write(b);
- }
-
- @Override
- public void write(byte[] b) throws IOException {
- delegate.write(b);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- delegate.write(b, off, len);
- }
- }
}
diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/multipart/FormDataOutputMapperGenerator.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/multipart/FormDataOutputMapperGenerator.java
index 7374f891a8fd4..f309275786abf 100644
--- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/multipart/FormDataOutputMapperGenerator.java
+++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/multipart/FormDataOutputMapperGenerator.java
@@ -4,7 +4,6 @@
import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.REST_FORM_PARAM;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.MediaType;
@@ -18,11 +17,12 @@
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.common.processor.JavaBeanUtil;
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
+import org.jboss.resteasy.reactive.server.core.multipart.MultipartFormDataOutput;
import org.jboss.resteasy.reactive.server.core.multipart.MultipartMessageBodyWriter;
import org.jboss.resteasy.reactive.server.core.multipart.MultipartOutputInjectionTarget;
import org.jboss.resteasy.reactive.server.core.multipart.PartItem;
-import org.jboss.resteasy.reactive.server.processor.util.JavaBeanUtil;
import io.quarkus.gizmo.AssignableResultHandle;
import io.quarkus.gizmo.ClassCreator;
@@ -32,12 +32,12 @@
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
-final class FormDataOutputMapperGenerator {
+public final class FormDataOutputMapperGenerator {
private static final Logger LOGGER = Logger.getLogger(FormDataOutputMapperGenerator.class);
private static final String TRANSFORM_METHOD_NAME = "mapFrom";
- private static final String ARRAY_LIST_ADD_METHOD_NAME = "add";
+ private static final String ADD_FORM_DATA_METHOD_NAME = "addFormData";
private FormDataOutputMapperGenerator() {
}
@@ -121,29 +121,30 @@ public static boolean isReturnTypeCompatible(ClassInfo returnTypeClassInfo, Inde
*
*/
- static String generate(ClassInfo returnTypeClassInfo, ClassOutput classOutput, IndexView index) {
+ public static String generate(ClassInfo returnTypeClassInfo, ClassOutput classOutput, IndexView index) {
String returnClassName = returnTypeClassInfo.name().toString();
String generateClassName = MultipartMessageBodyWriter.getGeneratedMapperClassNameFor(returnClassName);
String interfaceClassName = MultipartOutputInjectionTarget.class.getName();
try (ClassCreator cc = new ClassCreator(classOutput, generateClassName, null, Object.class.getName(),
interfaceClassName)) {
- MethodCreator populate = cc.getMethodCreator(TRANSFORM_METHOD_NAME, List.class.getName(),
+ MethodCreator populate = cc.getMethodCreator(TRANSFORM_METHOD_NAME, MultipartFormDataOutput.class.getName(),
Object.class);
populate.setModifiers(Modifier.PUBLIC);
- ResultHandle listPartItemListInstanceHandle = populate.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
+ ResultHandle formDataInstanceHandle = populate.newInstance(MethodDescriptor
+ .ofConstructor(MultipartFormDataOutput.class));
ResultHandle inputInstanceHandle = populate.checkCast(populate.getMethodParam(0), returnClassName);
// go up the class hierarchy until we reach Object
@@ -218,24 +219,20 @@ static String generate(ClassInfo returnTypeClassInfo, ClassOutput classOutput, I
}
// Get parameterized type if field type is a parameterized class
- String firstParamType = "";
+ String genericType = "";
if (fieldType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
List argumentTypes = fieldType.asParameterizedType().arguments();
if (argumentTypes.size() > 0) {
- firstParamType = argumentTypes.get(0).name().toString();
+ genericType = argumentTypes.get(0).name().toString();
}
}
- // Create Part Item instance
- ResultHandle partItemInstanceHandle = populate.newInstance(
- MethodDescriptor.ofConstructor(PartItem.class,
- String.class, MediaType.class, Object.class, String.class),
- populate.load(formAttrName), partTypeHandle, resultHandle, populate.load(firstParamType));
-
- // Add it to the list
+ // Add it to the form data object
populate.invokeVirtualMethod(
- MethodDescriptor.ofMethod(ArrayList.class, ARRAY_LIST_ADD_METHOD_NAME, boolean.class, Object.class),
- listPartItemListInstanceHandle, partItemInstanceHandle);
+ MethodDescriptor.ofMethod(MultipartFormDataOutput.class, ADD_FORM_DATA_METHOD_NAME, PartItem.class,
+ String.class, Object.class, String.class, MediaType.class),
+ formDataInstanceHandle,
+ populate.load(formAttrName), resultHandle, populate.load(genericType), partTypeHandle);
}
DotName superClassDotName = currentClassInHierarchy.superName();
@@ -250,7 +247,7 @@ static String generate(ClassInfo returnTypeClassInfo, ClassOutput classOutput, I
currentClassInHierarchy = newCurrentClassInHierarchy;
}
- populate.returnValue(listPartItemListInstanceHandle);
+ populate.returnValue(formDataInstanceHandle);
}
return generateClassName;
}
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/NoopCloseAndFlushOutputStream.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/NoopCloseAndFlushOutputStream.java
new file mode 100644
index 0000000000000..ceff72b4aba95
--- /dev/null
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/NoopCloseAndFlushOutputStream.java
@@ -0,0 +1,41 @@
+package org.jboss.resteasy.reactive.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class is needed because some message writers don't give us a way to control if the output stream is going to be closed
+ * or not.
+ */
+public class NoopCloseAndFlushOutputStream extends OutputStream {
+ private final OutputStream delegate;
+
+ public NoopCloseAndFlushOutputStream(OutputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void flush() {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ delegate.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ delegate.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ delegate.write(b, off, len);
+ }
+}
\ No newline at end of file
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
index f309df5d6ad4b..2b01d53642305 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
@@ -151,7 +151,7 @@ public Deployment getDeployment() {
}
public ProvidersImpl getProviders() {
- // this is rarely called (basically only of '@Context Providers' is used),
+ // this is rarely called (basically only if '@Context Providers' is used),
// so let's avoid creating an extra field
return new ProvidersImpl(deployment);
}
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java
index 7959c0f364e06..0a27fbc05fa8c 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java
@@ -43,6 +43,8 @@
import org.jboss.resteasy.reactive.common.util.MediaTypeHelper;
import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap;
import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedMap;
+import org.jboss.resteasy.reactive.server.core.multipart.MultipartFormDataOutput;
+import org.jboss.resteasy.reactive.server.core.multipart.MultipartMessageBodyWriter;
import org.jboss.resteasy.reactive.server.core.serialization.EntityWriter;
import org.jboss.resteasy.reactive.server.core.serialization.FixedEntityWriterArray;
import org.jboss.resteasy.reactive.server.jaxrs.WriterInterceptorContextImpl;
@@ -134,6 +136,8 @@ public void accept(ResteasyReactiveRequestContext context) {
MediaType.WILDCARD),
new Serialisers.BuiltinWriter(FilePart.class, ServerFilePartBodyHandler.class,
MediaType.WILDCARD),
+ new Serialisers.BuiltinWriter(MultipartFormDataOutput.class, MultipartMessageBodyWriter.class,
+ MediaType.MULTIPART_FORM_DATA),
new Serialisers.BuiltinWriter(java.nio.file.Path.class, ServerPathBodyHandler.class,
MediaType.WILDCARD),
new Serialisers.BuiltinWriter(PathPart.class, ServerPathPartBodyHandler.class,
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartFormDataOutput.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartFormDataOutput.java
new file mode 100644
index 0000000000000..9b8acc4b02d38
--- /dev/null
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartFormDataOutput.java
@@ -0,0 +1,25 @@
+package org.jboss.resteasy.reactive.server.core.multipart;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+
+public class MultipartFormDataOutput {
+ private final Map parts = new HashMap<>();
+
+ public Map getFormData() {
+ return Collections.unmodifiableMap(parts);
+ }
+
+ public PartItem addFormData(String key, Object entity, MediaType mediaType) {
+ return addFormData(key, entity, null, mediaType);
+ }
+
+ public PartItem addFormData(String key, Object entity, String genericType, MediaType mediaType) {
+ PartItem part = new PartItem(entity, genericType, mediaType);
+ parts.put(key, part);
+ return part;
+ }
+}
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartMessageBodyWriter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartMessageBodyWriter.java
index b963504d5cd5b..a7915b23b9349 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartMessageBodyWriter.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultipartMessageBodyWriter.java
@@ -9,7 +9,9 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
+import java.util.stream.Collectors;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.WebApplicationException;
@@ -21,6 +23,7 @@
import org.jboss.resteasy.reactive.common.core.Serialisers;
import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator;
import org.jboss.resteasy.reactive.multipart.FileDownload;
+import org.jboss.resteasy.reactive.server.NoopCloseAndFlushOutputStream;
import org.jboss.resteasy.reactive.server.core.CurrentRequestManager;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.core.ServerSerialisers;
@@ -56,33 +59,39 @@ private void writeMultiformData(Object o, MediaType mediaType, OutputStream outp
String boundary = generateBoundary();
appendBoundaryIntoMediaType(requestContext, boundary, mediaType);
- List formData = toFormData(o);
+ MultipartFormDataOutput formData;
+ if (o instanceof MultipartFormDataOutput) {
+ formData = (MultipartFormDataOutput) o;
+ } else {
+ formData = toFormData(o);
+ }
write(formData, boundary, outputStream, requestContext);
}
- private List toFormData(Object o) {
+ private MultipartFormDataOutput toFormData(Object o) {
String transformer = getGeneratedMapperClassNameFor(o.getClass().getName());
BeanFactory.BeanInstance instance = new ReflectionBeanFactoryCreator().apply(transformer).createInstance();
return ((MultipartOutputInjectionTarget) instance.getInstance()).mapFrom(o);
}
- private void write(List parts, String boundary, OutputStream outputStream,
+ private void write(MultipartFormDataOutput formDataOutput, String boundary, OutputStream outputStream,
ResteasyReactiveRequestContext requestContext)
throws IOException {
Charset charset = requestContext.getDeployment().getRuntimeConfiguration().body().defaultCharset();
String boundaryLine = "--" + boundary;
- for (PartItem part : parts) {
- Object partValue = part.getValue();
+ Map parts = formDataOutput.getFormData();
+ for (Map.Entry entry : parts.entrySet()) {
+ String partName = entry.getKey();
+ PartItem part = entry.getValue();
+ Object partValue = part.getEntity();
if (partValue != null) {
if (isListOf(part, File.class) || isListOf(part, FileDownload.class)) {
- List
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithIngressRulesTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithIngressRulesTest.java
new file mode 100644
index 0000000000000..5122343f0400b
--- /dev/null
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithIngressRulesTest.java
@@ -0,0 +1,95 @@
+
+package io.quarkus.it.kubernetes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.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.networking.v1.Ingress;
+import io.fabric8.kubernetes.api.model.networking.v1.IngressRule;
+import io.quarkus.builder.Version;
+import io.quarkus.maven.dependency.Dependency;
+import io.quarkus.test.ProdBuildResults;
+import io.quarkus.test.ProdModeTestResults;
+import io.quarkus.test.QuarkusProdModeTest;
+
+public class KubernetesWithIngressRulesTest {
+
+ private static final String APP_NAME = "kubernetes-with-ingress-rules";
+ private static final String PROD_INGRESS_HOST = "prod.svc.url";
+ private static final String DEV_INGRESS_HOST = "dev.svc.url";
+ private static final String ALT_INGRESS_HOST = "alt.svc.url";
+ private static final String PREFIX = "Prefix";
+ private static final String HTTP = "http";
+
+ @RegisterExtension
+ static final QuarkusProdModeTest config = new QuarkusProdModeTest()
+ .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class))
+ .setApplicationName(APP_NAME)
+ .setApplicationVersion("0.1-SNAPSHOT")
+ .withConfigurationResource(APP_NAME + ".properties")
+ .setLogFileName("k8s.log")
+ .setForcedDependencies(List.of(Dependency.of("io.quarkus", "quarkus-kubernetes", Version.getVersion())));
+
+ @ProdBuildResults
+ private ProdModeTestResults prodModeTestResults;
+
+ @Test
+ public void assertGeneratedResources() throws IOException {
+ final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes");
+ assertThat(kubernetesDir)
+ .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json"))
+ .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml"));
+ List list = DeserializationUtil
+ .deserializeAsList(kubernetesDir.resolve("kubernetes.yml"));
+ Ingress i = findFirst(list, Ingress.class).orElseThrow(() -> new IllegalStateException());
+ assertNotNull(i);
+ assertEquals(3, i.getSpec().getRules().size(), "There are more rules than expected");
+ assertTrue(i.getSpec().getRules().stream().anyMatch(this::generatedRuleWasUpdated));
+ assertTrue(i.getSpec().getRules().stream().anyMatch(this::newRuleWasAdded));
+ assertTrue(i.getSpec().getRules().stream().anyMatch(this::newRuleWasAddedWithCustomService));
+ }
+
+ private boolean newRuleWasAddedWithCustomService(IngressRule rule) {
+ return ALT_INGRESS_HOST.equals(rule.getHost())
+ && rule.getHttp().getPaths().size() == 1
+ && rule.getHttp().getPaths().stream().anyMatch(p -> p.getPath().equals("/ea")
+ && p.getPathType().equals(PREFIX)
+ && p.getBackend().getService().getName().equals("updated-service")
+ && p.getBackend().getService().getPort().getName().equals("tcp"));
+ }
+
+ private boolean newRuleWasAdded(IngressRule rule) {
+ return DEV_INGRESS_HOST.equals(rule.getHost())
+ && rule.getHttp().getPaths().size() == 1
+ && rule.getHttp().getPaths().stream().anyMatch(p -> p.getPath().equals("/dev")
+ && p.getPathType().equals("ImplementationSpecific")
+ && p.getBackend().getService().getName().equals(APP_NAME)
+ && p.getBackend().getService().getPort().getName().equals(HTTP));
+ }
+
+ private boolean generatedRuleWasUpdated(IngressRule rule) {
+ return PROD_INGRESS_HOST.equals(rule.getHost())
+ && rule.getHttp().getPaths().size() == 1
+ && rule.getHttp().getPaths().stream().anyMatch(p -> p.getPath().equals("/prod")
+ && p.getPathType().equals(PREFIX)
+ && p.getBackend().getService().getName().equals(APP_NAME)
+ && p.getBackend().getService().getPort().getName().equals(HTTP));
+ }
+
+ Optional findFirst(List list, Class t) {
+ return (Optional) list.stream()
+ .filter(i -> t.isInstance(i))
+ .findFirst();
+ }
+}
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-ingress-rules.properties b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-ingress-rules.properties
new file mode 100644
index 0000000000000..c59448fa3f7c3
--- /dev/null
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-ingress-rules.properties
@@ -0,0 +1,19 @@
+quarkus.kubernetes.ingress.expose=true
+quarkus.kubernetes.ingress.host=prod.svc.url
+quarkus.kubernetes.ports.http.path=/prod
+
+# Case 1: Update existing rule
+quarkus.kubernetes.ingress.rules.0.host=prod.svc.url
+quarkus.kubernetes.ingress.rules.0.path=/prod
+
+# Case 2: Add a new rule
+quarkus.kubernetes.ingress.rules.1.host=dev.svc.url
+quarkus.kubernetes.ingress.rules.1.path=/dev
+quarkus.kubernetes.ingress.rules.1.path-type=ImplementationSpecific
+# by default, path type is Prefix
+
+# Case 2: Add a new rule using another service
+quarkus.kubernetes.ingress.rules.2.host=alt.svc.url
+quarkus.kubernetes.ingress.rules.2.path=/ea
+quarkus.kubernetes.ingress.rules.2.service-name=updated-service
+quarkus.kubernetes.ingress.rules.2.service-port-name=tcp
\ No newline at end of file
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java
index ef5d03e70a742..16b859cf388da 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java
@@ -396,6 +396,34 @@ public void testProjectGenerationFromScratchWithCustomDependencies() throws Exce
&& d.getVersion().equalsIgnoreCase("2.5"))).isTrue();
}
+ @Test
+ public void testBadArtifactId() throws Exception {
+ testDir = initEmptyProject("projects/project-generation-with-bad-artifact-id");
+ assertThat(testDir).isDirectory();
+ invoker = initInvoker(testDir);
+
+ Properties properties = new Properties();
+ properties.put("projectArtifactId", "acme,fail");
+ properties.put("extensions", "resteasy,commons-io:commons-io:2.5");
+ InvocationResult result = setup(properties);
+
+ assertThat(result.getExitCode()).isNotZero();
+ }
+
+ @Test
+ public void testBadGroupId() throws Exception {
+ testDir = initEmptyProject("projects/project-generation-with-bad-group-id");
+ assertThat(testDir).isDirectory();
+ invoker = initInvoker(testDir);
+
+ Properties properties = new Properties();
+ properties.put("projectGroupId", "acme,fail");
+ properties.put("extensions", "resteasy,commons-io:commons-io:2.5");
+ InvocationResult result = setup(properties);
+
+ assertThat(result.getExitCode()).isNotZero();
+ }
+
@Test
public void testProjectGenerationFromScratchWithAppConfigParameter() throws MavenInvocationException, IOException {
testDir = initEmptyProject("projects/project-generation-with-config-param");
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java
index a82d3ba0c65bd..5dd019dd008f6 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java
@@ -56,6 +56,32 @@
@DisableForNative
public class DevMojoIT extends RunAndCheckMojoTestBase {
+ @Test
+ public void testConfigFactoryInAppModuleBannedInCodeGen() throws MavenInvocationException, IOException {
+ testDir = initProject("projects/codegen-config-factory");
+ run(true);
+ assertThat(DevModeTestUtils.getHttpResponse("/codegen-config/acme-config-factory")).isEqualTo("n/a");
+ assertThat(DevModeTestUtils.getHttpResponse("/codegen-config/acme-config-provider")).isEqualTo("n/a");
+ assertThat(DevModeTestUtils.getHttpResponse("/runtime-config/acme-config-factory"))
+ .isEqualTo("org.acme.AppConfigSourceFactory");
+ assertThat(DevModeTestUtils.getHttpResponse("/runtime-config/acme-config-provider"))
+ .isEqualTo("org.acme.AppConfigSourceProvider");
+ }
+
+ @Test
+ public void testConfigFactoryInAppModuleFilteredInCodeGen() throws MavenInvocationException, IOException {
+ testDir = initProject("projects/codegen-config-factory");
+ run(true, "-Dconfig-factory.enabled");
+ assertThat(DevModeTestUtils.getHttpResponse("/codegen-config/acme-config-factory"))
+ .isEqualTo("org.acme.config.AcmeConfigSourceFactory");
+ assertThat(DevModeTestUtils.getHttpResponse("/codegen-config/acme-config-provider"))
+ .isEqualTo("org.acme.config.AcmeConfigSourceProvider");
+ assertThat(DevModeTestUtils.getHttpResponse("/runtime-config/acme-config-factory"))
+ .isEqualTo("org.acme.AppConfigSourceFactory");
+ assertThat(DevModeTestUtils.getHttpResponse("/runtime-config/acme-config-provider"))
+ .isEqualTo("org.acme.AppConfigSourceProvider");
+ }
+
@Test
public void testSystemPropertiesConfig() throws MavenInvocationException, IOException {
testDir = initProject("projects/dev-mode-sys-props-config");
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/pom.xml
new file mode 100644
index 0000000000000..fdc1c6b2a70d5
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ org.acme
+ acme-codegen-parent
+ 1.0.0-SNAPSHOT
+
+ acme-codegen-deployment
+ Acme - Codegen - Deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ org.acme
+ acme-codegen
+
+
+ org.acme
+ acme-constants
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.platform.version}
+
+
+
+
+
+
+
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/java/org/acme/codegen/deployment/AcmeCodegenProcessor.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/java/org/acme/codegen/deployment/AcmeCodegenProcessor.java
new file mode 100644
index 0000000000000..64ebc9987b1c1
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/java/org/acme/codegen/deployment/AcmeCodegenProcessor.java
@@ -0,0 +1,14 @@
+package org.acme.codegen.deployment;
+
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+
+class AcmeCodegenProcessor {
+
+ private static final String FEATURE = "acme-codegen";
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/java/org/acme/codegen/deployment/AcmeCodegenProvider.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/java/org/acme/codegen/deployment/AcmeCodegenProvider.java
new file mode 100644
index 0000000000000..26a3a961c433b
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/java/org/acme/codegen/deployment/AcmeCodegenProvider.java
@@ -0,0 +1,88 @@
+package org.acme.codegen.deployment;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.acme.AcmeConstants;
+import org.eclipse.microprofile.config.Config;
+
+import io.quarkus.bootstrap.prebuild.CodeGenException;
+import io.quarkus.deployment.CodeGenContext;
+import io.quarkus.deployment.CodeGenProvider;
+
+public class AcmeCodegenProvider implements CodeGenProvider {
+
+ private static final String ACME = "acme";
+
+ @Override
+ public String providerId() {
+ return ACME;
+ }
+
+ @Override
+ public String inputExtension() {
+ return ACME;
+ }
+
+ @Override
+ public String inputDirectory() {
+ return ACME;
+ }
+
+ @Override
+ public boolean trigger(CodeGenContext context) throws CodeGenException {
+ generateEndpoint(context, AcmeConstants.ACME_CONFIG_FACTORY_PROP);
+ generateEndpoint(context, AcmeConstants.ACME_CONFIG_PROVIDER_PROP);
+ return true;
+ }
+
+ @Override
+ public boolean shouldRun(Path sourceDir, Config config) {
+ return !sourceDir.getParent().getFileName().toString().equals("generated-test-sources");
+ }
+
+ private void generateEndpoint(CodeGenContext ctx, String propName) throws CodeGenException {
+ try {
+ generateEndpoint(ctx.outDir(), propName, ctx.config().getOptionalValue(propName, String.class).orElse(AcmeConstants.NA));
+ } catch (IOException e) {
+ throw new CodeGenException("Failed to generate sources", e);
+ }
+ }
+
+ private static void generateEndpoint(Path outputDir, String name, String value) throws IOException {
+
+ final StringBuilder sb = new StringBuilder();
+ boolean nextUpperCase = true;
+ for(int i = 0; i < name.length(); ++i) {
+ char c = name.charAt(i);
+ if(c == '-') {
+ nextUpperCase = true;
+ } else if(nextUpperCase) {
+ sb.append(Character.toUpperCase(c));
+ nextUpperCase = false;
+ } else {
+ sb.append(c);
+ }
+ }
+ sb.append("Resource");
+
+ final String className = sb.toString();
+ final Path javaFile = outputDir.resolve("org").resolve(ACME).resolve(className + ".java");
+ Files.createDirectories(javaFile.getParent());
+ try(PrintWriter out = new PrintWriter(Files.newBufferedWriter(javaFile))) {
+ out.println("package org.acme;");
+ out.println("import javax.ws.rs.GET;");
+ out.println("import javax.ws.rs.Path;");
+ out.println("@Path(\"/codegen-config\")");
+ out.println("public class " + className + " {");
+ out.println(" @GET");
+ out.println(" @Path(\"/" + name + "\")");
+ out.println(" public String main() {");
+ out.println(" return \"" + value + "\";");
+ out.println(" }");
+ out.println("}");
+ }
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider
new file mode 100644
index 0000000000000..355c16c6b60a4
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider
@@ -0,0 +1 @@
+org.acme.codegen.deployment.AcmeCodegenProvider
\ No newline at end of file
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/test/java/org/acme/codegen/test/AcmeCodegenDevModeTest.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/test/java/org/acme/codegen/test/AcmeCodegenDevModeTest.java
new file mode 100644
index 0000000000000..6271982e26b5c
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/test/java/org/acme/codegen/test/AcmeCodegenDevModeTest.java
@@ -0,0 +1,23 @@
+package org.acme.codegen.test;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusDevModeTest;
+
+public class AcmeCodegenDevModeTest {
+
+ // Start hot reload (DevMode) test with your extension loaded
+ @RegisterExtension
+ static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
+
+ @Test
+ public void writeYourOwnDevModeTest() {
+ // Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information
+ Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName());
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/test/java/org/acme/codegen/test/AcmeCodegenTest.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/test/java/org/acme/codegen/test/AcmeCodegenTest.java
new file mode 100644
index 0000000000000..42a43445c2030
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/deployment/src/test/java/org/acme/codegen/test/AcmeCodegenTest.java
@@ -0,0 +1,23 @@
+package org.acme.codegen.test;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class AcmeCodegenTest {
+
+ // Start unit test with your extension loaded
+ @RegisterExtension
+ static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
+
+ @Test
+ public void writeYourOwnUnitTest() {
+ // Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information
+ Assertions.assertTrue(true, "Add some assertions to " + getClass().getName());
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/pom.xml
new file mode 100644
index 0000000000000..8b1a28ea1540a
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+ org.acme
+ acme-parent
+ 1.0.0-SNAPSHOT
+
+ acme-codegen-parent
+ pom
+ Acme - Codegen - Parent
+
+ deployment
+ runtime
+
+
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/runtime/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/runtime/pom.xml
new file mode 100644
index 0000000000000..ada345f7da918
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/runtime/pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+ org.acme
+ acme-codegen-parent
+ 1.0.0-SNAPSHOT
+
+ acme-codegen
+ Acme - Codegen - Runtime
+
+
+ io.quarkus
+ quarkus-arc
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+ ${quarkus.platform.version}
+
+
+ compile
+
+ extension-descriptor
+
+
+ \${project.groupId}:\${project.artifactId}-deployment:\${project.version}
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.platform.version}
+
+
+
+
+
+
+
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..b8273680c8d08
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/acme-codegen/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,9 @@
+name: Acme Codegen
+#description: Do something useful.
+metadata:
+# keywords:
+# - acme-codegen
+# guide: ...
+# categories:
+# - "miscellaneous"
+# status: "preview"
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/pom.xml
new file mode 100644
index 0000000000000..ed68335945f49
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ org.acme
+ acme-parent
+ 1.0.0-SNAPSHOT
+
+ acme-app
+ Acme - App
+
+
+ org.acme
+ acme-codegen
+
+
+ org.acme
+ acme-constants
+
+
+ io.quarkus
+ quarkus-resteasy-reactive
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+
+
+
+ config-factory
+
+
+ config-factory.enabled
+
+
+
+
+ org.acme
+ acme-config-factory
+
+
+
+
+
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000000000..1617fd89fa050
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,94 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/code-with-quarkus-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV AB_JOLOKIA_OFF=""
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar
new file mode 100644
index 0000000000000..189ff4eb040e3
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.legacy-jar
@@ -0,0 +1,90 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dquarkus.package.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/code-with-quarkus-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV AB_JOLOKIA_OFF=""
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.native b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000000000..4e076cf16088b
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.native
@@ -0,0 +1,27 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/code-with-quarkus .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.native-micro b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.native-micro
new file mode 100644
index 0000000000000..40afb1a22a0dd
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/docker/Dockerfile.native-micro
@@ -0,0 +1,30 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/code-with-quarkus .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus
+#
+###
+FROM quay.io/quarkus/quarkus-micro-image:2.0
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/AppConfigSourceFactory.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/AppConfigSourceFactory.java
new file mode 100644
index 0000000000000..7e5e569d79af7
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/AppConfigSourceFactory.java
@@ -0,0 +1,18 @@
+package org.acme;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+import io.smallrye.config.ConfigSourceContext;
+import io.smallrye.config.ConfigSourceFactory;
+import io.smallrye.config.PropertiesConfigSource;
+
+public class AppConfigSourceFactory implements ConfigSourceFactory {
+
+ @Override
+ public Iterable getConfigSources(ConfigSourceContext context) {
+ return List.of(new PropertiesConfigSource(Map.of(AcmeConstants.ACME_CONFIG_FACTORY_PROP, getClass().getName()), getClass().getName(), 150));
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/AppConfigSourceProvider.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/AppConfigSourceProvider.java
new file mode 100644
index 0000000000000..81506250e6eec
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/AppConfigSourceProvider.java
@@ -0,0 +1,17 @@
+package org.acme;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
+
+import io.smallrye.config.PropertiesConfigSource;
+
+public class AppConfigSourceProvider implements ConfigSourceProvider {
+
+ @Override
+ public Iterable getConfigSources(ClassLoader forClassLoader) {
+ return List.of(new PropertiesConfigSource(Map.of(AcmeConstants.ACME_CONFIG_PROVIDER_PROP, getClass().getName()), getClass().getName(), 150));
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/GreetingResource.java b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/GreetingResource.java
new file mode 100644
index 0000000000000..3868368bf9377
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/java/org/acme/GreetingResource.java
@@ -0,0 +1,28 @@
+package org.acme;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.resteasy.reactive.RestPath;
+
+@Path("/runtime-config")
+public class GreetingResource {
+
+ @ConfigProperty(name = AcmeConstants.ACME_CONFIG_PROVIDER_PROP)
+ String acmeConfigSourceProvider;
+ @ConfigProperty(name = AcmeConstants.ACME_CONFIG_FACTORY_PROP)
+ String acmeConfigSourceFactory;
+
+ @GET
+ @Path("{name}")
+ public String hello(@RestPath String name) {
+ if(AcmeConstants.ACME_CONFIG_PROVIDER_PROP.equals(name)) {
+ return acmeConfigSourceProvider;
+ }
+ if(AcmeConstants.ACME_CONFIG_FACTORY_PROP.equals(name)) {
+ return acmeConfigSourceFactory;
+ }
+ throw new IllegalArgumentException(name);
+ }
+}
\ No newline at end of file
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/resources/META-INF/resources/index.html b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/resources/META-INF/resources/index.html
new file mode 100644
index 0000000000000..d76eed75583b9
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/codegen-config-factory/app/src/main/resources/META-INF/resources/index.html
@@ -0,0 +1,279 @@
+
+
+
+
+ code-with-quarkus - 1.0.0-SNAPSHOT
+
+
+
+