diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 805bde451bd69..a863cd7979a52 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -44,7 +44,7 @@
3.3.1
3.0.5
2.3.1
- 1.9.0
+ 1.9.1
2.1.1
5.6.0
3.5.4
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
index 5238f2e607e2f..947e55779c7b5 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java
@@ -418,25 +418,15 @@ public MainClassBuildItem mainClassBuildStep(BuildProducer d.equals(mainClassDotName));
+ if (!hasQuarkusApplicationInterface) {
className += "Kt";
}
}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
index aa3ef463c7f52..3e10e02d71243 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
@@ -382,6 +382,15 @@ public class DevMojo extends AbstractMojo {
private Pty pty;
private boolean windowsColorSupport;
+ /**
+ * Indicates for which launch mode the dependencies should be resolved.
+ *
+ * @return launch mode for which the dependencies should be resolved
+ */
+ protected LaunchMode getLaunchModeClasspath() {
+ return LaunchMode.DEVELOPMENT;
+ }
+
@Override
public void setLog(Log log) {
super.setLog(log);
@@ -1089,7 +1098,7 @@ private QuarkusDevModeLauncher newLauncher(Boolean debugPortOk) throws Exception
final Path appModelLocation = resolveSerializedModelLocation();
ApplicationModel appModel = bootstrapProvider
- .getResolvedApplicationModel(QuarkusBootstrapProvider.getProjectId(project), LaunchMode.DEVELOPMENT);
+ .getResolvedApplicationModel(QuarkusBootstrapProvider.getProjectId(project), getLaunchModeClasspath());
if (appModel != null) {
bootstrapProvider.close();
} else {
@@ -1118,6 +1127,7 @@ private QuarkusDevModeLauncher newLauncher(Boolean debugPortOk) throws Exception
final BootstrapMavenContext mvnCtx = new BootstrapMavenContext(mvnConfig);
appModel = new BootstrapAppModelResolver(new MavenArtifactResolver(mvnCtx))
.setDevMode(true)
+ .setTest(LaunchMode.TEST.equals(getLaunchModeClasspath()))
.setCollectReloadableDependencies(!noDeps)
.resolveModel(mvnCtx.getCurrentProject().getAppArtifact());
}
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/TestMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/TestMojo.java
index a8e3008b568ec..bfbf3eff00656 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/TestMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/TestMojo.java
@@ -9,12 +9,19 @@
import io.quarkus.bootstrap.app.QuarkusBootstrap;
import io.quarkus.deployment.dev.DevModeContext;
import io.quarkus.deployment.dev.IsolatedTestModeMain;
+import io.quarkus.runtime.LaunchMode;
/**
* The test mojo, that starts continuous testing outside of dev mode
*/
@Mojo(name = "test", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
public class TestMojo extends DevMojo {
+
+ @Override
+ protected LaunchMode getLaunchModeClasspath() {
+ return LaunchMode.TEST;
+ }
+
@Override
protected void modifyDevModeContext(MavenDevModeLauncher.Builder builder) {
builder.entryPointCustomizer(new Consumer() {
diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc
index 608ad5f4f3cb9..194345a6458bd 100644
--- a/docs/src/main/asciidoc/security-openid-connect-client.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc
@@ -139,7 +139,7 @@ public class ProtectedResource {
As you can see `ProtectedResource` returns a name from both `userName()` and `adminName()` methods. The name is extracted from the current `JsonWebToken`.
-Next let's add a REST Client with `OpenID Connect Client Reactive Filter` and another REST Client with `OpenID Connect Token Propagation Filter`. `FrontendResource` will use these two clients to call `ProtectedResource`:
+Next let's add a REST Client with `OidcClientRequestReactiveFilter` and another REST Client with `AccessTokenRequestReactiveFilter`. `FrontendResource` will use these two clients to call `ProtectedResource`:
[source,java]
----
diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc
index 87f0feb9c3b87..3aed582620151 100644
--- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc
@@ -447,15 +447,16 @@ quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.path=/logout
+# Logged-out users should be returned to the /welcome.html site which will offer an option to re-login:
quarkus.oidc.logout.post-logout-path=/welcome.html
# Only the authenticated users can initiate a logout:
quarkus.http.auth.permission.authenticated.paths=/logout
quarkus.http.auth.permission.authenticated.policy=authenticated
-# Logged-out users should be returned to the /welcome.html site which will offer an option to re-login:
-quarkus.http.auth.permission.authenticated.paths=/welcome.html
-quarkus.http.auth.permission.authenticated.policy=permit
+# All users can see the welcome page:
+quarkus.http.auth.permission.public.paths=/welcome.html
+quarkus.http.auth.permission.public.policy=permit
----
You may also need to set `quarkus.oidc.authentication.cookie-path` to a path value common to all the application resources which is `/` in this example.
diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/QuarkusNettyConnectionCache.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/QuarkusNettyConnectionCache.java
index f2a124fb7776c..cfe46c1a7ab7b 100644
--- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/QuarkusNettyConnectionCache.java
+++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/QuarkusNettyConnectionCache.java
@@ -9,7 +9,7 @@
class QuarkusNettyConnectionCache implements ConnectionCache {
- volatile FastThreadLocal connectionCache = new FastThreadLocal<>();
+ final FastThreadLocal connectionCache = new FastThreadLocal<>();
@Override
public Acquirable get() {
@@ -36,6 +36,21 @@ public void put(Acquirable acquirable) {
@Override
public void reset() {
- connectionCache = new FastThreadLocal<>();
+ // Do our best to release memory. In fact `io.agroal.pool.ConnectionPool` calls
+ // this method in `housingkeepingExecutor` thread only, so business threads still
+ // hold references to `ConnectionHandler` objects.
+ connectionCache.remove();
+
+ // `FastThreadLocalThread` uses an array and increasing index for `FastThreadLocal`, the thread
+ // local variables will *never* be expunged until the thread exits, so `FastThreadLocal` instance
+ // musn't be created again.
+ //
+ // For other non-`FastThreadLocalThread` threads, `FastThreadLocal` actually uses static
+ // `java.lang.ThreadLocal`, it's useless to create fresh `FastThreadLocal` instance.
+ //
+ // In summary, this connection cache always holds `ConnectionHandler` instances in thread local
+ // variables even after the underlying JDBC connections close, this does leak some memory.
+
+ //connectionCache = new FastThreadLocal<>();
}
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java
index 050bc7cb27e28..043ba96b1ea15 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java
@@ -55,7 +55,6 @@
import io.dekorate.kubernetes.decorator.RemoveAnnotationDecorator;
import io.dekorate.kubernetes.decorator.RemoveFromMatchingLabelsDecorator;
import io.dekorate.kubernetes.decorator.RemoveFromSelectorDecorator;
-import io.dekorate.kubernetes.decorator.RemoveLabelDecorator;
import io.dekorate.project.BuildInfo;
import io.dekorate.project.FileProjectFactory;
import io.dekorate.project.Project;
@@ -208,13 +207,11 @@ private static Collection createLabelDecorators(Optional multiStreamJson() {
return Multi.createFrom().items(new Message("hello"), new Message("stef"));
}
+ /**
+ * Reproduce #30044.
+ */
+ @Path("stream-json/multi/fast")
+ @GET
+ @Produces(RestMediaType.APPLICATION_STREAM_JSON)
+ @RestStreamElementType(MediaType.APPLICATION_JSON)
+ public Multi multiStreamJsonFast() {
+ List ids = new ArrayList<>(5000);
+ for (int i = 0; i < 5000; i++) {
+ ids.add(UUID.randomUUID());
+ }
+ return Multi.createFrom().items(ids::stream)
+ .onItem().transform(id -> new Message(id.toString()))
+ .onOverflow().buffer(81920);
+ }
+
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/sse/SseTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java
similarity index 78%
rename from extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/sse/SseTestCase.java
rename to extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java
index afb909111ba93..bc82769dd1d7a 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/sse/SseTestCase.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java
@@ -1,4 +1,4 @@
-package io.quarkus.resteasy.reactive.jackson.deployment.test.sse;
+package io.quarkus.resteasy.reactive.jackson.deployment.test.streams;
import static io.restassured.RestAssured.when;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,7 +33,7 @@
import io.quarkus.test.common.http.TestHTTPResource;
import io.smallrye.mutiny.Multi;
-public class SseTestCase {
+public class StreamTestCase {
@TestHTTPResource
URI uri;
@@ -41,16 +41,16 @@ public class SseTestCase {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
- .addClasses(SseResource.class, Message.class));
+ .addClasses(StreamResource.class, Message.class));
@Test
public void testSseFromSse() throws Exception {
- testSse("sse");
+ testSse("streams");
}
@Test
public void testSseFromMulti() throws Exception {
- testSse("sse/multi");
+ testSse("streams/multi");
}
private void testSse(String path) throws Exception {
@@ -81,12 +81,12 @@ public void accept(Throwable throwable) {
@Test
public void testMultiFromSse() {
- testMulti("sse");
+ testMulti("streams");
}
@Test
public void testMultiFromMulti() {
- testMulti("sse/multi");
+ testMulti("streams/multi");
}
private void testMulti(String path) {
@@ -99,24 +99,24 @@ private void testMulti(String path) {
@Test
public void testJsonMultiFromSse() {
- testJsonMulti("sse/json");
- testJsonMulti("sse/json2");
- testJsonMulti("sse/blocking/json");
+ testJsonMulti("streams/json");
+ testJsonMulti("streams/json2");
+ testJsonMulti("streams/blocking/json");
}
@Test
public void testJsonMultiFromMulti() {
- testJsonMulti("sse/json/multi");
+ testJsonMulti("streams/json/multi");
}
@Test
public void testJsonMultiFromMultiWithDefaultElementType() {
- testJsonMulti("sse/json/multi2");
+ testJsonMulti("streams/json/multi2");
}
@Test
public void testNdJsonMultiFromMulti() {
- when().get(uri.toString() + "sse/ndjson/multi")
+ when().get(uri.toString() + "streams/ndjson/multi")
.then().statusCode(HttpStatus.SC_OK)
// @formatter:off
.body(is("{\"name\":\"hello\"}\n"
@@ -127,7 +127,7 @@ public void testNdJsonMultiFromMulti() {
@Test
public void testStreamJsonMultiFromMulti() {
- when().get(uri.toString() + "sse/stream-json/multi")
+ when().get(uri.toString() + "streams/stream-json/multi")
.then().statusCode(HttpStatus.SC_OK)
// @formatter:off
.body(is("{\"name\":\"hello\"}\n"
@@ -143,4 +143,19 @@ private void testJsonMulti(String path) {
List list = multi.collect().asList().await().atMost(Duration.ofSeconds(30));
assertThat(list).extracting("name").containsExactly("hello", "stef");
}
+
+ /**
+ * Reproduce #30044.
+ */
+ @Test
+ public void testStreamJsonMultiFromMultiFast() {
+ String payload = when().get(uri.toString() + "streams/stream-json/multi/fast")
+ .then().statusCode(HttpStatus.SC_OK)
+ .header(HttpHeaders.CONTENT_TYPE, containsString(RestMediaType.APPLICATION_STREAM_JSON))
+ .extract().response().asString();
+
+ // the payload include 5000 json objects
+ assertThat(payload.lines()).hasSize(5000)
+ .allSatisfy(s -> assertThat(s).matches("\\{\"name\":\".*\"}"));
+ }
}
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 cd018c190b115..9584c5efd89f1 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
@@ -43,8 +43,8 @@ public void testParser() {
// all fields
testParser("data:DATA\nid:ID\n:COMMENT\nretry:23\nevent:NAME\n\n", "DATA", "COMMENT", "ID", "NAME", 23);
- // all fields and no data: no event
- testParser("id:ID\n:COMMENT\nretry:23\nevent:NAME\n\n", null, null, null, null, SseEvent.RECONNECT_NOT_SET);
+ // all fields and no data
+ testParser("id:ID\n:COMMENT\nretry:23\nevent:NAME\n\n", null, "COMMENT", "ID", "NAME", 23);
// optional space after colon
testParser("data:foo\n\n", "foo", null, null, null, SseEvent.RECONNECT_NOT_SET);
@@ -147,6 +147,13 @@ private void testParser(String event, String data, String comment, String lastId
.setId(lastId)
.setName(name)
.setReconnectDelay(reconnectDelay)));
+ } else if (comment != null) {
+ testParser(Collections.singletonList(event), Collections.singletonList(new InboundSseEventImpl(null, null)
+ .setData(null)
+ .setComment(comment)
+ .setId(lastId)
+ .setName(name)
+ .setReconnectDelay(reconnectDelay)));
} else {
testParser(Collections.singletonList(event), Collections.emptyList());
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java
index 307d5b77d5d81..31f931789d3de 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java
@@ -160,7 +160,8 @@ public Multi sseThrows() {
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
public Multi sseRaw(@Context Sse sse) {
- return Multi.createFrom().items(sse.newEventBuilder().id("one").data("uno").name("eins").build(),
+ return Multi.createFrom().items(sse.newEventBuilder().comment("dummy").build(),
+ sse.newEventBuilder().id("one").data("uno").name("eins").build(),
sse.newEventBuilder().id("two").data("dos").name("zwei").build(),
sse.newEventBuilder().id("three").data("tres").name("drei").build());
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java
index d43b1ddb1249b..f1615e674ad80 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java
@@ -3,7 +3,6 @@
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -211,7 +210,7 @@ public void testSse() throws InterruptedException {
});
sse.open();
Assertions.assertTrue(latch.await(20, TimeUnit.SECONDS));
- Assertions.assertEquals(Arrays.asList("a", "b", "c"), results);
+ org.assertj.core.api.Assertions.assertThat(results).containsExactly("a", "b", "c");
Assertions.assertEquals(0, errors.size());
}
}
@@ -248,7 +247,9 @@ public void testSseForMultiWithOutboundSseEvent() throws InterruptedException {
List results = new CopyOnWriteArrayList<>();
List ids = new CopyOnWriteArrayList<>();
List names = new CopyOnWriteArrayList<>();
+ List comments = new CopyOnWriteArrayList<>();
sse.register(event -> {
+ comments.add(event.getComment());
results.add(event.readData());
ids.add(event.getId());
names.add(event.getName());
@@ -259,9 +260,10 @@ public void testSseForMultiWithOutboundSseEvent() throws InterruptedException {
});
sse.open();
Assertions.assertTrue(latch.await(20, TimeUnit.SECONDS));
- Assertions.assertEquals(Arrays.asList("uno", "dos", "tres"), results);
- Assertions.assertEquals(Arrays.asList("one", "two", "three"), ids);
- Assertions.assertEquals(Arrays.asList("eins", "zwei", "drei"), names);
+ org.assertj.core.api.Assertions.assertThat(results).containsExactly(null, "uno", "dos", "tres");
+ org.assertj.core.api.Assertions.assertThat(ids).containsExactly(null, "one", "two", "three");
+ org.assertj.core.api.Assertions.assertThat(names).containsExactly(null, "eins", "zwei", "drei");
+ org.assertj.core.api.Assertions.assertThat(comments).containsExactly("dummy", null, null, null);
Assertions.assertEquals(0, errors.size());
}
}
diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java
index 0ac3da55c1ebd..3e8ae58571990 100644
--- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java
+++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java
@@ -20,6 +20,7 @@
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;
@@ -272,8 +273,10 @@ void buildExecutionService(
SmallRyeGraphQLRecorder recorder,
SmallRyeGraphQLFinalIndexBuildItem graphQLFinalIndexBuildItem,
BeanContainerBuildItem beanContainer,
+ BuildProducer systemPropertyProducer,
SmallRyeGraphQLConfig graphQLConfig) {
+ activateFederation(graphQLConfig, systemPropertyProducer, graphQLFinalIndexBuildItem);
Schema schema = SchemaBuilder.build(graphQLFinalIndexBuildItem.getFinalIndex(), graphQLConfig.autoNameStrategy);
RuntimeValue initialized = recorder.createExecutionService(beanContainer.getValue(), schema);
@@ -627,6 +630,43 @@ void activateEventing(SmallRyeGraphQLConfig graphQLConfig, BuildProducer systemProperties,
+ SmallRyeGraphQLFinalIndexBuildItem index) {
+ if (config.federationEnabled.isPresent()) {
+ String value = config.federationEnabled.get().toString();
+ systemProperties.produce(new SystemPropertyBuildItem(ConfigKey.ENABLE_FEDERATION, value));
+ System.setProperty(ConfigKey.ENABLE_FEDERATION, value);
+ } else {
+ //
+ boolean foundAnyFederationAnnotation = false;
+ for (ClassInfo federationAnnotationType : index.getFinalIndex()
+ .getClassesInPackage("io.smallrye.graphql.api.federation")) {
+ if (federationAnnotationType.isAnnotation()) {
+ if (!index.getFinalIndex().getAnnotations(federationAnnotationType.name()).isEmpty()) {
+ foundAnyFederationAnnotation = true;
+ }
+ }
+ }
+ String value = Boolean.toString(foundAnyFederationAnnotation);
+ systemProperties.produce(new SystemPropertyBuildItem(ConfigKey.ENABLE_FEDERATION, value));
+ System.setProperty(ConfigKey.ENABLE_FEDERATION, value);
+ }
+ }
+
private boolean shouldActivateService(Optional serviceEnabled,
boolean linkedCapabilityIsPresent,
String linkedExtensionName,
diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLFederationDisabledTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLFederationDisabledTest.java
new file mode 100644
index 0000000000000..8c6e5b49f73ad
--- /dev/null
+++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/GraphQLFederationDisabledTest.java
@@ -0,0 +1,51 @@
+package io.quarkus.smallrye.graphql.deployment;
+
+import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.containsString;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+/**
+ * Make sure that if no Federation related annotations are in the application, then Federation is
+ * disabled (unless explicitly enabled in the config).
+ */
+public class GraphQLFederationDisabledTest extends AbstractGraphQLTest {
+
+ @RegisterExtension
+ static QuarkusUnitTest test = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(FooApi.class, Foo.class)
+ .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"));
+
+ @Test
+ public void checkSchemaDoesNotIncludeServiceDeclaration() {
+ RestAssured.given()
+ .get("/graphql/schema.graphql")
+ .then()
+ .body(not(containsString("type _Service {")));
+ }
+
+ @GraphQLApi
+ static class FooApi {
+
+ @Query
+ public Foo foo() {
+ return new Foo();
+ }
+
+ }
+
+ static class Foo {
+
+ public String name;
+
+ }
+
+}
diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java
index fa340d09cf5a2..84a9c90c7ce49 100644
--- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java
+++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java
@@ -20,6 +20,13 @@ public class SmallRyeGraphQLConfig {
@ConfigItem(defaultValue = "graphql")
public String rootPath;
+ /**
+ * Enable Apollo Federation. If this value is unspecified, then federation will be enabled
+ * automatically if any GraphQL Federation annotations are detected in the application.
+ */
+ @ConfigItem(name = "federation.enabled")
+ public Optional federationEnabled;
+
/**
* Enable metrics. By default, this is false. If set to true, a metrics extension is required.
*/
diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java
index 7ff8e8d403998..127e1ed8cc759 100644
--- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java
+++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java
@@ -13,9 +13,12 @@
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
+import org.jboss.logging.Logger;
+
import graphql.ErrorType;
import graphql.ExecutionResult;
import graphql.GraphQLError;
+import graphql.execution.AbortExecutionException;
import io.quarkus.security.identity.CurrentIdentityAssociation;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.smallrye.graphql.execution.ExecutionResponse;
@@ -46,6 +49,8 @@ public class SmallRyeGraphQLExecutionHandler extends SmallRyeGraphQLAbstractHand
+ StandardCharsets.UTF_8.name();
private static final String MISSING_OPERATION = "Missing operation body";
+ private static final Logger log = Logger.getLogger(SmallRyeGraphQLExecutionHandler.class);
+
public SmallRyeGraphQLExecutionHandler(boolean allowGet, boolean allowPostWithQueryParameters, boolean runBlocking,
CurrentIdentityAssociation currentIdentityAssociation,
CurrentVertxRequest currentVertxRequest) {
@@ -320,8 +325,13 @@ class VertxExecutionResponseWriter implements ExecutionResponseWriter {
@Override
public void write(ExecutionResponse er) {
-
if (shouldFail(er)) {
+ er.getExecutionResult()
+ .getErrors().stream()
+ .filter(e -> e.getErrorType().equals(ErrorType.ExecutionAborted))
+ .forEach(e -> {
+ log.error("Execution aborted", (AbortExecutionException) e);
+ });
response.setStatusCode(500)
.end();
} else {
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
index bfa14a3f460dc..027526b66551a 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
@@ -406,7 +406,12 @@ public InputStream getResourceAsStream(String unsanitisedName) {
if (name.endsWith(".class")) {
ClassPathElement[] providers = state.loadableResources.get(name);
if (providers != null) {
- return new ByteArrayInputStream(providers[0].getResource(name).getData());
+ final ClassPathResource resource = providers[0].getResource(name);
+ if (resource == null) {
+ throw new IllegalStateException(providers[0] + " from " + getName() + " (closed=" + this.isClosed()
+ + ") was expected to provide " + name + " but failed");
+ }
+ return new ByteArrayInputStream(resource.getData());
}
} else {
for (ClassPathElement i : elements) {
diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java
index c6edb7584aae4..aa4129bbd27b3 100644
--- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java
+++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java
@@ -1,6 +1,7 @@
package org.jboss.resteasy.reactive.client.impl;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -18,6 +19,7 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.net.impl.ConnectionBase;
+import io.vertx.core.parsetools.RecordParser;
public class MultiInvoker extends AbstractRxInvoker> {
@@ -121,10 +123,13 @@ public Multi method(String name, Entity> entity, GenericType respons
} else {
HttpClientResponse vertxResponse = restClientRequestContext.getVertxClientResponse();
if (!emitter.isCancelled()) {
- // FIXME: this is probably not good enough
if (response.getStatus() == 200
&& MediaType.SERVER_SENT_EVENTS_TYPE.isCompatible(response.getMediaType())) {
registerForSse(multiRequest, responseType, response, vertxResponse);
+ } else if (response.getStatus() == 200
+ && RestMediaType.APPLICATION_STREAM_JSON_TYPE.isCompatible(response.getMediaType())) {
+ registerForJsonStream(multiRequest, restClientRequestContext, responseType, response,
+ vertxResponse);
} else {
// read stuff in chunks
registerForChunks(multiRequest, restClientRequestContext, responseType, response, vertxResponse);
@@ -267,4 +272,46 @@ private boolean startsWith(byte[] array, byte[] prefix) {
});
}
+ private void registerForJsonStream(MultiRequest super R> multiRequest,
+ RestClientRequestContext restClientRequestContext,
+ GenericType responseType,
+ ResponseImpl response,
+ HttpClientResponse vertxClientResponse) {
+ RecordParser parser = RecordParser.newDelimited("\n");
+ parser.handler(new Handler() {
+ @Override
+ public void handle(Buffer chunk) {
+
+ ByteArrayInputStream in = new ByteArrayInputStream(chunk.getBytes());
+ try {
+ R item = restClientRequestContext.readEntity(in, responseType, response.getMediaType(),
+ response.getMetadata());
+ multiRequest.emitter.emit(item);
+ } catch (IOException e) {
+ multiRequest.emitter.fail(e);
+ }
+ }
+ });
+ vertxClientResponse.exceptionHandler(t -> {
+ if (t == ConnectionBase.CLOSED_EXCEPTION) {
+ // we can ignore this one since we registered a closeHandler
+ } else {
+ multiRequest.emitter.fail(t);
+ }
+ });
+ vertxClientResponse.endHandler(new Handler() {
+ @Override
+ public void handle(Void c) {
+ multiRequest.complete();
+ }
+ });
+
+ vertxClientResponse.handler(parser);
+
+ // watch for user cancelling
+ multiRequest.onCancel(() -> {
+ vertxClientResponse.request().connection().close();
+ });
+ }
+
}
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 e503570b36274..47b8e54957a69 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
@@ -145,7 +145,7 @@ private void parseEvent() {
private void dispatchEvent() {
// ignore empty events
- if (dataBuffer.length() == 0)
+ if (dataBuffer.length() == 0 && commentBuffer.length() == 0)
return;
WebTargetImpl webTarget = sseEventSource.getWebTarget();
InboundSseEventImpl event;
@@ -159,7 +159,7 @@ private void dispatchEvent() {
event.setComment(commentBuffer.length() == 0 ? null : commentBuffer.toString());
// SSE spec says empty string is the default, but JAX-RS says null if not specified
event.setId(lastEventId);
- event.setData(dataBuffer.toString());
+ event.setData(dataBuffer.length() == 0 ? null : dataBuffer.toString());
// SSE spec says "message" is the default, but JAX-RS says null if not specified
event.setName(eventType);
event.setReconnectDelay(eventReconnectTime);
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java
index b0969d595fc1b..24de690d468c9 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java
@@ -69,8 +69,10 @@ private static String serialiseEvent(ResteasyReactiveRequestContext context, Out
if (event.getReconnectDelay() >= 0)
serialiseField(context, sb, "retry", Long.toString(event.getReconnectDelay()), false);
}
- String data = serialiseDataToString(context, event, eventMediaType);
- serialiseField(context, sb, "data", data, true);
+ if (event.getData() != null) {
+ String data = serialiseDataToString(context, event, eventMediaType);
+ serialiseField(context, sb, "data", data, true);
+ }
sb.append(NL);
// return a UTF8 buffer
return sb.toString();
diff --git a/integration-tests/gradle/src/main/resources/kotlin-grpc-project/src/main/kotlin/org/acme/Main.kt b/integration-tests/gradle/src/main/resources/kotlin-grpc-project/src/main/kotlin/org/acme/Main.kt
new file mode 100644
index 0000000000000..46b8d7e400e0d
--- /dev/null
+++ b/integration-tests/gradle/src/main/resources/kotlin-grpc-project/src/main/kotlin/org/acme/Main.kt
@@ -0,0 +1,15 @@
+package org.acme
+
+import io.quarkus.runtime.Quarkus
+import io.quarkus.runtime.QuarkusApplication
+import io.quarkus.runtime.annotations.QuarkusMain
+
+@QuarkusMain
+class Main : QuarkusApplication {
+
+ @Throws(Exception::class)
+ override fun run(vararg args: String?): Int {
+ Quarkus.waitForExit()
+ return 0
+ }
+}
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithApplicationPropertiesTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithApplicationPropertiesTest.java
index 3156b4763d6bf..78e00fa64cb5e 100644
--- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithApplicationPropertiesTest.java
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithApplicationPropertiesTest.java
@@ -48,7 +48,8 @@ public void assertGeneratedResources() throws IOException {
assertThat(i).isInstanceOfSatisfying(Deployment.class, d -> {
assertThat(d.getMetadata()).satisfies(m -> {
assertThat(m.getName()).isEqualTo("test-it");
- assertThat(m.getLabels()).contains(entry("foo", "bar"));
+ assertThat(m.getLabels()).contains(entry("foo", "bar"))
+ .containsKey("app.kubernetes.io/version"); // make sure the version was not removed from the labels
assertThat(m.getNamespace()).isEqualTo("applications");
});
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithApplicationPropertiesTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithApplicationPropertiesTest.java
index 8962e67945cb0..6e8c0e89b7496 100644
--- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithApplicationPropertiesTest.java
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithApplicationPropertiesTest.java
@@ -45,7 +45,8 @@ public void assertGeneratedResources() throws IOException {
assertThat(openshiftList).filteredOn(h -> "DeploymentConfig".equals(h.getKind())).singleElement().satisfies(h -> {
assertThat(h.getMetadata()).satisfies(m -> {
assertThat(m.getName()).isEqualTo("test-it");
- assertThat(m.getLabels()).contains(entry("foo", "bar"));
+ assertThat(m.getLabels()).contains(entry("foo", "bar"))
+ .containsKey("app.kubernetes.io/version"); // make sure the version was not removed from the labels
assertThat(m.getNamespace()).isEqualTo("applications");
});
AbstractObjectAssert, ?> specAssert = assertThat(h).extracting("spec");
diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java
index 9e88e5bd86c63..9e94a122f1f6b 100644
--- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java
+++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java
@@ -118,6 +118,9 @@ void init(@Observes Router router) {
router.post("/hello").handler(rc -> rc.response().putHeader("content-type", MediaType.TEXT_PLAIN)
.end("Hello, " + (rc.getBodyAsString()).repeat(getCount(rc))));
+ router.post("/hello/fromMessage").handler(rc -> rc.response().putHeader("content-type", MediaType.TEXT_PLAIN)
+ .end(rc.body().asJsonObject().getString("message")));
+
router.route("/call-hello-client").blockingHandler(rc -> {
String url = rc.getBody().toString();
HelloClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url))
@@ -126,6 +129,14 @@ void init(@Observes Router router) {
rc.response().end(greeting);
});
+ router.route("/call-helloFromMessage-client").blockingHandler(rc -> {
+ String url = rc.getBody().toString();
+ HelloClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url))
+ .build(HelloClient.class);
+ String greeting = client.fromMessage(new HelloClient.Message("Hello world"));
+ rc.response().end(greeting);
+ });
+
router.post("/params/param").handler(rc -> rc.response().putHeader("content-type", MediaType.TEXT_PLAIN)
.end(getParam(rc)));
diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/HelloClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/HelloClient.java
index bc09e13d357ac..4d9ade9614667 100644
--- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/HelloClient.java
+++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/HelloClient.java
@@ -9,6 +9,8 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import com.fasterxml.jackson.annotation.JsonCreator;
+
import io.quarkus.rest.client.reactive.ClientExceptionMapper;
@Path("")
@@ -18,6 +20,12 @@ public interface HelloClient {
@Consumes(MediaType.TEXT_PLAIN)
String greeting(String name, @QueryParam("count") int count);
+ @Path("fromMessage")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ @Consumes(MediaType.APPLICATION_JSON)
+ String fromMessage(Message message);
+
// this isn't used, but it makes sure that the generated provider can be properly instantiated in native mode
@ClientExceptionMapper
static RuntimeException toException(Response response) {
@@ -26,4 +34,18 @@ static RuntimeException toException(Response response) {
}
return null;
}
+
+ class Message {
+
+ private final String message;
+
+ @JsonCreator
+ public Message(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
}
diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java
index 937d70633a9fb..bfe4f408ed506 100644
--- a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java
+++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java
@@ -42,6 +42,12 @@ public void shouldMakeTextRequest() {
assertThat(response.asString()).isEqualTo("Hello, JohnJohn");
}
+ @Test
+ public void shouldMakeJsonRequestAndGetTextResponse() {
+ Response response = RestAssured.with().body(helloUrl).post("/call-helloFromMessage-client");
+ assertThat(response.asString()).isEqualTo("Hello world");
+ }
+
@Test
public void restResponseShouldWorkWithNonSuccessfulResponse() {
Response response = RestAssured.with().body(helloUrl).post("/rest-response");
diff --git a/jakarta/rewrite.yml b/jakarta/rewrite.yml
index 43f4b630cb42b..978eed5d96d41 100644
--- a/jakarta/rewrite.yml
+++ b/jakarta/rewrite.yml
@@ -573,7 +573,7 @@ recipeList:
newValue: '6.1.0'
- org.openrewrite.maven.ChangePropertyValue:
key: smallrye-graphql.version
- newValue: '2.0.0'
+ newValue: '2.0.1'
- org.openrewrite.maven.ChangePropertyValue:
key: smallrye-health.version
newValue: '4.0.1'