Skip to content

[3.22] 3.22.2 backports 1 #47721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
41f558c
Improve error message when @BeanParam class has no fields
geoand Apr 29, 2025
ad8ab9a
Fully disable max-parallel to see if it causes Spring native issue
gsmet Apr 30, 2025
d9c887f
Added test for streaming large chunks of RestMediaType.APPLICATION_ND…
magnus-gustafsson Apr 30, 2025
c63017b
Take quarkus.http.test-ssl-enabled into account for @TestHTTPResource
geoand Apr 30, 2025
eb6510f
Add metadata to @Deprecated of TestHTTPResource.ssl
geoand Apr 30, 2025
bc27eb2
Improve documentation for executing tests against a running application
geoand Apr 30, 2025
0da00bf
Add REST Client to the list of extension supporting certificate reload
geoand Apr 30, 2025
476d6ff
OpenAPI: enable merging of schema examples by default
MikeEdgar Apr 30, 2025
49ee4d5
Fix Jackson serializers generation for Kotlin data classes
mariofusco May 2, 2025
a26ab10
Dev UI Router: Check guarded component before running intercept logic
phillip-kruger May 1, 2025
b6f242b
fix: more informative/encouraging grpc warning about legacy compatabi…
maxandersen May 1, 2025
a9c2599
deployment(vertx-http): guard against OOB in StaticResourcesProcessor…
LucienBrule May 2, 2025
5f07ed2
Health endpoints add display names
melloware May 4, 2025
d1c20a1
fix missing register for reflection : QuartzSchedulerImpl$Nonconcurre…
Olivier-aka-Raiden Apr 30, 2025
84640b0
Health OpenAPI ensure repeatable builds
melloware May 4, 2025
6593563
Only display the extension status note if attribute is defined
gsmet Apr 29, 2025
277762a
removed extra backslash typo
sheilamjones Apr 29, 2025
180b203
ArC: reduce the size of dependency graphs
mkouba Apr 30, 2025
fad62fa
ArC: dependency graph improvements
mkouba May 5, 2025
85efdf1
ArC: introduce quarkus.arc.dev-mode.generate-dependency-graphs=auto
mkouba May 5, 2025
54bcd9c
#47630 Handle empty bearer token file in ClientAssertionProvider
ayagmar May 2, 2025
3af4778
Dev UI: Shortern the save message in the workspace
phillip-kruger May 5, 2025
ef5d12b
Bump org.hibernate.reactive:hibernate-reactive-core
dependabot[bot] May 5, 2025
3c27f63
Health OpenAPI add missing Tag description
melloware May 5, 2025
2ad10f8
Fix Javadoc for TLS configuration of REST Client
geoand May 6, 2025
70b6528
Add a note how to workaround Keycloak Dev Service startup issues on Mac
sberyozkin May 6, 2025
546409e
Strip the classpath scheme from SmallrRye JWT native resources
sberyozkin May 6, 2025
a2d4908
Remove stale references to Mandrel not being supported
holly-cummins Apr 29, 2025
f4e3de3
Properly drop preview status for WebSockets.next
gsmet Apr 29, 2025
d56cbe5
Add some pointers to WebSockets Next to websockets.adoc
gsmet May 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci-actions-incremental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1221,7 +1221,8 @@ jobs:
# Ignore the following YAML Schema error
timeout-minutes: ${{matrix.timeout}}
strategy:
max-parallel: ${{ fromJson(needs.configure.outputs.config).maxParallel || 12 }}
# fully disable for now as we have issues with RunsOn and it might come from the value being defined (even if set to 999)
#max-parallel: ${{ fromJson(needs.configure.outputs.config).maxParallel || 12 }}
fail-fast: false
matrix: ${{ fromJson(needs.calculate-test-jobs.outputs.native_matrix) }}
steps:
Expand Down
2 changes: 2 additions & 0 deletions docs/src/main/asciidoc/_includes/extension-status.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ifdef::extension-status[]
[NOTE]
====
This technology is considered {extension-status}.
Expand All @@ -21,3 +22,4 @@ endif::[]

For a full list of possible statuses, check our https://quarkus.io/faq/#what-are-the-extension-statuses[FAQ entry].
====
endif::[]
6 changes: 3 additions & 3 deletions docs/src/main/asciidoc/building-native-image.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ This is particularly important when it comes to conformance and security.

