io.quarkus
quarkus-junit4-mock
diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java
index 56d79688b0219..e7971fdf3014d 100644
--- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java
+++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java
@@ -33,7 +33,7 @@ public class DevServicesConfig {
* ends with `-legacy`.
* Override with `quarkus.keycloak.devservices.keycloak-x-image`.
*/
- @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:23.0.6")
+ @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:23.0.7")
public String imageName;
/**
diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java
index c45ccd88dd41d..20aa1cbe976ff 100644
--- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java
+++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java
@@ -55,7 +55,7 @@ public interface OtlpExporterTracesConfig {
* OTLP defines the encoding of telemetry data and the protocol used to exchange data between the client and the
* server. Depending on the exporter, the available protocols will be different.
*
- * Currently, only {@code grpc} and {@code http} are allowed.
+ * Currently, only {@code grpc} and {@code http/protobuf} are allowed.
*/
@WithDefault(Protocol.GRPC)
Optional protocol();
diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java
index 551efb3e0afc8..af5206f3c152b 100644
--- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java
+++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java
@@ -310,12 +310,20 @@ private void logAppropriateWarning(GrpcStatus status,
+ statusMessage);
} else {
if (status == null) {
- logger.log(
- Level.WARNING,
- "Failed to export "
- + type
- + "s. Server responded with error message: "
- + statusMessage);
+ if (statusMessage == null) {
+ logger.log(
+ Level.WARNING,
+ "Failed to export "
+ + type
+ + "s. Perhaps the collector does not support collecting traces using grpc? Try configuring 'quarkus.otel.exporter.otlp.traces.protocol=http/protobuf'");
+ } else {
+ logger.log(
+ Level.WARNING,
+ "Failed to export "
+ + type
+ + "s. Server responded with error message: "
+ + statusMessage);
+ }
} else {
logger.log(
Level.WARNING,
diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java
index d3ec075c606d5..bc8472286dae8 100644
--- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java
+++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java
@@ -8,8 +8,10 @@
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
+import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
@@ -22,6 +24,7 @@
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.quarkus.vertx.core.runtime.BufferOutputStream;
+import io.smallrye.mutiny.Uni;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
@@ -38,6 +41,8 @@ final class VertxHttpExporter implements SpanExporter {
private static final Logger internalLogger = Logger.getLogger(VertxHttpExporter.class.getName());
private static final ThrottlingLogger logger = new ThrottlingLogger(internalLogger);
+ private static final int MAX_ATTEMPTS = 3;
+
private final HttpExporter delegate;
VertxHttpExporter(HttpExporter delegate) {
@@ -110,75 +115,35 @@ private static String determineBasePath(URI baseUri) {
@Override
public void send(Consumer marshaler,
int contentLength,
- Consumer onResponse,
+ Consumer onHttpResponseRead,
Consumer onError) {
- client.request(HttpMethod.POST, basePath + TRACES_PATH)
- .onSuccess(new Handler<>() {
- @Override
- public void handle(HttpClientRequest request) {
-
- HttpClientRequest clientRequest = request.response(new Handler<>() {
- @Override
- public void handle(AsyncResult callResult) {
- if (callResult.succeeded()) {
- HttpClientResponse clientResponse = callResult.result();
- clientResponse.body(new Handler<>() {
- @Override
- public void handle(AsyncResult bodyResult) {
- if (bodyResult.succeeded()) {
- onResponse.accept(new Response() {
- @Override
- public int statusCode() {
- return clientResponse.statusCode();
- }
-
- @Override
- public String statusMessage() {
- return clientResponse.statusMessage();
- }
-
- @Override
- public byte[] responseBody() {
- return bodyResult.result().getBytes();
- }
- });
- } else {
- onError.accept(bodyResult.cause());
- }
- }
- });
- } else {
- onError.accept(callResult.cause());
- }
- }
- })
- .putHeader("Content-Type", contentType);
-
- Buffer buffer = Buffer.buffer(contentLength);
- OutputStream os = new BufferOutputStream(buffer);
- if (compressionEnabled) {
- clientRequest.putHeader("Content-Encoding", "gzip");
- try (var gzos = new GZIPOutputStream(os)) {
- marshaler.accept(gzos);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- } else {
- marshaler.accept(os);
- }
-
- if (!headers.isEmpty()) {
- for (var entry : headers.entrySet()) {
- clientRequest.putHeader(entry.getKey(), entry.getValue());
- }
- }
-
- clientRequest.send(buffer);
+ String requestURI = basePath + TRACES_PATH;
+ var clientRequestSuccessHandler = new ClientRequestSuccessHandler(client, requestURI, headers, compressionEnabled,
+ contentType,
+ contentLength, onHttpResponseRead,
+ onError, marshaler, 1);
+ initiateSend(client, requestURI, MAX_ATTEMPTS, clientRequestSuccessHandler, onError);
+ }
+ private static void initiateSend(HttpClient client, String requestURI,
+ int numberOfAttempts,
+ Handler clientRequestSuccessHandler,
+ Consumer onError) {
+ Uni.createFrom().completionStage(new Supplier>() {
+ @Override
+ public CompletionStage get() {
+ return client.request(HttpMethod.POST, requestURI).toCompletionStage();
+ }
+ }).onFailure().retry()
+ .withBackOff(Duration.ofMillis(100))
+ .atMost(numberOfAttempts)
+ .subscribe().with(new Consumer<>() {
+ @Override
+ public void accept(HttpClientRequest request) {
+ clientRequestSuccessHandler.handle(request);
}
- })
- .onFailure(onError::accept);
+ }, onError);
}
@Override
@@ -204,5 +169,134 @@ public void handle(Throwable event) {
});
return shutdownResult;
}
+
+ private static class ClientRequestSuccessHandler implements Handler {
+ private final HttpClient client;
+ private final String requestURI;
+ private final Map headers;
+ private final boolean compressionEnabled;
+ private final String contentType;
+ private final int contentLength;
+ private final Consumer onHttpResponseRead;
+ private final Consumer onError;
+ private final Consumer marshaler;
+
+ private final int attemptNumber;
+
+ public ClientRequestSuccessHandler(HttpClient client,
+ String requestURI, Map headers,
+ boolean compressionEnabled,
+ String contentType,
+ int contentLength,
+ Consumer onHttpResponseRead,
+ Consumer onError,
+ Consumer marshaler,
+ int attemptNumber) {
+ this.client = client;
+ this.requestURI = requestURI;
+ this.headers = headers;
+ this.compressionEnabled = compressionEnabled;
+ this.contentType = contentType;
+ this.contentLength = contentLength;
+ this.onHttpResponseRead = onHttpResponseRead;
+ this.onError = onError;
+ this.marshaler = marshaler;
+ this.attemptNumber = attemptNumber;
+ }
+
+ @Override
+ public void handle(HttpClientRequest request) {
+
+ HttpClientRequest clientRequest = request.response(new Handler<>() {
+ @Override
+ public void handle(AsyncResult callResult) {
+ if (callResult.succeeded()) {
+ HttpClientResponse clientResponse = callResult.result();
+ clientResponse.body(new Handler<>() {
+ @Override
+ public void handle(AsyncResult bodyResult) {
+ if (bodyResult.succeeded()) {
+ if (clientResponse.statusCode() >= 500) {
+ if (attemptNumber <= MAX_ATTEMPTS) {
+ // we should retry for 5xx error as they might be recoverable
+ initiateSend(client, requestURI,
+ MAX_ATTEMPTS - attemptNumber,
+ newAttempt(),
+ onError);
+ return;
+ }
+ }
+ onHttpResponseRead.accept(new Response() {
+ @Override
+ public int statusCode() {
+ return clientResponse.statusCode();
+ }
+
+ @Override
+ public String statusMessage() {
+ return clientResponse.statusMessage();
+ }
+
+ @Override
+ public byte[] responseBody() {
+ return bodyResult.result().getBytes();
+ }
+ });
+ } else {
+ if (attemptNumber <= MAX_ATTEMPTS) {
+ // retry
+ initiateSend(client, requestURI,
+ MAX_ATTEMPTS - attemptNumber,
+ newAttempt(),
+ onError);
+ } else {
+ onError.accept(bodyResult.cause());
+ }
+ }
+ }
+ });
+ } else {
+ if (attemptNumber <= MAX_ATTEMPTS) {
+ // retry
+ initiateSend(client, requestURI,
+ MAX_ATTEMPTS - attemptNumber,
+ newAttempt(),
+ onError);
+ } else {
+ onError.accept(callResult.cause());
+ }
+ }
+ }
+ })
+ .putHeader("Content-Type", contentType);
+
+ Buffer buffer = Buffer.buffer(contentLength);
+ OutputStream os = new BufferOutputStream(buffer);
+ if (compressionEnabled) {
+ clientRequest.putHeader("Content-Encoding", "gzip");
+ try (var gzos = new GZIPOutputStream(os)) {
+ marshaler.accept(gzos);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ marshaler.accept(os);
+ }
+
+ if (!headers.isEmpty()) {
+ for (var entry : headers.entrySet()) {
+ clientRequest.putHeader(entry.getKey(), entry.getValue());
+ }
+ }
+
+ clientRequest.send(buffer);
+ }
+
+ public ClientRequestSuccessHandler newAttempt() {
+ return new ClientRequestSuccessHandler(client, requestURI, headers, compressionEnabled,
+ contentType, contentLength, onHttpResponseRead,
+ onError, marshaler, attemptNumber + 1);
+ }
+ }
}
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java
index a9c80ff5f2398..74a5ed3c26677 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveCDIProcessor.java
@@ -88,16 +88,26 @@ void unremovableContextMethodParams(Optional re
@BuildStep
void subResourcesAsBeans(ResourceScanningResultBuildItem setupEndpointsResult,
List subResourcesAsBeans,
- BuildProducer producer) {
- if (subResourcesAsBeans.isEmpty() || setupEndpointsResult.getResult().getPossibleSubResources().isEmpty()) {
+ BuildProducer unremovableProducer,
+ BuildProducer additionalProducer) {
+ Map possibleSubResources = setupEndpointsResult.getResult().getPossibleSubResources();
+ if (possibleSubResources.isEmpty()) {
return;
}
- List classNames = new ArrayList<>(setupEndpointsResult.getResult().getPossibleSubResources().size());
- for (DotName subResourceClass : setupEndpointsResult.getResult().getPossibleSubResources().keySet()) {
- classNames.add(subResourceClass.toString());
+ // make SubResources unremovable - this will only apply if they become beans by some other means
+ unremovableProducer.produce(UnremovableBeanBuildItem.beanTypes(possibleSubResources.keySet()));
+
+ if (subResourcesAsBeans.isEmpty()) {
+ return;
+ }
+
+ // now actually make SubResources beans as it was requested via build item
+ AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder();
+ for (DotName subResourceClass : possibleSubResources.keySet()) {
+ builder.addBeanClass(subResourceClass.toString());
}
- producer.produce(new AdditionalBeanBuildItem(classNames.toArray(new String[0])));
+ additionalProducer.produce(builder.build());
}
// when an interface is annotated with @Path and there is only one implementation of it that is not annotated with @Path,
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 742bec269c29b..7c3c4788360d3 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
@@ -119,7 +119,6 @@
import org.jboss.resteasy.reactive.server.vertx.serializers.ServerVertxAsyncFileMessageBodyWriter;
import org.jboss.resteasy.reactive.server.vertx.serializers.ServerVertxBufferMessageBodyWriter;
import org.jboss.resteasy.reactive.spi.BeanFactory;
-import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassVisitor;
import io.quarkus.arc.Unremovable;
@@ -1010,7 +1009,6 @@ public void providersFromClasspath(BuildProducer mes
}
}
- @Nullable
private static String determineHandledGenericTypeOfProviderInterface(Class> providerClass,
Class> targetProviderInterface) {
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SubResourcesAsBeansTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SubResourcesAsBeansTest.java
index 20767bbb3c741..9be0b0abbea24 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SubResourcesAsBeansTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SubResourcesAsBeansTest.java
@@ -7,6 +7,7 @@
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.container.ResourceContext;
import jakarta.ws.rs.core.HttpHeaders;
import org.hamcrest.Matchers;
@@ -63,11 +64,11 @@ public MiddleRestResource hello(String first) {
public static class MiddleRestResource {
@Inject
- RestSubResource restSubResource;
+ ResourceContext resourceContext;
@Path("{last}")
public RestSubResource hello() {
- return restSubResource;
+ return resourceContext.getResource(RestSubResource.class);
}
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/devmode/SubResourceDevModeTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/devmode/SubResourceDevModeTest.java
index b335d931fd040..3431f27270c61 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/devmode/SubResourceDevModeTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/devmode/SubResourceDevModeTest.java
@@ -4,6 +4,7 @@
import java.util.function.Supplier;
+import jakarta.inject.Singleton;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@@ -50,10 +51,11 @@ public static class Resource {
@Path("sub")
public SubResource subresource() {
- return new SubResource();
+ return resourceContext.getResource(SubResource.class);
}
}
+ @Singleton
public static class SubResource {
@GET
@@ -61,4 +63,5 @@ public String hello() {
return "hello";
}
}
+
}