diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 41905d456312f..65b925421f065 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -17,7 +17,7 @@
2.0.1
1.77
1.0.2.4
- 1.0.17
+ 1.0.18
5.0.0
3.0.2
3.1.6
@@ -157,14 +157,14 @@
2.14.0
2.2.0
1.0.0
- 1.9.21
+ 1.9.22
1.7.3
0.27.0
1.6.2
4.0.3
3.2.0
4.2.0
- 3.0.2.Final
+ 3.0.4.Final
9.22.3
3.0.3
diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index ae8c28c5ea334..5124c1c267c1e 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -20,7 +20,7 @@
3.11.0
- 1.9.21
+ 1.9.22
1.9.10
2.13.8
4.8.1
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java
index 346e3adf46bd7..a1b3e84a7fd53 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java
@@ -619,10 +619,23 @@ private static BiFunction generate
}
private static ResultHandle getLogManagerLevelIntValue(String levelName, BytecodeCreator method) {
- final ResultHandle infoLevel = method.readStaticField(
- FieldDescriptor.of(org.jboss.logmanager.Level.class, levelName, org.jboss.logmanager.Level.class));
+ FieldDescriptor fd;
+ switch (levelName) {
+ case "FATAL":
+ case "ERROR":
+ case "WARN":
+ case "INFO":
+ case "DEBUG":
+ case "TRACE":
+ fd = FieldDescriptor.of(org.jboss.logmanager.Level.class, levelName, org.jboss.logmanager.Level.class);
+ break;
+ default:
+ fd = FieldDescriptor.of(Level.class, levelName, Level.class);
+ break;
+ }
+ final ResultHandle levelVal = method.readStaticField(fd);
return method
- .invokeVirtualMethod(MethodDescriptor.ofMethod(Level.class, "intValue", int.class), infoLevel);
+ .invokeVirtualMethod(MethodDescriptor.ofMethod(Level.class, "intValue", int.class), levelVal);
}
private static void generateDefaultLoggingLogger(Level minLevel, ClassOutput output) {
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java
index c3dd57bae5e17..d63b5535faf49 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java
@@ -1,5 +1,7 @@
package io.quarkus.deployment.steps;
+import static io.quarkus.deployment.steps.KotlinUtil.isKotlinClass;
+
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -27,6 +29,8 @@
import org.jboss.jandex.VoidType;
import org.jboss.logging.Logger;
+import io.quarkus.deployment.Capabilities;
+import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
@@ -51,7 +55,7 @@ public ReflectiveHierarchyIgnoreWarningBuildItem ignoreJavaClassWarnings() {
}
@BuildStep
- public void build(CombinedIndexBuildItem combinedIndexBuildItem,
+ public void build(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities,
List hierarchy,
List ignored,
List finalFieldsWritablePredicates,
@@ -73,7 +77,7 @@ public void build(CombinedIndexBuildItem combinedIndexBuildItem,
final Deque visits = new ArrayDeque<>();
for (ReflectiveHierarchyBuildItem i : hierarchy) {
- addReflectiveHierarchy(combinedIndexBuildItem,
+ addReflectiveHierarchy(combinedIndexBuildItem, capabilities,
i,
i.hasSource() ? i.getSource() : i.getType().name().toString(),
i.getType(),
@@ -128,7 +132,7 @@ private void removeIgnored(Map> unindexedClasses,
}
private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildItem,
- ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type,
+ Capabilities capabilities, ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type,
Set processedReflectiveHierarchies, Map> unindexedClasses,
Predicate finalFieldsWritable, BuildProducer reflectiveClass,
Deque visits) {
@@ -142,30 +146,34 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte
return;
}
- addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), type.name(),
+ addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(),
+ type.name(),
processedReflectiveHierarchies, unindexedClasses,
finalFieldsWritable, reflectiveClass, visits);
for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownSubclasses(type.name())) {
- addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(),
+ addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
+ subclass.name(),
subclass.name(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits);
}
for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownImplementors(type.name())) {
- addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(),
+ addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
+ subclass.name(),
subclass.name(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits);
}
} else if (type instanceof ArrayType) {
- visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source,
+ visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities,
+ reflectiveHierarchyBuildItem, source,
type.asArrayType().constituent(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
} else if (type instanceof ParameterizedType) {
if (!reflectiveHierarchyBuildItem.getIgnoreTypePredicate().test(type.name())) {
- addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(),
+ addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(),
type.name(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits);
@@ -173,14 +181,15 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte
final ParameterizedType parameterizedType = (ParameterizedType) type;
for (Type typeArgument : parameterizedType.arguments()) {
visits.addLast(
- () -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, typeArgument,
+ () -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
+ typeArgument,
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
}
}
}
- private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem,
+ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities,
ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem,
String source,
DotName name,
@@ -223,7 +232,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem
return;
}
- visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source,
+ visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
info.superName(), initialName,
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
@@ -237,7 +246,8 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem
}
final Type fieldType = getFieldType(combinedIndexBuildItem, initialName, info, field);
visits.addLast(
- () -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, fieldType,
+ () -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
+ fieldType,
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
}
@@ -249,11 +259,30 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem
method.returnType().kind() == Kind.VOID) {
continue;
}
- visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source,
+ visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities,
+ reflectiveHierarchyBuildItem, source,
method.returnType(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
}
+
+ // for Kotlin classes, we need to register the nested classes as well because companion classes are very often necessary at runtime
+ if (capabilities.isPresent(Capability.KOTLIN) && isKotlinClass(info)) {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Class>[] declaredClasses = classLoader.loadClass(info.name().toString()).getDeclaredClasses();
+ for (Class> clazz : declaredClasses) {
+ DotName dotName = DotName.createSimple(clazz.getName());
+ addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
+ dotName, dotName,
+ processedReflectiveHierarchies, unindexedClasses,
+ finalFieldsWritable, reflectiveClass, visits);
+ }
+ } catch (ClassNotFoundException e) {
+ log.warnf(e, "Failed to load Class %s", info.name().toString());
+ }
+
+ }
}
private static Type getFieldType(CombinedIndexBuildItem combinedIndexBuildItem, DotName initialName, ClassInfo info,
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java
index 574ae74cf169e..d073ca201d7f9 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java
@@ -6,6 +6,7 @@
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
+import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@@ -186,6 +187,9 @@ public static Set configFiles(Path configFilesLocation) throws IOExcepti
for (Path candidate : candidates) {
configFiles.add(candidate.toUri().toURL().toString());
}
+ } catch (NotDirectoryException ignored) {
+ log.debugf("File %s is not a directory", configFilesLocation.toAbsolutePath());
+ return Collections.emptySet();
}
return configFiles;
}
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java
index 13c97cfd722a1..88196dbba173a 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java
@@ -28,6 +28,7 @@
import io.quarkus.gradle.QuarkusPlugin;
import io.quarkus.gradle.tasks.worker.BuildWorker;
import io.quarkus.maven.dependency.GACTV;
+import io.smallrye.config.Expressions;
/**
* Base class for the {@link QuarkusBuildDependencies}, {@link QuarkusBuildCacheableAppParts}, {@link QuarkusBuild} tasks
@@ -207,12 +208,14 @@ void generateBuild() {
ApplicationModel appModel = resolveAppModelForBuild();
Map configMap = new HashMap<>();
- for (Map.Entry entry : extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap()
- .entrySet()) {
- if (entry.getKey().startsWith("quarkus.")) {
- configMap.put(entry.getKey(), entry.getValue());
+ EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact());
+ Expressions.withoutExpansion(() -> {
+ for (Map.Entry entry : effectiveConfig.configMap().entrySet()) {
+ if (entry.getKey().startsWith("quarkus.")) {
+ configMap.put(entry.getKey(), effectiveConfig.config().getRawValue(entry.getKey()));
+ }
}
- }
+ });
getLogger().info("Starting Quarkus application build for package type {}", packageType);
diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java
index 862ad0a62abb6..74f976e2b4b7a 100644
--- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java
+++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java
@@ -105,7 +105,7 @@ public void shouldReturnMultipleOutputSourceDirectories() {
@Test
public void shouldNotFailOnProjectDependenciesWithoutMain(@TempDir Path testProjectDir) throws IOException {
- var kotlinVersion = System.getProperty("kotlin_version", "1.9.21");
+ var kotlinVersion = System.getProperty("kotlin_version", "1.9.22");
var settingFile = testProjectDir.resolve("settings.gradle.kts");
var mppProjectDir = testProjectDir.resolve("mpp");
var quarkusProjectDir = testProjectDir.resolve("quarkus");
diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml
index 5bb10bc0b84fc..a8f4fe2d5dfa3 100644
--- a/devtools/gradle/gradle/libs.versions.toml
+++ b/devtools/gradle/gradle/libs.versions.toml
@@ -2,7 +2,7 @@
plugin-publish = "1.2.1"
# updating Kotlin here makes QuarkusPluginTest > shouldNotFailOnProjectDependenciesWithoutMain(Path) fail
-kotlin = "1.9.21"
+kotlin = "1.9.22"
smallrye-config = "3.4.4"
junit5 = "5.10.1"
diff --git a/docs/src/main/asciidoc/cache.adoc b/docs/src/main/asciidoc/cache.adoc
index 2b711ed7ff187..10f397041d653 100644
--- a/docs/src/main/asciidoc/cache.adoc
+++ b/docs/src/main/asciidoc/cache.adoc
@@ -12,8 +12,6 @@ include::_attributes.adoc[]
In this guide, you will learn how to enable application data caching in any CDI managed bean of your Quarkus application.
-include::{includes}/extension-status.adoc[]
-
== Prerequisites
include::{includes}/prerequisites.adoc[]
diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc
index 31639cfb3af85..7b3537015ebaa 100644
--- a/docs/src/main/asciidoc/getting-started-testing.adoc
+++ b/docs/src/main/asciidoc/getting-started-testing.adoc
@@ -1088,7 +1088,7 @@ or starting a mock HTTP server using https://wiremock.org/[Wiremock] (an example
=== Altering the test class
-When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject the something into the test class, the `inject` methods can be used.
+When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject something into the test class, the `inject` methods can be used.
If for example you have a test like the following:
[source,java]
diff --git a/docs/src/main/asciidoc/kafka-dev-services.adoc b/docs/src/main/asciidoc/kafka-dev-services.adoc
index a94da965a3a21..2c0a35c674c8c 100644
--- a/docs/src/main/asciidoc/kafka-dev-services.adoc
+++ b/docs/src/main/asciidoc/kafka-dev-services.adoc
@@ -82,7 +82,7 @@ For Strimzi, you can select any image with a Kafka version which has Kraft suppo
[source, properties]
----
-quarkus.kafka.devservices.image-name=quay.io/strimzi-test-container/test-container:0.100.0-kafka-3.1.0
+quarkus.kafka.devservices.image-name=quay.io/strimzi-test-container/test-container:0.105.0-kafka-3.6.0
----
== Configuring Kafka topics
diff --git a/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc b/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc
index cb4c541a3c9c1..bce3607373361 100644
--- a/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc
+++ b/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc
@@ -407,7 +407,7 @@ version: '3.5'
services:
zookeeper:
- image: quay.io/strimzi/kafka:0.23.0-kafka-2.8.0
+ image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1
command: [
"sh", "-c",
"bin/zookeeper-server-start.sh config/zookeeper.properties"
@@ -420,7 +420,7 @@ services:
- kafka-quickstart-network
kafka:
- image: quay.io/strimzi/kafka:0.23.0-kafka-2.8.0
+ image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1
command: [
"sh", "-c",
"bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT}"
diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc
index ca1b4c787a759..224a81fbe682e 100644
--- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc
+++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc
@@ -324,7 +324,7 @@ version: '2'
services:
zookeeper:
- image: quay.io/strimzi/kafka:0.22.1-kafka-2.7.0
+ image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1
command: [
"sh", "-c",
"bin/zookeeper-server-start.sh config/zookeeper.properties"
@@ -335,7 +335,7 @@ services:
LOG_DIR: /tmp/logs
kafka:
- image: quay.io/strimzi/kafka:0.22.1-kafka-2.7.0
+ image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1
command: [
"sh", "-c",
"bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT}"
@@ -545,7 +545,7 @@ If we couldn't use Dev Services and wanted to start a Kafka broker and Apicurio
io.strimzi
strimzi-test-container
- 0.22.1
+ 0.105.0
test
@@ -559,7 +559,7 @@ If we couldn't use Dev Services and wanted to start a Kafka broker and Apicurio
[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"]
.build.gradle
----
-testImplementation("io.strimzi:strimzi-test-container:0.22.1") {
+testImplementation("io.strimzi:strimzi-test-container:0.105.0") {
exclude group: "org.apache.logging.log4j", module: "log4j-core"
}
----
diff --git a/docs/src/main/asciidoc/kafka-streams.adoc b/docs/src/main/asciidoc/kafka-streams.adoc
index e5cc5fa70d8fa..28f940e4a9e3a 100644
--- a/docs/src/main/asciidoc/kafka-streams.adoc
+++ b/docs/src/main/asciidoc/kafka-streams.adoc
@@ -499,7 +499,7 @@ version: '3.5'
services:
zookeeper:
- image: strimzi/kafka:0.19.0-kafka-2.5.0
+ image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1
command: [
"sh", "-c",
"bin/zookeeper-server-start.sh config/zookeeper.properties"
@@ -511,7 +511,7 @@ services:
networks:
- kafkastreams-network
kafka:
- image: strimzi/kafka:0.19.0-kafka-2.5.0
+ image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1
command: [
"sh", "-c",
"bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT} --override num.partitions=$${KAFKA_NUM_PARTITIONS}"
diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc
index b559acfa4d85d..f672fbcb4d37c 100644
--- a/docs/src/main/asciidoc/kafka.adoc
+++ b/docs/src/main/asciidoc/kafka.adoc
@@ -2268,7 +2268,7 @@ The configuration of the created Kafka broker can be customized using `@Resource
[source,java]
----
@QuarkusTestResource(value = KafkaCompanionResource.class, initArgs = {
- @ResourceArg(name = "strimzi.kafka.image", value = "quay.io/strimzi/kafka:0.28.0-kafka-3.0.0"), // Image name
+ @ResourceArg(name = "strimzi.kafka.image", value = "quay.io/strimzi-test-container/test-container:0.105.0-kafka-3.6.0"), // Image name
@ResourceArg(name = "kafka.port", value = "9092"), // Fixed port for kafka, by default it will be exposed on a random port
@ResourceArg(name = "kraft", value = "true"), // Enable Kraft mode
@ResourceArg(name = "num.partitions", value = "3"), // Other custom broker configurations
diff --git a/docs/src/main/asciidoc/lifecycle.adoc b/docs/src/main/asciidoc/lifecycle.adoc
index 911f10ff02f8b..a528863ac6bf0 100644
--- a/docs/src/main/asciidoc/lifecycle.adoc
+++ b/docs/src/main/asciidoc/lifecycle.adoc
@@ -111,6 +111,42 @@ public class Main {
}
----
+[TIP]
+====
+`Quarkus.run` also provides a version that allows the code to handle errors.
+For example:
+
+[source,java]
+----
+package com.acme;
+
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.QuarkusApplication;
+import io.quarkus.runtime.annotations.QuarkusMain;
+
+@QuarkusMain
+public class Main {
+ public static void main(String... args) {
+ Quarkus.run(MyApp.class,
+ (exitCode, exception) -> {
+ // do whatever
+ },
+ args);
+ }
+
+ public static class MyApp implements QuarkusApplication {
+
+ @Override
+ public int run(String... args) throws Exception {
+ System.out.println("Do startup logic here");
+ Quarkus.waitForExit();
+ return 0;
+ }
+ }
+}
+----
+====
+
=== Injecting the command line arguments
It is possible to inject the arguments that were passed in on the command line:
diff --git a/docs/src/main/asciidoc/scheduler-reference.adoc b/docs/src/main/asciidoc/scheduler-reference.adoc
index b999361cdb397..3ba552f80c169 100644
--- a/docs/src/main/asciidoc/scheduler-reference.adoc
+++ b/docs/src/main/asciidoc/scheduler-reference.adoc
@@ -302,6 +302,8 @@ The main idea is to keep the logic to skip the execution outside the scheduled b
TIP: A CDI event of type `io.quarkus.scheduler.SkippedExecution` is fired when an execution of a scheduled method is skipped.
+TIP: To skip the scheduled executions while the application is starting up/shutting down, you can make use of the `io.quarkus.scheduler.Scheduled.ApplicationNotRunning` skip predicate.
+
[[non-blocking-methods]]
=== Non-blocking Methods
diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc
index 7720edb8bb238..cf216a14c8225 100644
--- a/docs/src/main/asciidoc/security-oauth2.adoc
+++ b/docs/src/main/asciidoc/security-oauth2.adoc
@@ -22,8 +22,6 @@ This extension provides a light-weight support for using the opaque Bearer Token
If the OAuth2 Authentication server provides JWT Bearer Tokens, consider using either xref:security-oidc-bearer-token-authentication.adoc[OIDC Bearer token authentication] or xref:security-jwt.adoc[SmallRye JWT] extensions instead.
OpenID Connect extension has to be used if the Quarkus application needs to authenticate the users using OIDC Authorization Code Flow. For more information, see the xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications] guide.
-include::{includes}/extension-status.adoc[]
-
== Solution
We recommend that you follow the instructions in the next sections and create the application step by step.
diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java
index e525994361f12..6d7717e0a8d3f 100644
--- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java
+++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java
@@ -34,7 +34,7 @@ public class CsrfReactiveConfig {
/**
* CSRF cookie max age.
*/
- @ConfigItem(defaultValue = "10M")
+ @ConfigItem(defaultValue = "2H")
public Duration cookieMaxAge;
/**
diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java
index b75764772a912..25df381ac7b59 100644
--- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java
+++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java
@@ -30,6 +30,7 @@ public class CsrfRequestResponseReactiveFilter {
*/
private static final String CSRF_TOKEN_KEY = "csrf_token";
private static final String CSRF_TOKEN_BYTES_KEY = "csrf_token_bytes";
+ private static final String NEW_COOKIE_REQUIRED = "true";
/**
* CSRF token verification status.
@@ -85,12 +86,14 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi
if (requestMethodIsSafe(requestContext)) {
// safe HTTP method, tolerate the absence of a token
- if (cookieToken == null && isCsrfTokenRequired(routing, config)) {
+ if (isCsrfTokenRequired(routing, config)) {
// Set the CSRF cookie with a randomly generated value
byte[] tokenBytes = new byte[config.tokenSize];
secureRandom.nextBytes(tokenBytes);
routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes);
routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes));
+
+ routing.put(NEW_COOKIE_REQUIRED, true);
}
} else if (config.verifyToken) {
// unsafe HTTP method, token is required
@@ -129,7 +132,6 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi
String csrfTokenFormParam = (String) rrContext.getFormParameter(config.formFieldName, true, false);
LOG.debugf("CSRF token found in the form parameter");
verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenFormParam);
- return;
} else if (cookieToken == null) {
LOG.debug("CSRF token is not found");
@@ -159,6 +161,8 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont
} else {
routing.put(CSRF_TOKEN_KEY, csrfToken);
routing.put(CSRF_TOKEN_VERIFIED, true);
+ // reset the cookie
+ routing.put(NEW_COOKIE_REQUIRED, true);
return;
}
}
@@ -195,9 +199,9 @@ private static Response badClientRequest() {
@ServerResponseFilter
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext, RoutingContext routing) {
- final CsrfReactiveConfig config = configInstance.get();
- if (requestContext.getMethod().equals("GET") && isCsrfTokenRequired(routing, config)
- && getCookieToken(routing, config) == null) {
+ if (routing.get(NEW_COOKIE_REQUIRED) != null) {
+
+ final CsrfReactiveConfig config = configInstance.get();
String cookieValue = null;
if (config.tokenSignatureKey.isPresent()) {
@@ -230,7 +234,7 @@ && getCookieToken(routing, config) == null) {
*
* @return An Optional containing the token, or an empty Optional if the token cookie is not present or is invalid
*/
- private String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) {
+ private static String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) {
Cookie cookie = routing.getCookie(config.cookieName);
if (cookie == null) {
@@ -241,14 +245,14 @@ private String getCookieToken(RoutingContext routing, CsrfReactiveConfig config)
return cookie.getValue();
}
- private boolean isCsrfTokenRequired(RoutingContext routing, CsrfReactiveConfig config) {
+ private static boolean isCsrfTokenRequired(RoutingContext routing, CsrfReactiveConfig config) {
return config.createTokenPath
.map(value -> value.contains(routing.normalizedPath())).orElse(true);
}
- private void createCookie(String csrfToken, RoutingContext routing, CsrfReactiveConfig config) {
+ private static void createCookie(String cookieTokenValue, RoutingContext routing, CsrfReactiveConfig config) {
- ServerCookie cookie = new CookieImpl(config.cookieName, csrfToken);
+ ServerCookie cookie = new CookieImpl(config.cookieName, cookieTokenValue);
cookie.setHttpOnly(config.cookieHttpOnly);
cookie.setSecure(config.cookieForceSecure || routing.request().isSSL());
cookie.setMaxAge(config.cookieMaxAge.toSeconds());
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
index 4e66fd21023bb..65831169c0b03 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
@@ -254,14 +254,16 @@ public static Channel createChannel(String name, Set perClientIntercepto
options.setHttp2ClearTextUpgrade(false); // this fixes i30379
if (!plainText) {
+ // always set ssl + alpn for plain-text=false
+ options.setSsl(true);
+ options.setUseAlpn(true);
+
if (config.ssl.trustStore.isPresent()) {
Optional trustStorePath = config.ssl.trustStore;
if (trustStorePath.isPresent()) {
PemTrustOptions to = new PemTrustOptions();
to.addCertValue(bufferFor(trustStorePath.get(), "trust store"));
options.setTrustOptions(to);
- options.setSsl(true);
- options.setUseAlpn(true);
}
Optional certificatePath = config.ssl.certificate;
Optional keyPath = config.ssl.key;
@@ -270,8 +272,6 @@ public static Channel createChannel(String name, Set perClientIntercepto
cko.setCertValue(bufferFor(certificatePath.get(), "certificate"));
cko.setKeyValue(bufferFor(keyPath.get(), "key"));
options.setKeyCertOptions(cko);
- options.setSsl(true);
- options.setUseAlpn(true);
}
}
}
diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java
index dc7b382a921a4..d120cea847a9e 100644
--- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java
+++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java
@@ -25,6 +25,7 @@
import org.jboss.jandex.Type;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
@@ -79,7 +80,7 @@ public class JacksonProcessor {
private static final DotName JSON_AUTO_DETECT = DotName.createSimple(JsonAutoDetect.class.getName());
private static final DotName JSON_TYPE_ID_RESOLVER = DotName.createSimple(JsonTypeIdResolver.class.getName());
-
+ private static final DotName JSON_SUBTYPES = DotName.createSimple(JsonSubTypes.class.getName());
private static final DotName JSON_CREATOR = DotName.createSimple("com.fasterxml.jackson.annotation.JsonCreator");
private static final DotName JSON_NAMING = DotName.createSimple("com.fasterxml.jackson.databind.annotation.JsonNaming");
@@ -98,6 +99,7 @@ public class JacksonProcessor {
// this list can probably be enriched with more modules
private static final List MODULES_NAMES_TO_AUTO_REGISTER = Arrays.asList(TIME_MODULE, JDK8_MODULE,
PARAMETER_NAMES_MODULE);
+ private static final String[] EMPTY_STRING = new String[0];
@Inject
CombinedIndexBuildItem combinedIndexBuildItem;
@@ -266,6 +268,25 @@ void register(
}
}
+ // register @JsonSubTypes.Type values for reflection
+ Set subTypeTypesNames = new HashSet<>();
+ for (AnnotationInstance subTypeInstance : index.getAnnotations(JSON_SUBTYPES)) {
+ AnnotationValue subTypeValue = subTypeInstance.value();
+ if (subTypeValue != null) {
+ for (AnnotationInstance subTypeTypeInstance : subTypeValue.asNestedArray()) {
+ AnnotationValue subTypeTypeValue = subTypeTypeInstance.value();
+ if (subTypeTypeValue != null) {
+ subTypeTypesNames.add(subTypeTypeValue.asClass().name().toString());
+ }
+ }
+
+ }
+ }
+ if (!subTypeTypesNames.isEmpty()) {
+ reflectiveClass.produce(ReflectiveClassBuildItem.builder(subTypeTypesNames.toArray(EMPTY_STRING))
+ .methods().fields().build());
+ }
+
// this needs to be registered manually since the runtime module is not indexed by Jandex
additionalBeans.produce(new AdditionalBeanBuildItem(ObjectMapperProducer.class));
}
diff --git a/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java b/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java
index b2ed32c390f8b..18c452474e5f7 100644
--- a/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java
+++ b/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java
@@ -12,6 +12,7 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.AutoAddScopeBuildItem;
+import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.deployment.Feature;
@@ -76,6 +77,7 @@ IndexDependencyBuildItem picocliIndexDependency() {
void picocliRunner(ApplicationIndexBuildItem applicationIndex,
CombinedIndexBuildItem combinedIndex,
BuildProducer additionalBean,
+ BuildProducer unremovableBean,
BuildProducer quarkusApplicationClass,
BuildProducer annotationsTransformer) {
IndexView index = combinedIndex.getIndex();
@@ -99,6 +101,17 @@ void picocliRunner(ApplicationIndexBuildItem applicationIndex,
additionalBean.produce(AdditionalBeanBuildItem.unremovableOf(DefaultPicocliCommandLineFactory.class));
quarkusApplicationClass.produce(new QuarkusApplicationClassBuildItem(PicocliRunner.class));
}
+
+ // Make all classes that can be instantiated by IFactory unremovable
+ unremovableBean.produce(UnremovableBeanBuildItem.beanTypes(CommandLine.ITypeConverter.class,
+ CommandLine.IVersionProvider.class,
+ CommandLine.IModelTransformer.class,
+ CommandLine.IModelTransformer.class,
+ CommandLine.IDefaultValueProvider.class,
+ CommandLine.IParameterConsumer.class,
+ CommandLine.IParameterPreprocessor.class,
+ CommandLine.INegatableOptionTransformer.class,
+ CommandLine.IHelpFactory.class));
}
private List classesAnnotatedWith(IndexView indexView, String annotationClassName) {
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java
index d4e1238aadc60..68bd47c204deb 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java
@@ -9,17 +9,20 @@
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.container.CompletionCallback;
import jakarta.ws.rs.container.ConnectionCallback;
+import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader;
import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;
+import org.jboss.resteasy.reactive.server.jaxrs.HttpHeadersImpl;
import org.jboss.resteasy.reactive.server.spi.ContentType;
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse;
@@ -266,6 +269,11 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() {
return null;
}
+ @Override
+ public HttpHeaders getRequestHeaders() {
+ return new HttpHeadersImpl(Collections.emptyList());
+ }
+
@Override
public void abortWith(Response response) {
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java
index 36cca2e23779e..12c47625797e5 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java
@@ -108,6 +108,22 @@ public void testParser() {
testParser(Arrays.asList("data:f", "oo\n\n"),
Collections.singletonList(new InboundSseEventImpl(null, null)
.setData("foo")));
+ testParser(Arrays.asList("dat", "a:foo\n\n"),
+ Collections.singletonList(new InboundSseEventImpl(null, null)
+ .setData("foo")));
+ testParser(Arrays.asList("data", ":foo\n\n"),
+ Collections.singletonList(new InboundSseEventImpl(null, null)
+ .setData("foo")));
+ testParser(Arrays.asList("data:", "foo\n\n"),
+ Collections.singletonList(new InboundSseEventImpl(null, null)
+ .setData("foo")));
+ // chunk at the worst possible place, make sure we don't drop events
+ testParser(Arrays.asList("data:foo\n", "\n"),
+ Collections.singletonList(new InboundSseEventImpl(null, null)
+ .setData("foo")));
+ testParser(Arrays.asList("data:foo\n", "data:bar\n", "\n"),
+ Collections.singletonList(new InboundSseEventImpl(null, null)
+ .setData("foo\nbar")));
// one event in two buffers within a UTF-8 char
testParserWithBytes(
Arrays.asList(new byte[] { 'd', 'a', 't', 'a', ':', (byte) 0b11000010 },
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java
new file mode 100644
index 0000000000000..13b3ce8eb68da
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java
@@ -0,0 +1,42 @@
+package io.quarkus.resteasy.reactive.server.deployment;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+public final class BuiltInReaderOverrideBuildItem extends MultiBuildItem {
+
+ private final String readerClassName;
+ private final String overrideClassName;
+
+ public BuiltInReaderOverrideBuildItem(String readerClassName, String overrideClassName) {
+ this.readerClassName = readerClassName;
+ this.overrideClassName = overrideClassName;
+ }
+
+ public String getReaderClassName() {
+ return readerClassName;
+ }
+
+ public String getOverrideClassName() {
+ return overrideClassName;
+ }
+
+ public static Map toMap(List items) {
+ if (items.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map result = new HashMap<>();
+ for (BuiltInReaderOverrideBuildItem item : items) {
+ String previousOverride = result.put(item.getReaderClassName(), item.getOverrideClassName());
+ if (previousOverride != null) {
+ throw new IllegalStateException(
+ "Providing multiple BuiltInReaderOverrideBuildItem for the same readerClassName is not supported");
+ }
+ }
+ return result;
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
index 2b018c4247b58..26a848224c7ed 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
@@ -12,6 +12,7 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -110,6 +111,7 @@
import org.jboss.resteasy.reactive.server.processor.scanning.ResponseHeaderMethodScanner;
import org.jboss.resteasy.reactive.server.processor.scanning.ResponseStatusMethodScanner;
import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames;
+import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFileBodyHandler;
import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyAsyncFileMessageBodyWriter;
@@ -165,6 +167,8 @@
import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem;
import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig;
import io.quarkus.resteasy.reactive.server.EndpointDisabled;
+import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler;
+import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder;
@@ -1020,6 +1024,16 @@ private static String determineHandledGenericTypeOfProviderInterface(Class> pr
}
}
+ @BuildStep
+ public void fileHandling(BuildProducer overrideProducer,
+ BuildProducer readerProducer) {
+ overrideProducer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(),
+ QuarkusServerFileBodyHandler.class.getName()));
+ readerProducer.produce(
+ new MessageBodyReaderBuildItem(QuarkusServerPathBodyHandler.class.getName(), Path.class.getName(), List.of(
+ MediaType.WILDCARD), RuntimeType.SERVER, true, Priorities.USER));
+ }
+
@BuildStep
@Record(value = ExecutionTime.STATIC_INIT, useIdentityComparisonForParameters = false)
public void serverSerializers(ResteasyReactiveRecorder recorder,
@@ -1029,6 +1043,7 @@ public void serverSerializers(ResteasyReactiveRecorder recorder,
List additionalMessageBodyWriters,
List messageBodyReaderOverrideBuildItems,
List messageBodyWriterOverrideBuildItems,
+ List builtInReaderOverrideBuildItems,
BuildProducer reflectiveClass,
BuildProducer serverSerializersProducer) {
@@ -1046,11 +1061,16 @@ public void serverSerializers(ResteasyReactiveRecorder recorder,
reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinWriter.writerClass.getName())
.build());
}
+ Map builtInReaderOverrides = BuiltInReaderOverrideBuildItem.toMap(builtInReaderOverrideBuildItems);
for (Serialisers.BuiltinReader builtinReader : ServerSerialisers.BUILTIN_READERS) {
- registerReader(recorder, serialisers, builtinReader.entityClass.getName(), builtinReader.readerClass.getName(),
+ String effectiveReaderClassName = builtinReader.readerClass.getName();
+ if (builtInReaderOverrides.containsKey(effectiveReaderClassName)) {
+ effectiveReaderClassName = builtInReaderOverrides.get(effectiveReaderClassName);
+ }
+ registerReader(recorder, serialisers, builtinReader.entityClass.getName(), effectiveReaderClassName,
beanContainerBuildItem.getValue(),
builtinReader.mediaType, builtinReader.constraint);
- reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinReader.readerClass.getName())
+ reflectiveClass.produce(ReflectiveClassBuildItem.builder(effectiveReaderClassName)
.build());
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java
index 86348aa16e4b8..9bcb1a8dd1d7c 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java
@@ -3,6 +3,8 @@
import static org.awaitility.Awaitility.await;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.concurrent.Callable;
@@ -40,4 +42,8 @@ public Boolean call() {
}
});
}
+
+ protected String fileSizeAsStr(File file) throws IOException {
+ return "" + Files.readAllBytes(file.toPath()).length;
+ }
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java
new file mode 100644
index 0000000000000..a3d43bad36ab3
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java
@@ -0,0 +1,80 @@
+package io.quarkus.resteasy.reactive.server.test.multipart;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class FileInputWithDeleteTest extends AbstractMultipartTest {
+
+ private static final java.nio.file.Path uploadDir = Paths.get("file-uploads");
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .setArchiveProducer(new Supplier<>() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(Resource.class)
+ .addAsResource(new StringAsset(
+ "quarkus.http.body.uploads-directory="
+ + uploadDir.toString() + "\n"),
+ "application.properties");
+ }
+
+ });
+
+ private final File HTML_FILE = new File("./src/test/resources/test.html");
+ private final File HTML_FILE2 = new File("./src/test/resources/test2.html");
+
+ @Test
+ public void test() throws IOException {
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE)));
+
+ awaitUploadDirectoryToEmpty(uploadDir);
+
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE2)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE2)));
+
+ awaitUploadDirectoryToEmpty(uploadDir);
+ }
+
+ @Path("test")
+ public static class Resource {
+
+ @POST
+ @Consumes("application/octet-stream")
+ public long size(File file) throws IOException {
+ return Files.size(file.toPath());
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java
new file mode 100644
index 0000000000000..318b8602e9167
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java
@@ -0,0 +1,96 @@
+package io.quarkus.resteasy.reactive.server.test.multipart;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class FileInputWithoutDeleteTest extends AbstractMultipartTest {
+
+ private static final java.nio.file.Path uploadDir = Paths.get("file-uploads");
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .setArchiveProducer(new Supplier<>() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(Resource.class)
+ .addAsResource(new StringAsset(
+ // keep the files around so we can assert the outcome
+ "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory="
+ + uploadDir.toString() + "\n"),
+ "application.properties");
+ }
+
+ });
+
+ private final File HTML_FILE = new File("./src/test/resources/test.html");
+ private final File HTML_FILE2 = new File("./src/test/resources/test2.html");
+
+ @BeforeEach
+ public void assertEmptyUploads() {
+ Assertions.assertTrue(isDirectoryEmpty(uploadDir));
+ }
+
+ @AfterEach
+ public void clearDirectory() {
+ clearDirectory(uploadDir);
+ }
+
+ @Test
+ public void test() throws IOException {
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE)));
+
+ // ensure that the 3 uploaded files where created on disk
+ Assertions.assertEquals(1, uploadDir.toFile().listFiles().length);
+
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE2)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE2)));
+
+ // ensure that the 3 uploaded files where created on disk
+ Assertions.assertEquals(2, uploadDir.toFile().listFiles().length);
+ }
+
+ @Path("test")
+ public static class Resource {
+
+ @POST
+ @Consumes("application/octet-stream")
+ public long size(File file) throws IOException {
+ return Files.size(file.toPath());
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java
index f75e0a416573d..9b371cb02996e 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java
@@ -5,7 +5,6 @@
import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
@@ -164,8 +163,4 @@ private String filePath(File file) {
return file.toPath().toAbsolutePath().toString();
}
- private String fileSizeAsStr(File file) throws IOException {
- return "" + Files.readAllBytes(file.toPath()).length;
- }
-
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java
index 3b3bc76ad6b54..e29c3791fda69 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java
@@ -5,7 +5,6 @@
import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Supplier;
@@ -245,8 +244,4 @@ private String filePath(File file) {
return file.toPath().toAbsolutePath().toString();
}
- private String fileSizeAsStr(File file) throws IOException {
- return "" + Files.readAllBytes(file.toPath()).length;
- }
-
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java
new file mode 100644
index 0000000000000..b03d853f2fcab
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java
@@ -0,0 +1,80 @@
+package io.quarkus.resteasy.reactive.server.test.multipart;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class PathInputWithDeleteTest extends AbstractMultipartTest {
+
+ private static final java.nio.file.Path uploadDir = Paths.get("file-uploads");
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .setArchiveProducer(new Supplier<>() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(Resource.class)
+ .addAsResource(new StringAsset(
+ "quarkus.http.body.uploads-directory="
+ + uploadDir.toString() + "\n"),
+ "application.properties");
+ }
+
+ });
+
+ private final File HTML_FILE = new File("./src/test/resources/test.html");
+ private final File HTML_FILE2 = new File("./src/test/resources/test2.html");
+
+ @Test
+ public void test() throws IOException {
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE)));
+
+ awaitUploadDirectoryToEmpty(uploadDir);
+
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE2)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE2)));
+
+ awaitUploadDirectoryToEmpty(uploadDir);
+ }
+
+ @Path("test")
+ public static class Resource {
+
+ @POST
+ @Consumes("application/octet-stream")
+ public long size(java.nio.file.Path file) throws IOException {
+ return Files.size(file);
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java
new file mode 100644
index 0000000000000..08b7f7181da6b
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java
@@ -0,0 +1,96 @@
+package io.quarkus.resteasy.reactive.server.test.multipart;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class PathInputWithoutDeleteTest extends AbstractMultipartTest {
+
+ private static final java.nio.file.Path uploadDir = Paths.get("file-uploads");
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .setArchiveProducer(new Supplier<>() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(Resource.class)
+ .addAsResource(new StringAsset(
+ // keep the files around so we can assert the outcome
+ "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory="
+ + uploadDir.toString() + "\n"),
+ "application.properties");
+ }
+
+ });
+
+ private final File HTML_FILE = new File("./src/test/resources/test.html");
+ private final File HTML_FILE2 = new File("./src/test/resources/test2.html");
+
+ @BeforeEach
+ public void assertEmptyUploads() {
+ Assertions.assertTrue(isDirectoryEmpty(uploadDir));
+ }
+
+ @AfterEach
+ public void clearDirectory() {
+ clearDirectory(uploadDir);
+ }
+
+ @Test
+ public void test() throws IOException {
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE)));
+
+ // ensure that the 3 uploaded files where created on disk
+ Assertions.assertEquals(1, uploadDir.toFile().listFiles().length);
+
+ RestAssured.given()
+ .contentType("application/octet-stream")
+ .body(HTML_FILE2)
+ .when()
+ .post("/test")
+ .then()
+ .statusCode(200)
+ .body(equalTo(fileSizeAsStr(HTML_FILE2)));
+
+ // ensure that the 3 uploaded files where created on disk
+ Assertions.assertEquals(2, uploadDir.toFile().listFiles().length);
+ }
+
+ @Path("test")
+ public static class Resource {
+
+ @POST
+ @Consumes("application/octet-stream")
+ public long size(java.nio.file.Path file) throws IOException {
+ return Files.size(file);
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java
new file mode 100644
index 0000000000000..e8c8effc7300a
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java
@@ -0,0 +1,55 @@
+package io.quarkus.resteasy.reactive.server.runtime;
+
+import static io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler.createFile;
+import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX;
+import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler;
+import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
+import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
+
+public class QuarkusServerFileBodyHandler implements ServerMessageBodyReader {
+
+ private static final Logger log = Logger.getLogger(QuarkusServerFileBodyHandler.class);
+
+ @Override
+ public boolean isReadable(Class> type, Type genericType, ResteasyReactiveResourceInfo lazyMethod,
+ MediaType mediaType) {
+ return File.class.equals(type);
+ }
+
+ @Override
+ public File readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context)
+ throws WebApplicationException, IOException {
+ Path file = createFile(context);
+ return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile());
+ }
+
+ @Override
+ public boolean isReadable(Class> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return File.class.equals(type);
+ }
+
+ @Override
+ public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap httpHeaders, InputStream entityStream)
+ throws IOException, WebApplicationException {
+ // unfortunately we don't do much here to avoid the file leak
+ // however this should never be called in a real world scenario
+ return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile());
+ }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java
new file mode 100644
index 0000000000000..e6dcb8ff7dfa9
--- /dev/null
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java
@@ -0,0 +1,96 @@
+package io.quarkus.resteasy.reactive.server.runtime;
+
+import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX;
+import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.container.CompletionCallback;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler;
+import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
+import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration;
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
+import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
+
+public class QuarkusServerPathBodyHandler implements ServerMessageBodyReader {
+
+ private static final Logger log = Logger.getLogger(QuarkusServerPathBodyHandler.class);
+
+ @Override
+ public boolean isReadable(Class> type, Type genericType, ResteasyReactiveResourceInfo lazyMethod,
+ MediaType mediaType) {
+ return Path.class.equals(type);
+ }
+
+ @Override
+ public Path readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context)
+ throws WebApplicationException, IOException {
+ Path file = createFile(context);
+ return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile())
+ .toPath();
+ }
+
+ @Override
+ public boolean isReadable(Class> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return File.class.equals(type);
+ }
+
+ @Override
+ public Path readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap httpHeaders, InputStream entityStream)
+ throws IOException, WebApplicationException {
+ // unfortunately we don't do much here to avoid the file leak
+ // however this should never be called in a real world scenario
+ return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()).toPath();
+ }
+
+ static Path createFile(ServerRequestContext context) throws IOException {
+ RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment()
+ .getRuntimeConfiguration().body();
+ boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd();
+ String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory();
+ Path uploadDirectory = Paths.get(uploadsDirectoryStr);
+ try {
+ Files.createDirectories(uploadDirectory);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX);
+ if (deleteUploadedFilesOnEnd) {
+ context.registerCompletionCallback(new CompletionCallback() {
+ @Override
+ public void onComplete(Throwable throwable) {
+ ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() {
+ @Override
+ public void run() {
+ if (Files.exists(file)) {
+ try {
+ Files.delete(file);
+ } catch (NoSuchFileException e) { // ignore
+ } catch (IOException e) {
+ log.error("Cannot remove uploaded file " + file, e);
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+ return file;
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java
index 664dea477ef7d..baa2e8d3771d5 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java
@@ -1,7 +1,11 @@
package io.quarkus.rest.client.reactive.stork;
+import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@@ -10,4 +14,13 @@
public interface HelloClient {
@GET
String hello();
+
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Path("/")
+ String echo(String name);
+
+ @GET
+ @Path("/{name}")
+ public String helloWithPathParam(@PathParam("name") String name);
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java
index e9966a8d8eac6..1a544e2ab878e 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java
@@ -1,7 +1,13 @@
package io.quarkus.rest.client.reactive.stork;
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Request;
@Path("/hello")
public class HelloResource {
@@ -12,4 +18,16 @@ public class HelloResource {
public String hello() {
return HELLO_WORLD;
}
+
+ @GET
+ @Path("/{name}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String invoke(@PathParam("name") String name) {
+ return "Hello, " + name;
+ }
+
+ @POST
+ public String echo(String name, @Context Request request) {
+ return "hello, " + name;
+ }
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java
index 129b7aece4cda..51f11c1b539ca 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java
@@ -4,6 +4,7 @@
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@@ -22,6 +23,18 @@ public String invokeClient() {
return client.hello();
}
+ @Path("/v2/{name}")
+ @GET
+ public String invokeClientWithPathParamContainingSlash(@PathParam("name") String name) {
+ return client.helloWithPathParam(name + "/" + name);
+ }
+
+ @Path("/{name}")
+ @GET
+ public String invokeClientWithPathParam(@PathParam("name") String name) {
+ return client.helloWithPathParam(name);
+ }
+
@Path("/cdi")
@GET
public String invokeCdiClient() {
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java
index f30d13b937008..5a12b520c497b 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java
@@ -67,4 +67,25 @@ void shouldModifyStorkSettings() {
.body(equalTo(WIREMOCK_RESPONSE));
// @formatter:on
}
+
+ @Test
+ void shouldSayHelloNameWithSlash() {
+ when()
+ .get("/helper/v2/stork")
+ .then()
+ .statusCode(200)
+ // The response contains an encoded `/`
+ .body(equalTo("Hello, stork/stork"));
+
+ }
+
+ @Test
+ void shouldSayHelloNameWithBlank() {
+ when()
+ .get("/helper/smallrye stork")
+ .then()
+ .statusCode(200)
+ // The response contains an encoded blank espace
+ .body(equalTo("Hello, smallrye stork"));
+ }
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java
index cb22c1393db59..639ae39cd8fac 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java
@@ -15,8 +15,6 @@
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.rest.client.reactive.HelloClient2;
-import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.stork.api.NoSuchServiceDefinitionException;
@@ -24,45 +22,58 @@ public class StorkIntegrationTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
- .addClasses(HelloClient2.class, HelloResource.class))
+ .addClasses(HelloClient.class, HelloResource.class))
.withConfigurationResource("stork-application.properties");
@RestClient
- HelloClient2 client;
+ HelloClient client;
@Test
void shouldDetermineUrlViaStork() {
String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello"))
- .build(HelloClient2.class)
+ .build(HelloClient.class)
.echo("black and white bird");
assertThat(greeting).isEqualTo("hello, black and white bird");
+
+ greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello"))
+ .build(HelloClient.class)
+ .helloWithPathParam("black and white bird");
+ assertThat(greeting).isEqualTo("Hello, black and white bird");
}
@Test
void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException {
- String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request().get(String.class);
- assertThat(greeting).isEqualTo("Hello");
+ String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request()
+ .get(String.class);
+ assertThat(greeting).isEqualTo("Hello, World!");
greeting = ClientBuilder.newClient().target(new URI("stork://hello-service/hello")).request().get(String.class);
- assertThat(greeting).isEqualTo("Hello");
+ assertThat(greeting).isEqualTo("Hello, World!");
greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/hello")).request()
.get(String.class);
- assertThat(greeting).isEqualTo("Hello");
+ assertThat(greeting).isEqualTo("Hello, World!");
+
+ greeting = ClientBuilder.newClient().target("stork://hello-service/hello").path("big bird").request()
+ .get(String.class);
+ assertThat(greeting).isEqualTo("Hello, big bird");
}
@Test
void shouldDetermineUrlViaStorkCDI() {
String greeting = client.echo("big bird");
assertThat(greeting).isEqualTo("hello, big bird");
+
+ greeting = client.helloWithPathParam("big bird");
+ assertThat(greeting).isEqualTo("Hello, big bird");
}
@Test
@Timeout(20)
void shouldFailOnUnknownService() {
- HelloClient2 client2 = RestClientBuilder.newBuilder()
+ HelloClient client = RestClientBuilder.newBuilder()
.baseUri(URI.create("stork://nonexistent-service"))
- .build(HelloClient2.class);
- assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
+ .build(HelloClient.class);
+ assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
}
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java
index 507ca9eb31b1a..9dc52a8d0d271 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java
@@ -16,8 +16,6 @@
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
-import io.quarkus.rest.client.reactive.HelloClient2;
-import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;
public class StorkResponseTimeLoadBalancerTest {
@@ -28,7 +26,7 @@ public class StorkResponseTimeLoadBalancerTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
- .addClasses(HelloClient2.class, HelloResource.class))
+ .addClasses(HelloClient.class, HelloResource.class))
.withConfigurationResource("stork-stat-lb.properties");
@BeforeAll
@@ -46,7 +44,7 @@ public static void shutDown() {
}
@RestClient
- HelloClient2 client;
+ HelloClient client;
@Test
void shouldUseFasterService() {
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java
index 26ba43279cbae..26ac15b363f45 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java
@@ -15,8 +15,6 @@
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.rest.client.reactive.HelloClient2;
-import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.stork.api.NoSuchServiceDefinitionException;
@@ -24,45 +22,57 @@ public class StorkWithPathIntegrationTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
- .addClasses(HelloClient2.class, HelloResource.class))
+ .addClasses(HelloClient.class, HelloResource.class))
.withConfigurationResource("stork-application-with-path.properties");
@RestClient
- HelloClient2 client;
+ HelloClient client;
@Test
void shouldDetermineUrlViaStork() {
String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service"))
- .build(HelloClient2.class)
+ .build(HelloClient.class)
.echo("black and white bird");
assertThat(greeting).isEqualTo("hello, black and white bird");
+
+ greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service"))
+ .build(HelloClient.class)
+ .helloWithPathParam("black and white bird");
+ assertThat(greeting).isEqualTo("Hello, black and white bird");
}
@Test
void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException {
String greeting = ClientBuilder.newClient().target("stork://hello-service").request().get(String.class);
- assertThat(greeting).isEqualTo("Hello");
+ assertThat(greeting).isEqualTo("Hello, World!");
greeting = ClientBuilder.newClient().target(new URI("stork://hello-service")).request().get(String.class);
- assertThat(greeting).isEqualTo("Hello");
+ assertThat(greeting).isEqualTo("Hello, World!");
greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/")).request()
.get(String.class);
- assertThat(greeting).isEqualTo("Hello");
+ assertThat(greeting).isEqualTo("Hello, World!");
+
+ greeting = ClientBuilder.newClient().target("stork://hello-service/").path("big bird").request()
+ .get(String.class);
+ assertThat(greeting).isEqualTo("Hello, big bird");
}
@Test
void shouldDetermineUrlViaStorkCDI() {
String greeting = client.echo("big bird");
assertThat(greeting).isEqualTo("hello, big bird");
+
+ greeting = client.helloWithPathParam("big bird");
+ assertThat(greeting).isEqualTo("Hello, big bird");
}
@Test
@Timeout(20)
void shouldFailOnUnknownService() {
- HelloClient2 client2 = RestClientBuilder.newBuilder()
+ HelloClient client = RestClientBuilder.newBuilder()
.baseUri(URI.create("stork://nonexistent-service"))
- .build(HelloClient2.class);
- assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
+ .build(HelloClient.class);
+ assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class);
}
}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java
index 40fa135373900..02d3a1bf5deb5 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java
@@ -11,28 +11,38 @@ public class PortSystemProperties {
public void set(String subProperty, int actualPort, LaunchMode launchMode) {
String portPropertyValue = String.valueOf(actualPort);
- //we always set the .port property, even if we are in test mode, so this will always
- //reflect the current port
String portPropertyName = "quarkus." + subProperty + ".port";
- String prevPortPropertyValue = System.setProperty(portPropertyName, portPropertyValue);
- if (!Objects.equals(prevPortPropertyValue, portPropertyValue)) {
- portPropertiesToRestore.put(portPropertyName, prevPortPropertyValue);
+ String testPropName = "quarkus." + subProperty + ".test-port";
+
+ set(portPropertyName, testPropName, portPropertyValue, launchMode);
+ //if subProperty is "https", the correct properties are not quarkus.https.port and quarkus.https.test-port
+ //but quarkus.http.ssl-port and quarkus.http.test-ssl-port
+ //the incorrect properties are still set for backward compatibility with code that works around the incorrect
+ //names
+ if ("https".equals(subProperty)) {
+ set("quarkus.http.ssl-port", "quarkus.http.test-ssl-port", portPropertyValue, launchMode);
}
+ }
+
+ private void set(String portPropertyName, String testPropName, String portPropertyValue, LaunchMode launchMode) {
+ //we always set the .port property, even if we are in test mode, so this will always
+ //reflect the current port
+ set(portPropertyName, portPropertyValue);
if (launchMode == LaunchMode.TEST) {
//we also set the test-port property in a test
- String testPropName = "quarkus." + subProperty + ".test-port";
- String prevTestPropPrevValue = System.setProperty(testPropName, portPropertyValue);
- if (!Objects.equals(prevTestPropPrevValue, portPropertyValue)) {
- portPropertiesToRestore.put(testPropName, prevTestPropPrevValue);
- }
+ set(testPropName, portPropertyValue);
}
if (launchMode.isDevOrTest()) {
// set the profile property as well to make sure we don't have any inconsistencies
portPropertyName = "%" + launchMode.getDefaultProfile() + "." + portPropertyName;
- prevPortPropertyValue = System.setProperty(portPropertyName, portPropertyValue);
- if (!Objects.equals(prevPortPropertyValue, portPropertyValue)) {
- portPropertiesToRestore.put(portPropertyName, prevPortPropertyValue);
- }
+ set(portPropertyName, portPropertyValue);
+ }
+ }
+
+ private void set(String propertyName, String propertyValue) {
+ String prevPropertyValue = System.setProperty(propertyName, propertyValue);
+ if (!Objects.equals(prevPropertyValue, propertyValue)) {
+ portPropertiesToRestore.put(propertyName, prevPropertyValue);
}
}
diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java
index ecc3d790b0529..2db16e1fa057f 100644
--- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java
+++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java
@@ -5,12 +5,12 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
-import java.util.logging.Handler;
import java.util.logging.LogRecord;
+import org.jboss.logmanager.ExtHandler;
import org.jboss.logmanager.formatters.PatternFormatter;
-public class InMemoryLogHandler extends Handler {
+public class InMemoryLogHandler extends ExtHandler {
private static final PatternFormatter FORMATTER = new PatternFormatter("%X{requestId} ### %s");
private static final List recordList = new CopyOnWriteArrayList<>();
diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml
index 5cf3128c08d0c..9e02257fdec6c 100644
--- a/independent-projects/arc/pom.xml
+++ b/independent-projects/arc/pom.xml
@@ -55,7 +55,7 @@
3.24.2
5.10.1
- 1.9.21
+ 1.9.22
1.7.3
5.7.0
diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml
index ad9f2f590dadf..c4cc6b630e003 100644
--- a/independent-projects/bootstrap/pom.xml
+++ b/independent-projects/bootstrap/pom.xml
@@ -67,7 +67,7 @@
1.0.1
2.8
1.2.6
- 3.0.2.Final
+ 3.0.4.Final
1.1.0.Final
1.7.36
23.1.0
diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java
index 46bb82858514a..45db7ae4ac6fb 100644
--- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java
+++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java
@@ -40,7 +40,10 @@ public class SseParser implements Handler {
* True if we're at the very beginning of the data stream and could see a BOM
*/
private boolean firstByte = true;
-
+ /**
+ * True if we've started to read at least one byte of an event
+ */
+ private boolean startedEvent = false;
/**
* The event type we're reading. Defaults to "message" and changes with "event" fields
*/
@@ -95,6 +98,7 @@ public void handle(Buffer event) {
while (hasByte()) {
boolean lastFirstByte = firstByte;
+ startedEvent = false;
nameBuffer.setLength(0);
valueBuffer.setLength(0);
commentBuffer.setLength(0);
@@ -105,10 +109,19 @@ public void handle(Buffer event) {
eventReconnectTime = SseEvent.RECONNECT_NOT_SET;
// SSE spec says ID is persistent
+ boolean needsMoreData = false;
int lastEventStart = i;
try {
parseEvent();
+ // if we started an event but did not fire it, it means we lacked a final end-of-line and must
+ // wait for more data
+ if (startedEvent) {
+ needsMoreData = true;
+ }
} catch (NeedsMoreDataException x) {
+ needsMoreData = true;
+ }
+ if (needsMoreData) {
// save the remaining bytes for later
i = lastEventStart;
// be ready to rescan the BOM, but only if we didn't already see it in a previous event
@@ -133,8 +146,10 @@ private void parseEvent() {
int c = readChar();
firstByte = false;
if (c == COLON) {
+ startedEvent = true;
parseComment();
} else if (isNameChar(c)) {
+ startedEvent = true;
parseField(c);
} else if (isEofWithSideEffect(c)) {
dispatchEvent();
@@ -164,6 +179,8 @@ private void dispatchEvent() {
event.setReconnectDelay(eventReconnectTime);
event.setMediaType(contentType != null ? MediaType.valueOf(contentType) : null);
sseEventSource.fireEvent(event);
+ // make sure we mark that we are done with this event
+ startedEvent = false;
}
private byte peekByte() {
diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java
index 7083d96803940..60990009a9d88 100644
--- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java
+++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java
@@ -7,6 +7,7 @@
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.ext.Provider;
import org.jboss.logging.Logger;
@@ -62,7 +63,7 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
}
// Service instance can also contain an optional path.
Optional path = instance.getPath();
- String actualPath = uri.getPath();
+ String actualPath = uri.getRawPath();
if (path.isPresent()) {
var p = path.get();
if (!p.startsWith("/")) {
@@ -79,11 +80,12 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) {
}
}
}
-
+ //To avoid the path double encoding we create uri with path=null and set the path after
URI newUri = new URI(scheme,
uri.getUserInfo(), host, port,
- actualPath, uri.getQuery(), uri.getFragment());
- requestContext.setUri(newUri);
+ null, uri.getQuery(), uri.getFragment());
+ URI build = UriBuilder.fromUri(newUri).path(actualPath).build();
+ requestContext.setUri(build);
if (measureTime && instance.gatherStatistics()) {
requestContext.setCallStatsCollector(instance);
}
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 0f4dca20e286b..b8dedcde357e2 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
@@ -1623,6 +1623,9 @@ protected void warnAboutMissingJsonProviderIfNeeded(ResourceMethod method, Metho
DefaultProducesHandler jsonDefaultProducersHandler,
DefaultProducesHandler.Context context) {
if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson(jsonDefaultProducersHandler, context))) {
+ if (STRING.toString().equals(method.getSimpleReturnType())) { // when returning string, we assume that the method implementation is actually handling to conversion
+ return;
+ }
boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders());
boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters());
if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) {
diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java
index 82d2045179024..1f502c66555a2 100644
--- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java
+++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java
@@ -21,8 +21,8 @@
import org.jboss.resteasy.reactive.common.headers.HeaderUtil;
public class FileBodyHandler implements MessageBodyReader, MessageBodyWriter {
- protected static final String PREFIX = "pfx";
- protected static final String SUFFIX = "sfx";
+ public static final String PREFIX = "pfx";
+ public static final String SUFFIX = "sfx";
@Override
public boolean isReadable(Class> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
@@ -33,16 +33,20 @@ public boolean isReadable(Class> type, Type genericType, Annotation[] annotati
public File readFrom(Class type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap httpHeaders, InputStream entityStream) throws IOException {
- File downloadedFile = Files.createTempFile(PREFIX, SUFFIX).toFile();
+ return doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile());
+ }
+
+ public static File doRead(MultivaluedMap httpHeaders, InputStream entityStream,
+ File file) throws IOException {
if (HeaderUtil.isContentLengthZero(httpHeaders)) {
- return downloadedFile;
+ return file;
}
- try (OutputStream output = new BufferedOutputStream(new FileOutputStream(downloadedFile))) {
+ try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) {
entityStream.transferTo(output);
}
- return downloadedFile;
+ return file;
}
public boolean isWriteable(Class> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml
index 5ce2d22175a19..f91324942e673 100644
--- a/independent-projects/resteasy-reactive/pom.xml
+++ b/independent-projects/resteasy-reactive/pom.xml
@@ -54,6 +54,7 @@
3.9.6
3.24.2
3.5.3.Final
+ 3.0.4.Final
2.1.1
1.7.0
3.1.0
@@ -295,6 +296,18 @@
${jboss-logging.version}
+
+ org.jboss.logmanager
+ jboss-logmanager
+ ${jboss-logmanager.version}
+
+
+ *
+ *
+
+
+
+
jakarta.annotation
jakarta.annotation-api
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 2248a8f7763ac..4fd59f33828db 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
@@ -19,6 +19,7 @@
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.GenericEntity;
+import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.Request;
@@ -148,6 +149,11 @@ public ResteasyReactiveRequestContext(Deployment deployment,
@Override
public abstract ServerHttpResponse serverResponse();
+ @Override
+ public HttpHeaders getRequestHeaders() {
+ return getHttpHeaders();
+ }
+
public Deployment getDeployment() {
return deployment;
}
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java
index 50fba8210e0da..4825de1472c11 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java
@@ -3,6 +3,7 @@
import java.io.InputStream;
import java.io.OutputStream;
+import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@@ -22,5 +23,7 @@ public interface ServerRequestContext extends ResteasyReactiveCallbackContext {
ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo();
+ HttpHeaders getRequestHeaders();
+
void abortWith(Response response);
}
diff --git a/independent-projects/resteasy-reactive/server/vertx/pom.xml b/independent-projects/resteasy-reactive/server/vertx/pom.xml
index 4e4ea50e5e650..6149a3d62dc5b 100644
--- a/independent-projects/resteasy-reactive/server/vertx/pom.xml
+++ b/independent-projects/resteasy-reactive/server/vertx/pom.xml
@@ -100,6 +100,12 @@
jboss-logging
+
+ org.jboss.logmanager
+ jboss-logmanager
+ test
+
+
diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java
index b1dde96f45bc1..274570a6883c2 100644
--- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java
+++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java
@@ -3,11 +3,12 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
-import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
-public class InMemoryLogHandler extends Handler {
+import org.jboss.logmanager.ExtHandler;
+
+public class InMemoryLogHandler extends ExtHandler {
private final Predicate predicate;
diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java
index 770d36441e300..a8a937e1906a8 100644
--- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java
+++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java
@@ -41,6 +41,8 @@ public void testCsrfTokenInForm() throws Exception {
try (final WebClient webClient = createWebClient()) {
webClient.addRequestHeader("Authorization", basicAuth("alice", "alice"));
HtmlPage htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenForm");
+ htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenForm");
+ assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie"));
assertEquals("CSRF Token Form Test", htmlPage.getTitleText());
@@ -51,11 +53,19 @@ public void testCsrfTokenInForm() throws Exception {
assertNotNull(webClient.getCookieManager().getCookie("csrftoken"));
TextPage textPage = loginForm.getInputByName("submit").click();
-
+ assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie"));
assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent());
+ // This request which returns String is not CSRF protected
textPage = webClient.getPage("http://localhost:8081/service/hello");
assertEquals("hello", textPage.getContent());
+ // therefore no Set-Cookie header is expected
+ assertNull(textPage.getWebResponse().getResponseHeaderValue("Set-Cookie"));
+
+ // Repeat a form submission
+ textPage = loginForm.getInputByName("submit").click();
+ assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie"));
+ assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent());
webClient.getCookieManager().clearCookies();
}
diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts
index 5e3808937ce3e..5e4ee3b8fe2e4 100644
--- a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts
+++ b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts
@@ -1,6 +1,6 @@
plugins {
- kotlin("jvm") version "1.9.21"
- kotlin("plugin.allopen") version "1.9.21"
+ kotlin("jvm") version "1.9.22"
+ kotlin("plugin.allopen") version "1.9.22"
id("io.quarkus")
}
diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java
new file mode 100644
index 0000000000000..ebffa6018819c
--- /dev/null
+++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java
@@ -0,0 +1,29 @@
+package io.quarkus.it.jackson;
+
+import java.util.List;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.quarkus.it.jackson.model.Elephant;
+import io.quarkus.it.jackson.model.MammalFamily;
+import io.quarkus.it.jackson.model.Whale;
+
+@Path("jsonSubTypes")
+public class JsonSubTypesResource {
+
+ private final ObjectMapper objectMapper;
+
+ public JsonSubTypesResource(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @GET
+ public String test() throws JsonProcessingException {
+ return objectMapper.writeValueAsString(new MammalFamily(List.of(
+ new Whale(30.0, "white"), new Elephant(10, "africa"))));
+ }
+}
diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java
new file mode 100644
index 0000000000000..4fea8838dc3b7
--- /dev/null
+++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java
@@ -0,0 +1,20 @@
+package io.quarkus.it.jackson.model;
+
+public class Elephant implements Mammal {
+
+ private final int hornLength;
+ private final String continent;
+
+ public Elephant(int hornLength, String continent) {
+ this.hornLength = hornLength;
+ this.continent = continent;
+ }
+
+ public int getHornLength() {
+ return hornLength;
+ }
+
+ public String getContinent() {
+ return continent;
+ }
+}
diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java
new file mode 100644
index 0000000000000..dc622346349f2
--- /dev/null
+++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java
@@ -0,0 +1,15 @@
+package io.quarkus.it.jackson.model;
+
+import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY;
+import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+@JsonTypeInfo(use = NAME, include = PROPERTY)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = Elephant.class, name = "Elephant"),
+ @JsonSubTypes.Type(value = Whale.class, name = "Whale"),
+})
+public interface Mammal {
+}
diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java
new file mode 100644
index 0000000000000..b35e13d143ddc
--- /dev/null
+++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java
@@ -0,0 +1,19 @@
+package io.quarkus.it.jackson.model;
+
+import java.util.Collection;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+public class MammalFamily {
+
+ private final Collection mammals;
+
+ public MammalFamily(Collection mammals) {
+ this.mammals = mammals;
+ }
+
+ public Collection getMammals() {
+ return mammals;
+ }
+}
diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java
new file mode 100644
index 0000000000000..de7d22dfe2c64
--- /dev/null
+++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java
@@ -0,0 +1,20 @@
+package io.quarkus.it.jackson.model;
+
+public class Whale implements Mammal {
+
+ private final double swimSpeed;
+ private final String color;
+
+ public Whale(double swimSpeed, String color) {
+ this.swimSpeed = swimSpeed;
+ this.color = color;
+ }
+
+ public double getSwimSpeed() {
+ return swimSpeed;
+ }
+
+ public String getColor() {
+ return color;
+ }
+}
diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java
new file mode 100644
index 0000000000000..9d58d36a156f4
--- /dev/null
+++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.jackson;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class JsonSubTypesResourceIT extends JsonSubTypesResourceTest {
+}
diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java
new file mode 100644
index 0000000000000..fe1979b72db7a
--- /dev/null
+++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java
@@ -0,0 +1,27 @@
+package io.quarkus.it.jackson;
+
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.http.ContentType;
+import io.restassured.response.Response;
+
+@QuarkusTest
+class JsonSubTypesResourceTest {
+
+ @Test
+ void test() {
+ Response response = given()
+ .accept(ContentType.JSON)
+ .when()
+ .get("jsonSubTypes")
+ .then()
+ .statusCode(200)
+ .extract().response();
+ assertThat(response.jsonPath().getString("mammals[0].color")).isEqualTo("white");
+ assertThat(response.jsonPath().getString("mammals[1].continent")).isEqualTo("africa");
+ }
+}
diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java
new file mode 100644
index 0000000000000..c2121fe991b46
--- /dev/null
+++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java
@@ -0,0 +1,9 @@
+package io.quarkus.it.picocli;
+
+import io.quarkus.picocli.runtime.annotations.TopCommand;
+import picocli.CommandLine;
+
+@TopCommand
+@CommandLine.Command(mixinStandardHelpOptions = true, versionProvider = VersionProvider.class)
+public class EntryWithVersionCommand {
+}
diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java
new file mode 100644
index 0000000000000..e06d7c92156f7
--- /dev/null
+++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java
@@ -0,0 +1,22 @@
+package io.quarkus.it.picocli;
+
+import jakarta.inject.Singleton;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import picocli.CommandLine;
+
+@Singleton
+public class VersionProvider implements CommandLine.IVersionProvider {
+
+ private final String version;
+
+ public VersionProvider(@ConfigProperty(name = "some.version", defaultValue = "0.0.1") String version) {
+ this.version = version;
+ }
+
+ @Override
+ public String[] getVersion() throws Exception {
+ return new String[] { version };
+ }
+}
diff --git a/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java
new file mode 100644
index 0000000000000..1218095d3de49
--- /dev/null
+++ b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java
@@ -0,0 +1,25 @@
+package io.quarkus.it.picocli;
+
+import static io.quarkus.it.picocli.TestUtils.createConfig;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusProdModeTest;
+
+public class TestVersion {
+
+ @RegisterExtension
+ static final QuarkusProdModeTest config = createConfig("version-app", EntryWithVersionCommand.class,
+ VersionProvider.class)
+ .overrideConfigKey("some.version", "1.1")
+ .setCommandLineParameters("--version");
+
+ @Test
+ public void simpleTest() {
+ Assertions.assertThat(config.getStartupConsoleOutput()).containsOnlyOnce("1.1");
+ Assertions.assertThat(config.getExitCode()).isZero();
+ }
+
+}
diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java
index 3c3bcea3042bc..ebf44dc314680 100644
--- a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java
+++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java
@@ -2,6 +2,9 @@
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@@ -11,4 +14,9 @@ public interface Client {
@GET
@Consumes(MediaType.TEXT_PLAIN)
String echo(String name);
+
+ @GET
+ @Path("/v2/{name}")
+ @Produces(MediaType.TEXT_PLAIN)
+ String invoke(@PathParam("name") String name);
}
diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java
index 2e3a307174dc8..782165a5c3895 100644
--- a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java
+++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java
@@ -3,6 +3,9 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@@ -17,4 +20,11 @@ public class ClientCallingResource {
public String passThrough() {
return client.echo("World!");
}
+
+ @GET
+ @Path("/{name}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String invoke(@PathParam("name") String name) {
+ return client.invoke(name + "/" + name);
+ }
}
diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java
index ba55cdc0f30f0..a6b277681a970 100644
--- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java
+++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java
@@ -1,6 +1,7 @@
package io.quarkus.it.rest.reactive.stork;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate;
import java.util.Map;
@@ -25,6 +26,8 @@ int httpsPort() {
protected Map initWireMock(WireMockServer server) {
server.stubFor(WireMock.get("/hello")
.willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200)));
+ server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}"))
+ .willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200)));
return Map.of("fast-service", "localhost:8443");
}
}
diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java
index 5adb6924ee71b..884d5ffbbfc0f 100644
--- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java
+++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java
@@ -54,4 +54,17 @@ void shouldUseFasterService() {
// after hitting the slow endpoint, we should only use the fast one:
assertThat(responses).containsOnly(FAST_RESPONSE, FAST_RESPONSE, FAST_RESPONSE);
}
+
+ @Test
+ void shouldUseV2Service() {
+ Set responses = new HashSet<>();
+
+ for (int i = 0; i < 2; i++) {
+ Response response = when().get("/client/quarkus");
+ response.then().statusCode(200);
+ }
+
+ responses.clear();
+
+ }
}
diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java
index 7dbc7f74b9b9d..3b1a051f345a6 100644
--- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java
+++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java
@@ -1,6 +1,7 @@
package io.quarkus.it.rest.reactive.stork;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate;
import java.util.Map;
@@ -26,6 +27,8 @@ protected Map initWireMock(WireMockServer server) {
server.stubFor(WireMock.get("/hello")
.willReturn(aResponse().withFixedDelay(1000)
.withBody(SLOW_RESPONSE).withStatus(200)));
+ server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}"))
+ .willReturn(aResponse().withFixedDelay(1000).withBody(SLOW_RESPONSE).withStatus(200)));
return Map.of("slow-service", "localhost:8444");
}
}
diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt
new file mode 100644
index 0000000000000..6459e438c013f
--- /dev/null
+++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt
@@ -0,0 +1,19 @@
+package io.quarkus.it.resteasy.reactive.kotlin
+
+import jakarta.ws.rs.GET
+import jakarta.ws.rs.Path
+import jakarta.ws.rs.Produces
+import jakarta.ws.rs.core.MediaType
+
+@Path("/companion")
+class CompanionResource {
+ @Path("success")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ fun success() = ResponseData.success()
+
+ @Path("failure")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ fun failure() = ResponseData.failure("error")
+}
diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt
new file mode 100644
index 0000000000000..efce43fa20bac
--- /dev/null
+++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt
@@ -0,0 +1,17 @@
+package io.quarkus.it.resteasy.reactive.kotlin
+
+class ResponseData(
+ val code: Int = STATUS_CODE.SUCCESS.code,
+ val msg: String = "",
+ val data: T? = null,
+) {
+ companion object {
+ fun success() = ResponseData()
+ fun failure(msg: String) = ResponseData(code = STATUS_CODE.ERROR.code, msg = msg)
+ }
+}
+
+enum class STATUS_CODE(val code: Int) {
+ SUCCESS(200),
+ ERROR(500)
+}
diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt
new file mode 100644
index 0000000000000..75b52c24a0edc
--- /dev/null
+++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt
@@ -0,0 +1,5 @@
+package io.quarkus.it.resteasy.reactive.kotlin
+
+import io.quarkus.test.junit.QuarkusIntegrationTest
+
+@QuarkusIntegrationTest class CompanionIT : CompanionTest() {}
diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt
new file mode 100644
index 0000000000000..1f088fffed227
--- /dev/null
+++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt
@@ -0,0 +1,29 @@
+package io.quarkus.it.resteasy.reactive.kotlin
+
+import io.quarkus.test.junit.QuarkusTest
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers.*
+import org.junit.jupiter.api.Test
+
+@QuarkusTest
+class CompanionTest {
+
+ @Test
+ fun testSuccessResponseData() {
+ When { get("/companion/success") } Then
+ {
+ statusCode(200)
+ body(containsString("200"))
+ }
+ }
+
+ @Test
+ fun testFailureResponseData() {
+ When { get("/companion/failure") } Then
+ {
+ statusCode(200)
+ body(containsString("500"), containsString("error"))
+ }
+ }
+}
diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java
new file mode 100644
index 0000000000000..b8275f241d538
--- /dev/null
+++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java
@@ -0,0 +1,86 @@
+package io.quarkus.it.vertx;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.QuarkusTestProfile;
+import io.quarkus.test.junit.TestProfile;
+
+@QuarkusTest
+@TestProfile(RandomTestPortTestCase.TestPortProfile.class)
+public class RandomTestPortTestCase {
+
+ @ConfigProperty(name = "quarkus.http.port")
+ int httpPort;
+
+ @ConfigProperty(name = "quarkus.http.test-port")
+ int httpTestPort;
+
+ @TestHTTPResource(value = "/some-path")
+ URL httpTestUrl;
+
+ @ConfigProperty(name = "quarkus.http.ssl-port")
+ int httpsPort;
+
+ @ConfigProperty(name = "quarkus.http.test-ssl-port")
+ int httpsTestPort;
+
+ @TestHTTPResource(value = "/some-path", ssl = true)
+ URL httpsTestUrl;
+
+ @Test
+ public void testHttpTestPort() {
+ assertNotEquals(0, httpTestPort);
+ assertNotEquals(8080, httpTestPort);
+ assertNotEquals(8081, httpTestPort);
+
+ assertEquals(httpTestPort, httpPort);
+ assertEquals(httpTestPort, httpTestUrl.getPort());
+ }
+
+ @Test
+ public void testHttpsTestPort() {
+ assertNotEquals(0, httpsTestPort);
+ assertNotEquals(8443, httpsTestPort);
+ assertNotEquals(8444, httpsTestPort);
+
+ assertEquals(httpsTestPort, httpsPort);
+ assertEquals(httpsTestPort, httpsTestUrl.getPort());
+ }
+
+ @Test
+ public void testLegacyProperties() {
+ Config config = ConfigProvider.getConfig();
+
+ testLegacyProperty(config, "quarkus.http.ssl-port", "quarkus.https.port", httpsPort);
+ testLegacyProperty(config, "quarkus.http.test-ssl-port", "quarkus.https.test-port", httpsTestPort);
+ testLegacyProperty(config, "%test.quarkus.http.ssl-port", "%test.quarkus.https.port", httpsTestPort);
+ }
+
+ private void testLegacyProperty(Config config, String correctName, String legacyName, int expectedValue) {
+ Optional portWithCorrectProperty = config.getOptionalValue(correctName, Integer.class);
+ Optional portWithLegacyProperty = config.getOptionalValue(legacyName, Integer.class);
+
+ assertEquals(Optional.of(expectedValue), portWithCorrectProperty);
+ assertEquals(portWithCorrectProperty, portWithLegacyProperty);
+ }
+
+ public static class TestPortProfile implements QuarkusTestProfile {
+
+ @Override
+ public Map getConfigOverrides() {
+ return Map.of("quarkus.http.test-port", "0", "quarkus.http.test-ssl-port", "0");
+ }
+ }
+}
diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java
index 60eeb40d6f5ba..59f41cc339b4e 100644
--- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java
+++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java
@@ -3,13 +3,13 @@
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
-import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
+import org.jboss.logmanager.ExtHandler;
import org.wildfly.common.Assert;
-public class InMemoryLogHandler extends Handler {
+public class InMemoryLogHandler extends ExtHandler {
private final Predicate predicate;