* Mandrel is recommended for building native executables that target Linux containerized environments.
This means that Mandrel users are encouraged to use containers to build their native executables.
If you are building native executables for macOS,
If you are building native executables for macOS on amd64/x86,
you should consider using Oracle GraalVM instead,
because Mandrel does not currently target this platform.
Building native executables directly on bare metal Linux or Windows is possible,
Building native executables directly on bare metal Linux, macOS (on M processors), or Windows is possible,
with details available in the https://github.com/graalvm/mandrel/blob/default/README.md[Mandrel README]
and https://github.com/graalvm/mandrel/releases[Mandrel releases].

Expand Down Expand Up @@ -111,7 +111,7 @@ We recommend the _community edition_ of GraalVM. For example, install it with `s
export GRAALVM_HOME=$HOME/Development/mandrel/
----
+
On macOS (not supported by Mandrel), point the variable to the `Home` sub-directory:
On macOS (amd64/x86 based Macs not supported), point the variable to the `Home` sub-directory:
+
[source,bash]
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ openshift-helloworld openshift-helloworld-username-dev.apps.sandbox-m2.ll9k.p1
Be aware that the route is now listening on port 80 and is no longer on port 8080.
====
+
You can test the application demonstrated in this example with a web browser or a terminal by using `curl` and the complete URL output from `oc get routes`, that is, "\http://openshift-helloworld-username-dev.apps.sandbox-m2.ll9k.p1.openshiftapps.com".
You can test the application demonstrated in this example with a web browser or a terminal by using `curl` and the complete URL output from `oc get routes`, that is, `\http://openshift-helloworld-username-dev.apps.sandbox-m2.ll9k.p1.openshiftapps.com`.
+
For example: `curl \http://openshift-helloworld-username-dev.apps.sandbox-m2.ll9k.p1.openshiftapps.com`.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/getting-started-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1431,7 +1431,7 @@ An example use of this could be the following Maven command, that forces `@Quark
./mvnw verify -Dquarkus.http.test-host=1.2.3.4 -Dquarkus.http.test-port=4321
----

To test against a running instance that only accepts SSL/TLS connection (example: `https://1.2.3.4:4321`) set the system property `quarkus.http.test-ssl-enabled` to `true`.
To test against a running instance that only accepts SSL/TLS connection (example: `https://1.2.3.4:4321`) set the system property `quarkus.http.test-ssl-enabled` to `true` and `quarkus.http.test-ssl-port` to the target HTTPS port.

== Mixing `@QuarkusTest` with other type of tests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ KeyCloak Dev Services Starting:
2021-11-02 17:14:44,170 INFO [io.qua.oid.dep.dev.key.KeycloakDevServicesProcessor] (build-10) Dev Services for Keycloak started.
----

[NOTE]
====
Adding the `quarkus.keycloak.devservices.java-opts=-XX:UseSVE=0` configuration property may help to workaround Keycloak Dev Services startup problems on some Mac OS systems.
====

[IMPORTANT]
====
When logging in to the Keycloak admin console, the username is `admin`, and the password is `admin`.
Expand Down
3 changes: 2 additions & 1 deletion docs/src/main/asciidoc/tls-registry-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,8 @@ quarkus.tls.http.key-store.pem.0.key=tls.key
[IMPORTANT]
====
Impacted servers and clients may need to listen to the `CertificateUpdatedEvent` to apply the new certificates.
This is automatically done for the Quarkus HTTP server, such as the Quarkus REST server, gRPC server, and WebSocket server, as well as the management interface if it is enabled.
This is automatically done for the Quarkus HTTP server, as well as the Quarkus REST server, gRPC server, and WebSocket server, as well as the management interface if it is enabled.
On the client side, Quarkus REST Client automatically handles certificate update events.
====

NOTE: In Quarkus dev mode, when files are touched, it will trigger the `CertificateUpdatedEvent` much more frequently.
Expand Down
3 changes: 0 additions & 3 deletions docs/src/main/asciidoc/websockets-next-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
[id="websockets-next-reference-guide"]
= WebSockets Next reference guide
:extension-status: preview
include::_attributes.adoc[]
:numbered:
:sectnums:
Expand All @@ -14,8 +13,6 @@ include::_attributes.adoc[]
:topics: web,websockets
:extensions: io.quarkus:quarkus-websockets-next

include::{includes}/extension-status.adoc[]

The `quarkus-websockets-next` extension provides a modern declarative API to define WebSocket server and client endpoints.

== The WebSocket protocol
Expand Down
3 changes: 0 additions & 3 deletions docs/src/main/asciidoc/websockets-next-tutorial.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ include::_attributes.adoc[]
This guide explains how your Quarkus application can utilize web sockets to create interactive web applications.
In this guide, we will develop a very simple chat application using web sockets to receive and send messages to the other connected users.

include::{includes}/extension-status.adoc[]


== Prerequisites

include::{includes}/prerequisites.adoc[]
Expand Down
12 changes: 10 additions & 2 deletions docs/src/main/asciidoc/websockets.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Using WebSockets
= Using WebSockets with Undertow
include::_attributes.adoc[]
:categories: web
:summary: This guide explains how your Quarkus application can utilize web sockets to create interactive web applications. Because it’s the canonical web socket application, we are going to create a simple chat application.
:topics: web,websockets
:extensions: io.quarkus:quarkus-websockets,io.quarkus:quarkus-websockets-client

This guide explains how your Quarkus application can utilize web sockets to create interactive web applications.
This guide explains how your Quarkus application can utilize web sockets to create interactive web applications,
in the context of an Undertow-based Quarkus application, or if you rely on https://jakarta.ee/specifications/websocket/[Jakarta WebSocket].

[TIP]
====
If you don't use Undertow or https://jakarta.ee/specifications/websocket/[Jakarta WebSocket],
it is recommended to use the more modern xref:websockets-next-tutorial.adoc[WebSockets Next extensions].
====

Because it's the _canonical_ web socket application, we are going to create a simple chat application.

== Prerequisites
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ public interface ArcDevModeConfig {
boolean monitoringEnabled();

/**
* If set to true then the dependency graphs are generated and available in the Dev UI.
* If set to {@code true} then the dependency graphs are generated and available in the Dev UI. If set to {@code auto}
* then the dependency graphs are generated if there's less than 1000 beans in the application. If set to {@code false} the
* dependency graphs are not generated.
*/
@WithDefault("true")
boolean generateDependencyGraphs();
@WithDefault("auto")
GenerateDependencyGraphs generateDependencyGraphs();

public enum GenerateDependencyGraphs {
TRUE,
FALSE,
AUTO
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.util.Set;
import java.util.stream.Collectors;

import org.jboss.logging.Logger;

import io.quarkus.arc.deployment.ArcConfig;
import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
Expand All @@ -29,6 +31,19 @@

public class ArcDevModeApiProcessor {

private static final Logger LOG = Logger.getLogger(ArcDevModeApiProcessor.class);

/**
* Do not generate dependency graphs for apps with more than N beans
*/
private static final int DEPENCENY_GRAPH_BEANS_LIMIT = 1000;

/**
* If a dependency graph exceeds the limit then we apply the {@link DevBeanInfos#MAX_DEPENDENCY_LEVEL} and if still exceeds
* the limit it's skipped completely, i.e. dependency graph is not available
*/
private static final int DEPENCENY_GRAPH_NODES_LIMIT = 30;

@BuildStep(onlyIf = IsDevelopment.class)
public void collectBeanInfo(ArcConfig config, ValidationPhaseBuildItem validationPhaseBuildItem,
CompletedApplicationClassPredicateBuildItem predicate,
Expand Down Expand Up @@ -65,7 +80,7 @@ public void collectBeanInfo(ArcConfig config, ValidationPhaseBuildItem validatio

// Build dependency graphs
Map<String, List<String>> beanDependenciesMap = new HashMap<>();
if (config.devMode().generateDependencyGraphs()) {
if (generateDependencyGraphs(config, beanInfos)) {
BeanResolver resolver = validationPhaseBuildItem.getBeanResolver();
Collection<BeanInfo> beans = validationContext.get(BuildExtension.Key.BEANS);
Map<BeanInfo, List<InjectionPointInfo>> directDependents = new HashMap<>();
Expand All @@ -85,11 +100,27 @@ public void collectBeanInfo(ArcConfig config, ValidationPhaseBuildItem validatio
DependencyGraph dependencyGraph = buildDependencyGraph(bean, validationContext, resolver, beanInfos,
allInjectionPoints, declaringToProducers,
directDependents);
if (dependencyGraph.links.isEmpty()) {
// Skip the graph if no links exist
continue;
}
if (dependencyGraph.nodes.size() > DEPENCENY_GRAPH_NODES_LIMIT) {
DependencyGraph visibleGraph = dependencyGraph.forLevel(DevBeanInfos.DEFAULT_MAX_DEPENDENCY_LEVEL);
if (visibleGraph.nodes.size() > DEPENCENY_GRAPH_NODES_LIMIT) {
LOG.debugf("Skip dependency graph for %s - too many visible nodes: %s", bean,
visibleGraph.nodes.size());
continue;
} else {
LOG.debugf("Dependency graph for %s was reduced to visible nodes: %s", bean,
dependencyGraph.nodes.size());
dependencyGraph = visibleGraph;
}
}
beanInfos.addDependencyGraph(bean.getIdentifier(), dependencyGraph);
// id -> [dep1Id, dep2Id]
beanDependenciesMap.put(bean.getIdentifier(),
dependencyGraph.filterLinks(link -> link.type.equals("directDependency")).nodes.stream()
.map(DevBeanInfo::getId).filter(id -> !id.equals(bean.getIdentifier()))
.map(Node::getId).filter(id -> !id.equals(bean.getIdentifier()))
.collect(Collectors.toList()));
}
}
Expand All @@ -111,7 +142,7 @@ DependencyGraph buildDependencyGraph(BeanInfo bean, ValidationContext validation
addNodesDependencies(0, bean, nodes, links, bean, devBeanInfos);
addNodesDependents(0, bean, nodes, links, bean, allInjectionPoints, declaringToProducers, resolver, devBeanInfos,
directDependents);
return new DependencyGraph(nodes, links);
return new DependencyGraph(nodes.stream().map(Node::from).collect(Collectors.toSet()), links);
}

private void addNodesDependencies(int level, BeanInfo root, Set<DevBeanInfo> nodes, Set<Link> links, BeanInfo bean,
Expand Down Expand Up @@ -184,4 +215,13 @@ private void addNodesDependents(int level, BeanInfo root, Set<DevBeanInfo> nodes
}
}

private boolean generateDependencyGraphs(ArcConfig config, DevBeanInfos beanInfos) {
return switch (config.devMode().generateDependencyGraphs()) {
case TRUE -> true;
case FALSE -> false;
case AUTO -> beanInfos.getBeans().size() < DEPENCENY_GRAPH_BEANS_LIMIT;
default -> throw new IllegalArgumentException("Unexpected value: " + config.devMode().generateDependencyGraphs());
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ public class DependencyGraph {

static final DependencyGraph EMPTY = new DependencyGraph(Set.of(), Set.of());

public final Set<DevBeanInfo> nodes;
public final Set<Node> nodes;
public final Set<Link> links;
public final int maxLevel;

public DependencyGraph(Set<DevBeanInfo> nodes, Set<Link> links) {
public DependencyGraph(Set<Node> nodes, Set<Link> links) {
this.nodes = nodes;
this.links = links;
this.maxLevel = links.stream().mapToInt(l -> l.level).max().orElse(0);
Expand All @@ -25,7 +25,7 @@ DependencyGraph forLevel(int level) {
DependencyGraph filterLinks(Predicate<Link> predicate) {
// Filter out links first
Set<Link> newLinks = new HashSet<>();
Set<DevBeanInfo> newNodes = new HashSet<>();
Set<Node> newNodes = new HashSet<>();
Set<String> usedIds = new HashSet<>();
for (Link link : links) {
if (predicate.test(link)) {
Expand All @@ -35,7 +35,7 @@ DependencyGraph filterLinks(Predicate<Link> predicate) {
}
}
// Now keep only nodes for which a link exists...
for (DevBeanInfo node : nodes) {
for (Node node : nodes) {
if (usedIds.contains(node.getId())) {
newNodes.add(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public DevInterceptorInfo getInterceptor(String id) {
}

public DependencyGraph getDependencyGraph(String beanId) {
// Note that MAX_DEPENDENCY_LEVEL is not implemented in UI yet
Integer maxLevel = DevConsoleManager.getGlobal(MAX_DEPENDENCY_LEVEL);
if (maxLevel == null) {
maxLevel = DEFAULT_MAX_DEPENDENCY_LEVEL;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.arc.deployment.devui;

public class Node {

static Node from(DevBeanInfo beanInfo) {
return new Node(beanInfo.getId(), beanInfo.getDescription(), beanInfo.getSimpleDescription());
}

private final String id;
private final String description;
private final String simpleDescription;

Node(String id, String description, String simpleDescription) {
this.id = id;
this.description = description;
this.simpleDescription = simpleDescription;
}

public String getId() {
return id;
}

public String getDescription() {
return description;
}

public String getSimpleDescription() {
return simpleDescription;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ export class QwcArcBeanGraph extends LitElement {
}else {
newNode.category = catindex;
}
//console.log('Adding node: ' + newNode.name);
return this._nodes.push(newNode) - 1;
}
return index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,11 @@ public void initializeGrpcServer(boolean hasNoBindableServiceBeans, BeanContaine
if (configuration.useSeparateServer()) {
if (provider == null) {
LOGGER.warn(
"Using legacy gRPC support, with separate new HTTP server instance. " +
"Switch to single HTTP server instance usage with quarkus.grpc.server.use-separate-server=false property");
"""
Using legacy gRPC support with a separate HTTP server instance. This is the current default to maintain compatibility.
You can switch to the new unified HTTP server by setting quarkus.grpc.server.use-separate-server=false
This change is recommended for new applications and will become the default in future versions.
""");
}

if (launchMode == LaunchMode.DEVELOPMENT) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ private ClientAssertion loadFromFileSystem() {
if (Files.exists(bearerTokenPath)) {
try {
String bearerToken = Files.readString(bearerTokenPath).trim();
if (bearerToken.isEmpty()) {
LOG.error(String.format("Bearer token file at path %s is empty or contains only whitespace",
bearerTokenPath));
return null;
}
Long expiresAt = getExpiresAtFromExpClaim(bearerToken);
if (expiresAt != null) {
return new ClientAssertion(bearerToken, expiresAt, scheduleRefresh(expiresAt));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import java.io.IOException;
import java.nio.file.Files;
Expand Down Expand Up @@ -40,6 +41,25 @@ public void testJwtBearerTokenRefresh() {
}
}

@Test
public void EmptyBearerTokenFileShouldReturnNullClientAssertion() {
Vertx vertx = Vertx.vertx();
Path emptyTokenPath = Path.of("target").resolve("empty-jwt-bearer-token.json");

storeNewJwtBearerToken(emptyTokenPath, "");
try (var clientAssertionProvider = new ClientAssertionProvider(vertx, emptyTokenPath)) {
assertNull(clientAssertionProvider.getClientAssertion());

String validToken = createJwtBearerToken();
storeNewJwtBearerToken(emptyTokenPath, validToken);

Awaitility.await().atMost(Duration.ofSeconds(10))
.untilAsserted(() -> assertEquals(validToken, clientAssertionProvider.getClientAssertion()));
} finally {
vertx.close().toCompletionStage().toCompletableFuture().join();
}
}

private static void storeNewJwtBearerToken(Path jwtBearerTokenPath, String jwtBearerToken) {
try {
Files.writeString(jwtBearerTokenPath, jwtBearerToken, TRUNCATE_EXISTING, CREATE, WRITE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,10 @@ List<ReflectiveClassBuildItem> reflectiveClasses(QuartzBuildTimeConfig config,
reflectiveClasses.addAll(getAdditionalConfigurationReflectiveClasses(config.triggerListeners(), TriggerListener.class));
reflectiveClasses.addAll(getAdditionalConfigurationReflectiveClasses(config.jobListeners(), JobListener.class));
reflectiveClasses.addAll(getAdditionalConfigurationReflectiveClasses(config.plugins(), SchedulerPlugin.class));

reflectiveClasses
.add(ReflectiveClassBuildItem.builder("io.quarkus.quartz.runtime.QuartzSchedulerImpl$NonconcurrentInvokerJob")
.reason(getClass().getName())
.methods().build());
return reflectiveClasses;
}

Expand Down
Loading
Loading