diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d664d7e396cd2..00202eba22941 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -92,15 +92,15 @@ 22.3.2 ${graal-sdk.version} 1.6.1.Final - 2.15.0 + 2.15.2 1.0.0.Final 3.12.0 1.15 1.5.1 - 6.2.1.Final + 6.2.4.Final 1.12.18 6.0.6.Final - 2.0.0.CR1 + 2.0.0.Final 8.0.0.Final 6.1.7.Final 6.0.1.Final @@ -159,7 +159,7 @@ 1.0.0 1.8.21 1.7.1 - 1.5.0 + 1.5.1 3.6.1 3.2.0 4.2.0 @@ -201,7 +201,7 @@ 2.20.0 1.3.0.Final 1.11.1 - 2.4.2.Final + 2.4.3.Final 0.1.15.Final 1.18.1 3.3.0 diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java index c2d254d25edbc..ed538474b8b57 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java @@ -7,12 +7,12 @@ import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.logging.Logger; -import io.smallrye.common.os.OS; import io.smallrye.config.SmallRyeConfig; public final class ContainerRuntimeUtil { @@ -20,6 +20,7 @@ public final class ContainerRuntimeUtil { private static final Logger log = Logger.getLogger(ContainerRuntimeUtil.class); private static final String CONTAINER_EXECUTABLE = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class) .getOptionalValue("quarkus.native.container-runtime", String.class).orElse(null); + private static final Pattern PODMAN_PATTERN = Pattern.compile("^podman(?:\\.exe)? version.*", Pattern.DOTALL); /** * Static variable is not used because the class gets loaded by different classloaders at @@ -44,7 +45,7 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { return containerRuntime; } - ContainerRuntime containerRuntimeEnvironment = getContainerRuntimeEnvironment(); + final ContainerRuntime containerRuntimeEnvironment = getContainerRuntimeEnvironment(); if (containerRuntimeEnvironment == ContainerRuntime.UNAVAILABLE) { storeContainerRuntimeInSystemProperty(ContainerRuntime.UNAVAILABLE); @@ -83,7 +84,7 @@ private static ContainerRuntime getContainerRuntimeEnvironment() { } if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("podman")) { podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); - podmanAvailable = podmanVersionOutput.startsWith("podman version"); + podmanAvailable = PODMAN_PATTERN.matcher(podmanVersionOutput).matches(); if (podmanAvailable) { return ContainerRuntime.PODMAN; } @@ -96,15 +97,13 @@ private static ContainerRuntime getContainerRuntimeEnvironment() { dockerAvailable = dockerVersionOutput.contains("Docker version"); if (dockerAvailable) { // Check if "docker" is an alias to "podman" - if (dockerVersionOutput.startsWith("podman version") || - dockerVersionOutput.startsWith("podman.exe version")) { + if (PODMAN_PATTERN.matcher(dockerVersionOutput).matches()) { return ContainerRuntime.PODMAN; } return ContainerRuntime.DOCKER; } podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); - podmanAvailable = podmanVersionOutput.startsWith("podman version") || - podmanVersionOutput.startsWith("podman.exe version"); + podmanAvailable = PODMAN_PATTERN.matcher(podmanVersionOutput).matches(); if (podmanAvailable) { return ContainerRuntime.PODMAN; } @@ -130,7 +129,7 @@ private static ContainerRuntime loadContainerRuntimeFromSystemProperty() { return null; } - ContainerRuntime containerRuntime = ContainerRuntime.valueOf(runtime); + final ContainerRuntime containerRuntime = ContainerRuntime.of(runtime); if (containerRuntime == null) { log.warnf("System property %s contains an unknown value %s. Ignoring it.", @@ -225,7 +224,7 @@ public enum ContainerRuntime { private final boolean rootless; ContainerRuntime(String executableName, boolean rootless) { - this.executableName = executableName + (OS.current() == OS.WINDOWS ? ".exe" : ""); + this.executableName = executableName; this.rootless = rootless; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java index 85571465b66ab..e4cd6f2963f56 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java @@ -17,7 +17,7 @@ public class OutputOptionMixin implements MessageWriter { static final boolean picocliDebugEnabled = "DEBUG".equalsIgnoreCase(System.getProperty("picocli.trace")); - @CommandLine.Option(names = { "-e", "--errors" }, description = "Display error messages.") + @CommandLine.Option(names = { "-e", "--errors" }, description = "Print more context on errors and exceptions.") boolean showErrors; @CommandLine.Option(names = { "--verbose" }, description = "Verbose mode.") diff --git a/docs/src/main/asciidoc/dev-ui.adoc b/docs/src/main/asciidoc/dev-ui.adoc index 18dbea5d2cd6d..64802be42a0a0 100644 --- a/docs/src/main/asciidoc/dev-ui.adoc +++ b/docs/src/main/asciidoc/dev-ui.adoc @@ -12,7 +12,7 @@ include::_attributes.adoc[] .Dev UI v1 ==== This guide covers the Dev UI v1, which has been replaced in Quarkus 3. -You can still access the Dev UI v1 using http://localhost/q/dev-v1/ +You can still access the Dev UI v1 using http://localhost:8080/q/dev-v1[/q/dev-v1] ==== This guide covers the Quarkus Dev UI for xref:building-my-first-extension.adoc[extension authors]. diff --git a/docs/src/main/asciidoc/security-keycloak-admin-client.adoc b/docs/src/main/asciidoc/security-keycloak-admin-client.adoc index 066ab69960068..93a8e84818e4e 100644 --- a/docs/src/main/asciidoc/security-keycloak-admin-client.adoc +++ b/docs/src/main/asciidoc/security-keycloak-admin-client.adoc @@ -179,7 +179,7 @@ An example using the `client-credentials` grant type needs only a minor adjustme [source,properties] ---- -quarkus.keycloak.admin-client=true +quarkus.keycloak.admin-client.enabled=true quarkus.keycloak.admin-client.server-url=http://localhost:8081 quarkus.keycloak.admin-client.realm=quarkus quarkus.keycloak.admin-client.client-id=quarkus-client diff --git a/extensions/datasource/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/datasource/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 4004eb6e31ba2..d727e12bff51d 100644 --- a/extensions/datasource/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/datasource/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,4 +8,6 @@ metadata: categories: - "data" status: "stable" - unlisted: true \ No newline at end of file + unlisted: true + config: + - "quarkus.datasource." \ No newline at end of file diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index cc59601653cd7..02c744f3472ba 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -233,7 +233,7 @@ public void enrollBeanValidationTypeSafeActivatorForReflection(Capabilities capa if (capabilities.isPresent(Capability.HIBERNATE_VALIDATOR)) { reflectiveClasses.produce(ReflectiveClassBuildItem.builder("org.hibernate.boot.beanvalidation.TypeSafeActivator") .methods().fields().build()); - reflectiveClasses.produce(ReflectiveClassBuildItem.builder(BeanValidationIntegrator.BV_CHECK_CLASS) + reflectiveClasses.produce(ReflectiveClassBuildItem.builder(BeanValidationIntegrator.JAKARTA_BV_CHECK_CLASS) .constructors(false).build()); } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index 85ffce844c7f5..3eabdd8e7bc79 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -442,6 +442,7 @@ private PrevalidatedQuarkusMetadata trimBootstrapMetadata(MetadataImpl fullMeta) fullMeta.getMetadataBuildingOptions(), //TODO Replace this fullMeta.getEntityBindingMap(), fullMeta.getComposites(), + fullMeta.getGenericComponentsMap(), fullMeta.getMappedSuperclassMap(), fullMeta.getCollectionBindingMap(), fullMeta.getTypeDefinitionMap(), diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java index 0cd850422385d..01086814f5d9a 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java @@ -20,6 +20,7 @@ import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; import org.hibernate.integrator.spi.Integrator; +import org.hibernate.loader.ast.internal.BatchLoaderFactoryInitiator; import org.hibernate.persister.internal.PersisterClassResolverInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; @@ -235,6 +236,9 @@ private static List> buildQuarkusServiceInitiatorLis // Default implementation serviceInitiators.add(ParameterMarkerStrategyInitiator.INSTANCE); + // Default implementation + serviceInitiators.add(BatchLoaderFactoryInitiator.INSTANCE); + // Default implementation serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/PrevalidatedQuarkusMetadata.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/PrevalidatedQuarkusMetadata.java index d620d37d0a26a..9562475e330d3 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/PrevalidatedQuarkusMetadata.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/PrevalidatedQuarkusMetadata.java @@ -290,6 +290,11 @@ public void visitRegisteredComponents(Consumer consumer) { metadata.visitRegisteredComponents(consumer); } + @Override + public Component getGenericComponent(Class componentClass) { + return metadata.getGenericComponent(componentClass); + } + public Map getEntityBindingMap() { return metadata.getEntityBindingMap(); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java index 7d92e318ad25e..04eaa9f9f032d 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java @@ -14,6 +14,7 @@ import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator; import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; +import org.hibernate.loader.ast.internal.BatchLoaderFactoryInitiator; import org.hibernate.persister.internal.PersisterClassResolverInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; @@ -106,6 +107,9 @@ public List> initialInitiatorList() { // Default implementation serviceInitiators.add(ParameterMarkerStrategyInitiator.INSTANCE); + // Default implementation + serviceInitiators.add(BatchLoaderFactoryInitiator.INSTANCE); + // Default implementation serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java index e474441059d26..1b1a51d01d586 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/ReactiveSessionFactoryProducer.java @@ -8,7 +8,6 @@ import jakarta.persistence.PersistenceUnit; import org.hibernate.reactive.common.spi.Implementor; -import org.hibernate.reactive.common.spi.MutinyImplementor; import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.impl.MutinySessionFactoryImpl; @@ -29,7 +28,7 @@ public class ReactiveSessionFactoryProducer { @ApplicationScoped @DefaultBean @Unremovable - @Typed({ Mutiny.SessionFactory.class, MutinyImplementor.class, Implementor.class }) + @Typed({ Mutiny.SessionFactory.class, Implementor.class }) public MutinySessionFactoryImpl mutinySessionFactory() { if (jpaConfig.getDeactivatedPersistenceUnitNames() .contains(HibernateReactive.DEFAULT_REACTIVE_PERSISTENCE_UNIT_NAME)) { @@ -38,7 +37,6 @@ public MutinySessionFactoryImpl mutinySessionFactory() { + HibernateReactive.DEFAULT_REACTIVE_PERSISTENCE_UNIT_NAME + ": Hibernate Reactive was deactivated through configuration properties"); } - // TODO Remove this cast when we get rid of the dependency to MutinyImplementor return (MutinySessionFactoryImpl) emf.unwrap(Mutiny.SessionFactory.class); } diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java index a5be0f364df0d..059f826ebf6b2 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java @@ -23,6 +23,7 @@ import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorServiceInitiator; import org.hibernate.reactive.id.factory.spi.ReactiveIdentifierGeneratorFactoryInitiator; +import org.hibernate.reactive.loader.ast.internal.ReactiveBatchLoaderFactoryInitiator; import org.hibernate.reactive.provider.service.NativeParametersHandling; import org.hibernate.reactive.provider.service.NoJtaPlatformInitiator; import org.hibernate.reactive.provider.service.ReactiveMarkerServiceInitiator; @@ -245,6 +246,9 @@ private static List> buildQuarkusServiceInitiatorLis // Default implementation serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); + // Custom for Hibernate Reactive: BatchLoaderFactory + serviceInitiators.add(ReactiveBatchLoaderFactoryInitiator.INSTANCE); + serviceInitiators.trimToSize(); return serviceInitiators; } diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java index fa57b9e8b3bde..169b52a90d19e 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java @@ -17,6 +17,7 @@ import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; import org.hibernate.reactive.id.factory.spi.ReactiveIdentifierGeneratorFactoryInitiator; +import org.hibernate.reactive.loader.ast.internal.ReactiveBatchLoaderFactoryInitiator; import org.hibernate.reactive.provider.service.NativeParametersHandling; import org.hibernate.reactive.provider.service.NoJtaPlatformInitiator; import org.hibernate.reactive.provider.service.ReactiveMarkerServiceInitiator; @@ -127,6 +128,9 @@ public List> initialInitiatorList() { // Default implementation serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE); + // Custom for Hibernate Reactive: BatchLoaderFactory + serviceInitiators.add(ReactiveBatchLoaderFactoryInitiator.INSTANCE); + serviceInitiators.trimToSize(); return serviceInitiators; } diff --git a/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java b/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java index e0e455bb140a8..6b351c420710f 100644 --- a/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java +++ b/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java @@ -20,6 +20,27 @@ void build(BuildProducer reflectiveClass) { final String driverName = "org.postgresql.Driver"; reflectiveClass.produce(ReflectiveClassBuildItem.builder(driverName).build()); + // We want to register these postgresql "object" types since they are used by a driver to build result set elements + // and reflection is used to create their instances. While ORM might only use a `PGInterval` if a @JdbcType(PostgreSQLIntervalSecondJdbcType.class) + // is applied to a Duration property, we still register other types as users might create their own JdbcTypes that + // would rely on some subtype of a PGobject: + final String[] pgObjectClasses = new String[] { + "org.postgresql.util.PGobject", + "org.postgresql.util.PGInterval", + "org.postgresql.util.PGmoney", + "org.postgresql.geometric.PGbox", + "org.postgresql.geometric.PGcircle", + "org.postgresql.geometric.PGline", + "org.postgresql.geometric.PGlseg", + "org.postgresql.geometric.PGpath", + "org.postgresql.geometric.PGpoint", + "org.postgresql.geometric.PGpolygon", + // One more subtype of the PGobject, it doesn't look like that this one will be instantiated through reflection, + // so let's not include it: + // "org.postgresql.jdbc.PgResultSet.NullObject" + }; + reflectiveClass.produce(ReflectiveClassBuildItem.builder(pgObjectClasses).build()); + // Needed when quarkus.datasource.jdbc.transactions=xa for the setting of the username and password reflectiveClass.produce(ReflectiveClassBuildItem.builder("org.postgresql.ds.common.BaseDataSource").constructors(false) .methods().build()); diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java index d2ced26b18010..eef8ad985c913 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java @@ -561,14 +561,17 @@ public AdditionalBeanBuildItem kafkaClientBeans() { @BuildStep(onlyIf = IsDevelopment.class) @Record(ExecutionTime.RUNTIME_INIT) public void registerKafkaUiExecHandler( + Capabilities capabilities, BuildProducer routeProducer, KafkaUiRecorder recorder) { - routeProducer.produce(DevConsoleRouteBuildItem.builder() - .method("POST") - .handler(recorder.kafkaControlHandler()) - .path(KAFKA_ADMIN_PATH) - .bodyHandlerRequired() - .build()); + if (capabilities.isPresent(Capability.VERTX_HTTP)) { + routeProducer.produce(DevConsoleRouteBuildItem.builder() + .method("POST") + .handler(recorder.kafkaControlHandler()) + .path(KAFKA_ADMIN_PATH) + .bodyHandlerRequired() + .build()); + } } @BuildStep(onlyIf = IsDevelopment.class) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index 936cae8f81c75..cc3c9a1e6a142 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -124,7 +124,7 @@ private TenantConfigContext createStaticTenantContext(Vertx vertx, public TenantConfigContext apply(Throwable t) { if (t instanceof OIDCException) { LOG.warnf("Tenant '%s': '%s'." - + " OIDC server is not available yet, an attempt to connect will be made duiring the first request." + + " OIDC server is not available yet, an attempt to connect will be made during the first request." + " Access to resources protected by this tenant may fail" + " if OIDC server will not become available", tenantId, t.getMessage()); diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java index 9cf4ec222e5d2..683c154f93b49 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java @@ -10,6 +10,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Set; import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; @@ -51,8 +52,9 @@ public static Redis create(String name, Vertx vertx, RedisClientConfig config) { } } else if (config.hostsProviderName.isPresent()) { RedisHostsProvider hostsProvider = findProvider(config.hostsProviderName.get()); - hosts.addAll(hostsProvider.getHosts()); - for (URI uri : hostsProvider.getHosts()) { + Set computedHosts = hostsProvider.getHosts(); + hosts.addAll(computedHosts); + for (URI uri : computedHosts) { options.addConnectionString(uri.toString()); } } else { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java index 6b9774f5c1671..6c02c61cb12c1 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/ResponseHeaderTest.java @@ -13,6 +13,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import org.jboss.resteasy.reactive.ResponseHeader; import org.jboss.resteasy.reactive.ResponseStatus; @@ -23,6 +24,7 @@ import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import io.restassured.http.Headers; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -179,6 +181,50 @@ public void testReturnRestMulti3() { "header2", "h2")); } + @Test + public void testReturnRestMulti4() { + RestAssured + .given() + .get("/test/rest-multi2") + .then() + .statusCode(200) + .contentType(ContentType.TEXT) + .headers(Map.of( + "Access-Control-Allow-Origin", "foo", + "Keep-Alive", "bar")); + + RestAssured + .given() + .get("/test/rest-multi2?keepAlive=dummy") + .then() + .statusCode(200) + .contentType(ContentType.TEXT) + .headers(Map.of( + "Access-Control-Allow-Origin", "foo", + "Keep-Alive", "dummy")); + } + + @Test + public void testReturnRestMulti5() { + RestAssured + .given() + .get("/test/rest-multi3") + .then() + .statusCode(200) + .headers(Map.of( + "header1", "foo", + "header2", "bar")); + + RestAssured + .given() + .get("/test/rest-multi3?h1=h1&h2=h2") + .then() + .statusCode(200) + .headers(Map.of( + "header1", "h1", + "header2", "h2")); + } + @Path("/test") public static class TestResource { @@ -275,6 +321,21 @@ public RestMulti getTestRestMulti3(@DefaultValue("foo") @RestQuery("h1") return RestMulti.fromUniResponse(getWrapper(header1, header2), Wrapper::getData, Wrapper::getHeaders); } + @GET + @Path("/rest-multi4") + public RestMulti getTestRestMulti4(@DefaultValue("bar") @RestQuery String keepAlive) { + return RestMulti.fromMultiData(Multi.createFrom().item("test".getBytes(StandardCharsets.UTF_8))) + .header("Access-Control-Allow-Origin", "foo") + .header("Keep-Alive", keepAlive).header("Content-Type", MediaType.TEXT_PLAIN).build(); + } + + @GET + @Path("/rest-multi5") + public RestMulti getTestRestMulti5(@DefaultValue("foo") @RestQuery("h1") String header1, + @DefaultValue("bar") @RestQuery("h2") String header2) { + return RestMulti.fromUniResponse(getWrapper(header1, header2), Wrapper::getData, Wrapper::getHeaders); + } + private IllegalArgumentException createException() { IllegalArgumentException result = new IllegalArgumentException(); result.setStackTrace(EMPTY_STACK_TRACE); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/EntitySettingRequestFilterTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/EntitySettingRequestFilterTest.java new file mode 100644 index 0000000000000..18fe86069f6c7 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/EntitySettingRequestFilterTest.java @@ -0,0 +1,74 @@ +package io.quarkus.rest.client.reactive.provider; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.net.URI; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class EntitySettingRequestFilterTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(Resource.class, Client.class, EntityRequestFilter.class)); + + @TestHTTPResource + URI baseUri; + + @Test + void testNullEntity() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).register(new EntityRequestFilter(null)) + .build(Client.class); + assertThat(client.call("ignored")).isEqualTo("null"); + } + + @Test + void testNonNullEntity() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).register(new EntityRequestFilter("foo")) + .build(Client.class); + assertThat(client.call("ignored")).isEqualTo("foo"); + } + + @Path("/") + public static class Resource { + @POST + public String returnHeaders(String body) { + if ((body == null) || body.isEmpty()) { + return "null"; + } + return body; + } + } + + public interface Client { + + @Path("/") + @POST + String call(String body); + } + + public static class EntityRequestFilter implements ClientRequestFilter { + + private final String entity; + + public EntityRequestFilter(String entity) { + this.entity = entity; + } + + @Override + public void filter(ClientRequestContext context) throws IOException { + context.setEntity(entity); + } + } +} 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 89053a9dd37e6..3febad882c0f5 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 @@ -204,7 +204,8 @@ void registerNativeImageResources(BuildProducer servic serviceProvider.produce(ServiceProviderBuildItem.allProvidersFromClassPath(EventingService.class.getName())); // Add a condition for the optional eventing services - reflectiveClassCondition.produce(new ReflectiveClassConditionBuildItem(TracingService.class, "io.opentracing.Tracer")); + reflectiveClassCondition + .produce(new ReflectiveClassConditionBuildItem(TracingService.class, "io.opentelemetry.api.trace.Tracer")); // Use MicroProfile Config (We use the one from the CDI Module) serviceProvider.produce(ServiceProviderBuildItem.allProvidersFromClassPath(MicroProfileConfig.class.getName())); diff --git a/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeDiscoveryState.java b/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeDiscoveryState.java index 7e49c499fbafa..dbe9ede291e33 100644 --- a/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeDiscoveryState.java +++ b/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeDiscoveryState.java @@ -1,5 +1,7 @@ package io.quarkus.smallrye.reactivemessaging.kafka.deployment; +import static io.quarkus.smallrye.reactivemessaging.kafka.deployment.SmallRyeReactiveMessagingKafkaProcessor.getChannelPropertyKey; + import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -57,7 +59,7 @@ boolean isKafkaConnector(List channelsManagedB String channelType = incoming ? "incoming" : "outgoing"; return isKafkaConnector.computeIfAbsent(channelType + "|" + channelName, ignored -> { - String connectorKey = "mp.messaging." + channelType + "." + channelName + ".connector"; + String connectorKey = getChannelPropertyKey(channelName, "connector", incoming); String connector = getConfig() .getOptionalValue(connectorKey, String.class) .orElse("ignored"); diff --git a/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java b/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java index 97357ddb04df2..76e7c79739473 100644 --- a/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java +++ b/extensions/smallrye-reactive-messaging-kafka/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/SmallRyeReactiveMessagingKafkaProcessor.java @@ -101,6 +101,13 @@ private static List getChannelProperties(String keySuffix, Config config return values; } + static String channelPropertyFormat = "mp.messaging.%s.%s.%s"; + + static String getChannelPropertyKey(String channelName, String propertyName, boolean incoming) { + return String.format(channelPropertyFormat, incoming ? "incoming" : "outgoing", + channelName.contains(".") ? "\"" + channelName + "\"" : channelName, propertyName); + } + @BuildStep public void checkpointRedis(BuildProducer additionalBean, BuildProducer reflectiveClass, @@ -172,7 +179,7 @@ public void defaultChannelConfiguration( if (!discoveryState.isKafkaConnector(channelsManagedByConnectors, true, channelName)) { continue; } - String key = "mp.messaging.incoming." + channelName + ".graceful-shutdown"; + String key = getChannelPropertyKey(channelName, "graceful-shutdown", true); discoveryState.ifNotYetConfigured(key, () -> { defaultConfigProducer.produce(new RunTimeConfigurationDefaultBuildItem(key, "false")); }); @@ -214,12 +221,11 @@ void discoverDefaultSerdeConfig(DefaultSerdeDiscoveryState discovery, Type outgoingType = getOutgoingTypeFromMethod(method); processOutgoingType(discovery, outgoingType, (keySerializer, valueSerializer) -> { produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".key.serializer", keySerializer); + getChannelPropertyKey(channelName, "key.serializer", false), keySerializer); produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".value.serializer", valueSerializer); + getChannelPropertyKey(channelName, "value.serializer", false), valueSerializer); - handleAdditionalProperties("mp.messaging.outgoing." + channelName + ".", discovery, - config, keySerializer, valueSerializer); + handleAdditionalProperties(channelName, false, discovery, config, keySerializer, valueSerializer); }, generatedClass, reflection, alreadyGeneratedSerializers); } @@ -245,12 +251,11 @@ void discoverDefaultSerdeConfig(DefaultSerdeDiscoveryState discovery, Type outgoingType = getOutgoingTypeFromChannelInjectionPoint(injectionPointType); processOutgoingType(discovery, outgoingType, (keySerializer, valueSerializer) -> { produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".key.serializer", keySerializer); + getChannelPropertyKey(channelName, "key.serializer", false), keySerializer); produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".value.serializer", valueSerializer); + getChannelPropertyKey(channelName, "value.serializer", false), valueSerializer); - handleAdditionalProperties("mp.messaging.outgoing." + channelName + ".", discovery, - config, keySerializer, valueSerializer); + handleAdditionalProperties(channelName, false, discovery, config, keySerializer, valueSerializer); }, generatedClass, reflection, alreadyGeneratedSerializers); } } @@ -258,17 +263,17 @@ void discoverDefaultSerdeConfig(DefaultSerdeDiscoveryState discovery, private void processKafkaTransactions(DefaultSerdeDiscoveryState discovery, BuildProducer config, String channelName, Type injectionPointType) { if (injectionPointType != null && isKafkaEmitter(injectionPointType)) { + String transactionalIdKey = getChannelPropertyKey(channelName, "transactional.id", false); + String enableIdempotenceKey = getChannelPropertyKey(channelName, "enable.idempotence", false); + String acksKey = getChannelPropertyKey(channelName, "acks", false); LOGGER.infof("Transactional producer detected for channel '%s', setting following default config values: " - + "'mp.messaging.outgoing.%s.transactional.id=${quarkus.application.name}-${channelName}', " - + "'mp.messaging.outgoing.%s.enable.idempotence=true', " - + "'mp.messaging.outgoing.%s.acks=all'", channelName, channelName, channelName, channelName); - produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".transactional.id", + + "'" + transactionalIdKey + "=${quarkus.application.name}-${channelName}', " + + "'" + enableIdempotenceKey + "=true', " + + "'" + acksKey + "=all'", channelName); + produceRuntimeConfigurationDefaultBuildItem(discovery, config, transactionalIdKey, "${quarkus.application.name}-" + channelName); - produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".enable.idempotence", "true"); - produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.outgoing." + channelName + ".acks", "all"); + produceRuntimeConfigurationDefaultBuildItem(discovery, config, enableIdempotenceKey, "true"); + produceRuntimeConfigurationDefaultBuildItem(discovery, config, acksKey, "all"); } } @@ -282,16 +287,15 @@ private void processIncomingType(DefaultSerdeDiscoveryState discovery, alreadyGeneratedDeserializers); produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.incoming." + channelName + ".key.deserializer", keyDeserializer); + getChannelPropertyKey(channelName, "key.deserializer", true), keyDeserializer); produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.incoming." + channelName + ".value.deserializer", valueDeserializer); + getChannelPropertyKey(channelName, "value.deserializer", true), valueDeserializer); if (Boolean.TRUE.equals(isBatchType)) { produceRuntimeConfigurationDefaultBuildItem(discovery, config, - "mp.messaging.incoming." + channelName + ".batch", "true"); + getChannelPropertyKey(channelName, "batch", true), "true"); } - handleAdditionalProperties("mp.messaging.incoming." + channelName + ".", discovery, - config, keyDeserializer, valueDeserializer); + handleAdditionalProperties(channelName, true, discovery, config, keyDeserializer, valueDeserializer); }); } @@ -307,7 +311,7 @@ private Type getInjectionPointType(AnnotationInstance annotation) { } } - private void handleAdditionalProperties(String configPropertyBase, DefaultSerdeDiscoveryState discovery, + private void handleAdditionalProperties(String channelName, boolean incoming, DefaultSerdeDiscoveryState discovery, BuildProducer config, Result... results) { for (Result result : results) { if (result == null) { @@ -315,7 +319,8 @@ private void handleAdditionalProperties(String configPropertyBase, DefaultSerdeD } result.additionalProperties.forEach((key, value) -> { - produceRuntimeConfigurationDefaultBuildItem(discovery, config, configPropertyBase + key, value); + String configKey = getChannelPropertyKey(channelName, key, incoming); + produceRuntimeConfigurationDefaultBuildItem(discovery, config, configKey, value); }); } } @@ -938,7 +943,9 @@ private void processAnnotationsForReflectiveClassPayload(IndexView index, Config } private boolean isSerdeJson(IndexView index, Config config, String channelName, boolean serializer, boolean isKey) { - ConfigValue configValue = config.getConfigValue(getConfigName(channelName, serializer, isKey)); + String configKey = getChannelPropertyKey(channelName, (isKey ? "key" : "value") + "." + + (serializer ? "serializer" : "deserializer"), !serializer); + ConfigValue configValue = config.getConfigValue(configKey); if (configValue.getValue() != null) { DotName serdeName = DotName.createSimple(configValue.getValue()); return serializer ? isSubclassOfJsonSerializer(index, serdeName) : isSubclassOfJsonDeserializer(index, serdeName); @@ -946,14 +953,6 @@ private boolean isSerdeJson(IndexView index, Config config, String channelName, return false; } - String getConfigName(String channelName, boolean serializer, boolean isKey) { - return "mp.messaging." + - (serializer ? "outgoing" : "incoming") + "." + - channelName + "." + - (isKey ? "key" : "value") + "." + - (serializer ? "serializer" : "deserializer"); - } - private boolean isSubclassOfJsonSerializer(IndexView index, DotName serializerName) { return isSubclassOf(index, DotNames.OBJECT_MAPPER_SERIALIZER, serializerName) || isSubclassOf(index, DotNames.JSONB_SERIALIZER, serializerName); diff --git a/extensions/smallrye-reactive-messaging-kafka/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeConfigTest.java b/extensions/smallrye-reactive-messaging-kafka/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeConfigTest.java index 0f941cc60d1bd..70a6d866a7019 100644 --- a/extensions/smallrye-reactive-messaging-kafka/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeConfigTest.java +++ b/extensions/smallrye-reactive-messaging-kafka/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/kafka/deployment/DefaultSerdeConfigTest.java @@ -2727,5 +2727,32 @@ void method2(JsonObject msg) { } + @Test + void channelNameContainingDot() { + Tuple[] expectations = { + tuple("mp.messaging.incoming.\"new.channel\".key.deserializer", "org.apache.kafka.common.serialization.IntegerDeserializer"), + tuple("mp.messaging.incoming.\"new.channel\".value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"), + tuple("mp.messaging.outgoing.\"new.channel.out\".key.serializer", "org.apache.kafka.common.serialization.LongSerializer"), + tuple("mp.messaging.outgoing.\"new.channel.out\".value.serializer", "io.quarkus.kafka.client.serialization.JsonObjectSerializer"), + tuple("mp.messaging.outgoing.\"new.channel.out\".transactional.id", "${quarkus.application.name}-new.channel.out"), + tuple("mp.messaging.outgoing.\"new.channel.out\".enable.idempotence", "true"), + tuple("mp.messaging.outgoing.\"new.channel.out\".acks", "all"), + }; + doTest(expectations, ChannelContainingDot.class); + } + + + private static class ChannelContainingDot { + + @Incoming("new.channel") + void method1(KafkaRecord msg) { + + } + + @Channel("new.channel.out") + KafkaTransactions> transactions; + + } + } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java index 4f03b2b204568..3d278a51aa0da 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java @@ -2,26 +2,44 @@ import static io.quarkus.vertx.http.deployment.devmode.console.ConfigEditorProcessor.cleanUpAsciiDocIfNecessary; import static io.quarkus.vertx.http.deployment.devmode.console.ConfigEditorProcessor.isSetByDevServices; +import static io.smallrye.config.Expressions.withoutExpansion; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigSource; + import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; +import io.quarkus.dev.console.DevConsoleManager; import io.quarkus.devui.deployment.InternalPageBuildItem; +import io.quarkus.devui.runtime.config.ConfigJsonRPCService; +import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; import io.quarkus.devui.spi.page.Page; +import io.quarkus.vertx.http.deployment.devmode.console.ConfigEditorProcessor; import io.quarkus.vertx.http.runtime.devmode.ConfigDescription; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SmallRyeConfig; /** * This creates Extensions Page */ public class ConfigurationProcessor { + private static final String QUOTED_DOT = "\".\""; + private static final String QUOTED_DOT_KEY = "$$QUOTED_DOT$$"; + @BuildStep(onlyIf = IsDevelopment.class) InternalPageBuildItem createConfigurationPages(List configDescriptionBuildItems, Optional devServicesLauncherConfig) { @@ -46,6 +64,16 @@ InternalPageBuildItem createConfigurationPages(List return configurationPages; } + @BuildStep(onlyIf = IsDevelopment.class) + JsonRPCProvidersBuildItem registerJsonRpcService() { + DevConsoleManager.register("config-update-property", map -> { + Map values = Collections.singletonMap(map.get("name"), map.get("value")); + ConfigEditorProcessor.updateConfig(values, false); + return null; + }); + return new JsonRPCProvidersBuildItem("devui-configuration", ConfigJsonRPCService.class); + } + private List getAllConfig(List configDescriptionBuildItems, Optional devServicesLauncherConfig) { List configDescriptions = new ArrayList<>(); @@ -59,7 +87,228 @@ private List getAllConfig(List co item.getAllowedValues(), item.getConfigPhase().name())); } - return configDescriptions; + + Set devServicesConfig = new HashSet<>(); + if (devServicesLauncherConfig.isPresent()) { + devServicesConfig.addAll(devServicesLauncherConfig.get().getConfig().keySet()); + } + + return calculate(configDescriptions, devServicesConfig); + } + + private List calculate(List cd, Set devServicesProperties) { + List configDescriptions = new ArrayList<>(cd); + + List ordered = new ArrayList<>(); + List properties = new ArrayList<>(); + SmallRyeConfig current = (SmallRyeConfig) ConfigProvider.getConfig(); + + Map, Set> allPropertySegments = new HashMap<>(); + Set propertyNames = new HashSet<>(); + current.getPropertyNames().forEach(propertyNames::add); + for (String propertyName : propertyNames) { + propertyName = propertyName.replace(QUOTED_DOT, QUOTED_DOT_KEY); // Make sure dots can be quoted + String[] parts = propertyName.split("\\."); + + List accumulate = new ArrayList<>(); + //we never want to add the full string + //hence -1 + for (int i = 0; i < parts.length - 1; ++i) { + if (parts[i].isEmpty()) { + //this can't map to a quarkus prop as it has an empty segment + //so skip + break; + } + // If there was a quoted dot, put that back + if (parts[i].contains(QUOTED_DOT_KEY)) { + parts[i] = parts[i].replaceAll(QUOTED_DOT_KEY, QUOTED_DOT); + } + + accumulate.add(parts[i]); + //if there is both a quoted and unquoted version we only want to apply the quoted version + //and remove the unquoted one + Set potentialSegmentSet = allPropertySegments.computeIfAbsent(List.copyOf(accumulate), + (k) -> new HashSet<>()); + if (isQuoted(parts[i + 1])) { + potentialSegmentSet.add(parts[i + 1]); + potentialSegmentSet.remove(parts[i + 1].substring(1, parts[i + 1].length() - 1)); + } else { + if (!potentialSegmentSet.contains(ensureQuoted(parts[i + 1]))) { + potentialSegmentSet.add(parts[i + 1]); + } + } + + } + } + + Map, Set> wildcardsToAdd = new HashMap<>(); + Map foundItems = new HashMap<>(); + Set bannedExpansionCombos = new HashSet<>(); + //we iterate over every config description + for (ConfigDescription item : configDescriptions) { + //if they are a non-wildcard description we just add them directly + if (!item.getName().contains("{*}")) { + //we don't want to accidentally use these properties as name expansions + //we ban them which means that the only way the name can be expanded into a map + //is if it is quoted + bannedExpansionCombos.add(item.getName()); + for (int i = 0; i < item.getName().length(); ++i) { + //add all possible segments to the banned list + if (item.getName().charAt(i) == '.') { + bannedExpansionCombos.add(item.getName().substring(0, i)); + } + } + properties.add(item.getName()); + item.setConfigValue(getConfigValue(current, item.getName())); + ordered.add(item); + } else if (!item.getName().startsWith("quarkus.log.filter")) { //special case, we use this internally and we don't want it clogging up the editor + //we need to figure out how to expand it + //this can have multiple stars + List> componentParts = new ArrayList<>(); + List accumulator = new ArrayList<>(); + //keys that were used to expand, checked against the banned list before adding + for (var i : item.getName().split("\\.")) { + if (i.equals("{*}")) { + componentParts.add(accumulator); + accumulator = new ArrayList<>(); + } else { + accumulator.add(i); + } + } + //note that accumulator is still holding the final part + //we need it later, but we don't want it in this loop + Map, Set> building = new HashMap<>(); + building.put(List.of(), new HashSet<>()); + for (List currentPart : componentParts) { + Map, Set> newBuilding = new HashMap<>(); + for (Map.Entry, Set> entry : building.entrySet()) { + List attempt = entry.getKey(); + List newBase = new ArrayList<>(attempt); + newBase.addAll(currentPart); + wildcardsToAdd.put(newBase, entry.getValue()); + Set potential = allPropertySegments.get(newBase); + if (potential != null) { + bannedExpansionCombos.add(String.join(".", newBase).replace("\"", "")); + for (String definedName : potential) { + List toAdd = new ArrayList<>(newBase); + toAdd.add(definedName); + //for expansion keys we always use unquoted values, same with banned + //so we are always comparing unquoted + Set expansionKeys = new HashSet<>(entry.getValue()); + expansionKeys.add(String.join(".", newBase) + "." + definedName); + newBuilding.put(toAdd, expansionKeys); + } + } + } + building = newBuilding; + } + //now we have our config properties + for (var entry : building.entrySet()) { + List segments = entry.getKey(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < segments.size(); ++i) { + if (i > 0) { + sb.append("."); + } + sb.append(segments.get(i)); + } + //accumulator holds the find string + for (String s : accumulator) { + sb.append(".").append(s); + } + String expandedName = sb.toString(); + foundItems.put(expandedName, new Holder(entry.getValue(), item)); + } + } + } + for (Map.Entry e : foundItems.entrySet()) { + boolean ok = true; + for (String key : e.getValue().expansionKeys) { + if (bannedExpansionCombos.contains(key)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + String expandedName = e.getKey(); + var item = e.getValue().configDescription; + ConfigDescription newDesc = new ConfigDescription(expandedName, item.getDescription(), + item.getDefaultValue(), devServicesProperties.contains(expandedName), item.getTypeName(), + item.getAllowedValues(), + item.getConfigPhase()); + + properties.add(newDesc.getName()); + newDesc.setConfigValue(getConfigValue(current, newDesc.getName())); + ordered.add(newDesc); + } + + //now add our star properties + for (var entry : wildcardsToAdd.entrySet()) { + boolean ok = true; + for (String key : entry.getValue()) { + if (bannedExpansionCombos.contains(key)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + List segments = entry.getKey(); + StringBuilder sb = new StringBuilder(); + for (String segment : segments) { + sb.append(segment); + sb.append("."); + } + String expandedName = sb.toString(); + ConfigDescription newDesc = new ConfigDescription(expandedName, true); + + properties.add(newDesc.getName()); + newDesc.setConfigValue(getConfigValue(current, newDesc.getName())); + ordered.add(newDesc); + } + + for (ConfigSource configSource : current.getConfigSources()) { + if (configSource.getName().equals("PropertiesConfigSource[source=Build system]")) { + properties.addAll(configSource.getPropertyNames()); + } + } + + withoutExpansion(() -> { + for (String propertyName : current.getPropertyNames()) { + if (properties.contains(propertyName)) { + continue; + } + + ConfigDescription item = new ConfigDescription(propertyName, null, null, getConfigValue(current, propertyName)); + ordered.add(item); + + configDescriptions.add(item); + } + }); + + return ordered; + } + + private ConfigValue getConfigValue(SmallRyeConfig config, String name) { + try { + return config.getConfigValue(name); + } catch (java.util.NoSuchElementException nse) { + return null; + } + } + + private String ensureQuoted(String part) { + if (isQuoted(part)) { + return part; + } + return "\"" + part + "\""; + } + + private boolean isQuoted(String part) { + return part.length() >= 2 && part.charAt(0) == '\"' && part.charAt(part.length() - 1) == '\"'; } private static final Pattern codePattern = Pattern.compile("(\\{@code )([^}]+)(\\})"); @@ -77,4 +326,14 @@ static String formatJavadoc(String val) { val = val.replace("@deprecated", "
Deprecated"); return val; } + + static class Holder { + final Set expansionKeys; + final ConfigDescription configDescription; + + private Holder(Set expansionKeys, ConfigDescription configDescription) { + this.expansionKeys = expansionKeys; + this.configDescription = configDescription; + } + } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java index 07c324325043b..63be94a20825c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java @@ -8,7 +8,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -44,8 +43,6 @@ import io.quarkus.devconsole.runtime.spi.DevConsolePostHandler; import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; -import io.quarkus.devui.runtime.config.ConfigJsonRPCService; -import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; import io.quarkus.vertx.http.runtime.devmode.ConfigDescription; import io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsManager; import io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsRecorder; @@ -120,7 +117,6 @@ protected void handlePost(RoutingContext event, MultiMap form) throws Exception updateConfig(autoconfig); } else if (action.equals("updateProperties")) { - Map properties = new LinkedHashMap<>(); String values = event.request().getFormAttribute("values"); setConfig(values); } @@ -154,16 +150,6 @@ void handleRequests(BuildProducer devConsoleRouteProdu })); } - @BuildStep(onlyIf = IsDevelopment.class) - JsonRPCProvidersBuildItem registerJsonRpcService() { - DevConsoleManager.register("config-update-property", map -> { - Map values = Collections.singletonMap(map.get("name"), map.get("value")); - updateConfig(values); - return null; - }); - return new JsonRPCProvidersBuildItem("devui-configuration", ConfigJsonRPCService.class); - } - private Map filterAndApplyProfile(Map autoconfig, List configFilter, String profile) { return autoconfig.entrySet().stream() @@ -221,6 +207,10 @@ static byte[] getConfig() { } public static void updateConfig(Map values) { + updateConfig(values, true); + } + + public static void updateConfig(Map values, boolean preventKill) { if (values != null && !values.isEmpty()) { try { Path configPath = getConfigPath(); @@ -255,7 +245,8 @@ public static void updateConfig(Map values) { writer.newLine(); } } - preventKill(); + if (preventKill) + preventKill(); } catch (Throwable t) { throw new RuntimeException(t); } @@ -263,6 +254,10 @@ public static void updateConfig(Map values) { } static void setConfig(String value) { + setConfig(value, true); + } + + static void setConfig(String value, boolean preventKill) { try { Path configPath = getConfigPath(); try (BufferedWriter writer = Files.newBufferedWriter(configPath)) { @@ -272,7 +267,8 @@ static void setConfig(String value) { writer.write(value); } } - preventKill(); + if (preventKill) + preventKill(); } catch (Throwable t) { throw new RuntimeException(t); } diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/jsonrpc.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/jsonrpc.js index 0a097a92baa9f..c41215df3952d 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/jsonrpc.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/controller/jsonrpc.js @@ -249,10 +249,11 @@ export class JsonRpc { if (messageType === MessageType.Void.toString()) { // Void response, typically used on initial subscription // Do nothing - } else if (messageType === MessageType.HotReload.toString()){ - connectionState.hotreload(JsonRpc.serverUri); - connectionState.connected(JsonRpc.serverUri) - } else if (messageType === MessageType.Response.toString()) { // Normal Request-Response + } else if (messageType === MessageType.HotReload.toString() || messageType === MessageType.Response.toString()) { + if (messageType === MessageType.HotReload.toString()){ + connectionState.hotreload(JsonRpc.serverUri); + connectionState.connected(JsonRpc.serverUri); + } if (JsonRpc.promiseQueue.has(response.id)) { var saved = JsonRpc.promiseQueue.get(response.id); var promise = saved.promise; diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js index 88f8a1b92a742..89e1fa0f44ea1 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js @@ -37,6 +37,12 @@ export class QwcConfiguration extends observeState(LitElement) { overflow: hidden; } + .confTopBar { + display: flex; + justify-content: space-between; + align-items: center; + } + vaadin-grid { height: 100%; } @@ -82,10 +88,13 @@ export class QwcConfiguration extends observeState(LitElement) { `; static properties = { - _filtered: {state: true, type: Array}, + _filtered: {state: true, type: Array}, // Filter the visible configuration + _visibleConfiguration: {state: true, type: Array}, // Either all or just user's configuration _values: {state: true}, _detailsOpenedItem: {state: true, type: Array}, _busy: {state: true}, + _showOnlyOwnProperties: {state: true}, + _searchTerm: {state: true} }; constructor() { @@ -96,13 +105,16 @@ export class QwcConfiguration extends observeState(LitElement) { if(this._filteredValue){ this._filteredValue = this._filteredValue.replaceAll(",", " OR "); } - - this._filtered = devuiState.allConfiguration; + this._visibleConfiguration = devuiState.allConfiguration; + this._filtered = this._visibleConfiguration; this.jsonRpc.getAllValues().then(e => { this._values = e.result; }); this._detailsOpenedItem = []; this._busy = null; + + this._showOnlyOwnProperties = false; + this._searchTerm = ''; } render() { @@ -132,31 +144,56 @@ export class QwcConfiguration extends observeState(LitElement) { } _filterTextChanged(e) { - const searchTerm = (e.detail.value || '').trim(); - if (searchTerm === '') { - this._filtered = devuiState.allConfiguration + this._searchTerm = (e.detail.value || '').trim(); + return this._filterGrid(); + } + + _filterGrid(){ + if (this._searchTerm === '') { + this._filtered = this._visibleConfiguration; return; } - this._filtered = devuiState.allConfiguration.filter((prop) => { - return this._match(prop.name, searchTerm) || this._match(prop.description, searchTerm) + this._filtered = this._visibleConfiguration.filter((prop) => { + return this._match(prop.name, this._searchTerm) || this._match(prop.description, this._searchTerm) }); } _render() { return html`
- - - ${this._filtered.length} - +
+ + + ${this._filtered.length} + + + +
${this._renderGrid()}
`; } + _toggleShowOnlyOwnProperties(onlyMine){ + this._showOnlyOwnProperties = onlyMine; + if(this._showOnlyOwnProperties){ + this._visibleConfiguration = devuiState.allConfiguration.filter((prop) => { + return (prop.configValue.sourceName && prop.configValue.sourceName.startsWith("PropertiesConfigSource[source") + && prop.configValue.sourceName.endsWith("/application.properties]")); + }); + }else { + this._visibleConfiguration = devuiState.allConfiguration; + } + return this._filterGrid(); + } + _renderGrid(){ if(this._busy){ return html`${this._renderStyledGrid("disabledDatatable")}`; @@ -211,7 +248,7 @@ export class QwcConfiguration extends observeState(LitElement) { - ` + `; } if (prop.wildcardEntry) { @@ -219,7 +256,7 @@ export class QwcConfiguration extends observeState(LitElement) { - ` + `; } return html` @@ -339,12 +376,13 @@ export class QwcConfiguration extends observeState(LitElement) { if (prop.defaultValue) { def = "Default value: " + prop.defaultValue; } - + let src = "Config source: " + prop.configValue.sourceName; return html`

${unsafeHTML(prop.description)}

Environment variable: ${res}
- ${unsafeHTML(def)} + ${unsafeHTML(def)}
+ ${unsafeHTML(src)}
`; } diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js index d6162d2d9b7c9..d1fe10a11e701 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js @@ -141,15 +141,23 @@ export class QwcExtension extends LitElement { _footerTemplate() { return html` `; } + _renderConfigFilterIcon(){ + if(this.configFilter){ + return html` + + `; + }else{ + return html``; + } + } + _renderStatus(){ var l = this._statusLevelOnCard(); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcMessage.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcMessage.java new file mode 100644 index 0000000000000..2e7428a72cc93 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcMessage.java @@ -0,0 +1,35 @@ +package io.quarkus.devui.runtime.comms; + +/** + * Allows JSON RPC methods to response with more finer grade message types + * + * @param The type of the response object + */ +public class JsonRpcMessage { + private T response; + private MessageType messageType; + + public JsonRpcMessage() { + } + + public JsonRpcMessage(T response, MessageType messageType) { + this.response = response; + this.messageType = messageType; + } + + public T getResponse() { + return response; + } + + public void setResponse(T response) { + this.response = response; + } + + public MessageType getMessageType() { + return messageType; + } + + public void setMessageType(MessageType messageType) { + this.messageType = messageType; + } +} \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java index b55dc0e71fe39..310437b29257a 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java @@ -1,7 +1,5 @@ package io.quarkus.devui.runtime.comms; -import static io.quarkus.devui.runtime.jsonrpc.JsonRpcKeys.MessageType; - import java.lang.reflect.Method; import java.time.LocalDateTime; import java.util.ArrayList; @@ -199,8 +197,14 @@ private void route(JsonRpcRequest jsonRpcRequest, ServerWebSocket s) { } uni.subscribe() .with(item -> { - codec.writeResponse(s, jsonRpcRequest.getId(), item, - MessageType.Response); + if (item != null && JsonRpcMessage.class.isAssignableFrom(item.getClass())) { + JsonRpcMessage jsonRpcMessage = (JsonRpcMessage) item; + codec.writeResponse(s, jsonRpcRequest.getId(), jsonRpcMessage.getResponse(), + jsonRpcMessage.getMessageType()); + } else { + codec.writeResponse(s, jsonRpcRequest.getId(), item, + MessageType.Response); + } }, failure -> { codec.writeErrorResponse(s, jsonRpcRequest.getId(), jsonRpcMethodName, failure); }); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/MessageType.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/MessageType.java new file mode 100644 index 0000000000000..b6856874b052d --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/MessageType.java @@ -0,0 +1,8 @@ +package io.quarkus.devui.runtime.comms; + +public enum MessageType { + Void, + Response, + SubscriptionMessage, + HotReload +} \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/config/ConfigJsonRPCService.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/config/ConfigJsonRPCService.java index bd00385cf577d..04c296e282b75 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/config/ConfigJsonRPCService.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/config/ConfigJsonRPCService.java @@ -8,14 +8,16 @@ import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.devui.runtime.comms.JsonRpcMessage; +import io.quarkus.devui.runtime.comms.MessageType; import io.vertx.core.json.JsonObject; @ApplicationScoped public class ConfigJsonRPCService { - public boolean updateProperty(String name, String value) { + public JsonRpcMessage updateProperty(String name, String value) { DevConsoleManager.invoke("config-update-property", Map.of("name", name, "value", value)); - return true; + return new JsonRpcMessage(true, MessageType.HotReload); } public JsonObject getAllValues() { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcCodec.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcCodec.java index d7c1c601233ef..662d8e6a2e7c6 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcCodec.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcCodec.java @@ -2,8 +2,8 @@ import static io.quarkus.devui.runtime.jsonrpc.JsonRpcKeys.INTERNAL_ERROR; import static io.quarkus.devui.runtime.jsonrpc.JsonRpcKeys.METHOD_NOT_FOUND; -import static io.quarkus.devui.runtime.jsonrpc.JsonRpcKeys.MessageType; +import io.quarkus.devui.runtime.comms.MessageType; import io.quarkus.devui.runtime.jsonrpc.json.JsonMapper; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.json.JsonObject; diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcKeys.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcKeys.java index d027420f55492..0902b35c208b4 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcKeys.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/jsonrpc/JsonRpcKeys.java @@ -20,10 +20,4 @@ public interface JsonRpcKeys { public static final int INVALID_PARAMS = -32602; // Invalid params. Invalid method parameter(s). public static final int INTERNAL_ERROR = -32603; // Internal error. Internal JSON-RPC error. - public static enum MessageType { - Void, - Response, - SubscriptionMessage, - HotReload - } } \ No newline at end of file diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 6f1ef3fff22fe..f8c392808f881 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -57,7 +57,7 @@ 4.4.16 4.5.14 1.0.0.Final - 2.14.2 + 2.15.2 2.1.1 4.0.1 2.0.1 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index c9be0f5ffb148..4090afc342ebc 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -42,7 +42,7 @@ 3.0.0 1.6.8 3.8.1 - 2.15.0 + 2.15.2 5.9.3 diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java index eb653ee587544..f1b2017f27c82 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientRequestContextImpl.java @@ -171,8 +171,12 @@ public Locale getLanguage() { @Override public MediaType getMediaType() { + if (restClientRequestContext.entity == null) { + return null; + } // those come from the entity - return restClientRequestContext.entity != null ? restClientRequestContext.entity.getMediaType() : null; + return restClientRequestContext.entity.getMediaType() == RestClientRequestContext.IGNORED_MEDIA_TYPE ? null + : restClientRequestContext.entity.getMediaType(); } @Override @@ -515,12 +519,8 @@ private boolean isContentType(Object key) { } private String mediaType() { - Entity entity = restClientRequestContext.entity; - return entity == null - ? null - : entity.getMediaType() == null - ? null - : entity.getMediaType().toString(); + MediaType mediaType = getMediaType(); + return mediaType == null ? null : mediaType.toString(); } } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java index 59e89940f428c..3fb35e41152e7 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java @@ -60,6 +60,8 @@ public class RestClientRequestContext extends AbstractResteasyReactiveContext entity, MultivaluedMap heade } public void setEntity(Object entity, Annotation[] annotations, MediaType mediaType) { - this.entity = Entity.entity(entity, mediaType, annotations); + if (entity == null) { + this.entity = null; + } else { + if (mediaType == null) { + mediaType = IGNORED_MEDIA_TYPE; // we need this in order to avoid getting IAE from Variant... + } + this.entity = Entity.entity(entity, mediaType, annotations); + } } public CompletableFuture getResult() { diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 061fa8f88bf4f..a44524ec2eef7 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -67,7 +67,7 @@ 4.4.1 5.3.0 1.0.0.Final - 2.15.0 + 2.15.2 2.1.0 3.0.2 3.0.3 diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java index 12c8f2722e74c..a6aa28fd1e632 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java @@ -40,6 +40,9 @@ public class PublisherResponseHandler implements ServerRestHandler { private static final String JSON = "json"; + private static final ServerMediaType REST_MULTI_DEFAULT_SERVER_MEDIA_TYPE = new ServerMediaType( + List.of(MediaType.APPLICATION_OCTET_STREAM_TYPE), StandardCharsets.UTF_8.name(), false); + private List streamingResponseCustomizers = Collections.emptyList(); public void setStreamingResponseCustomizers(List streamingResponseCustomizers) { @@ -301,8 +304,13 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti // or make this SSE produce build-time ServerMediaType produces = requestContext.getTarget().getProduces(); if (produces == null) { - throw new IllegalStateException( - "Negotiation or dynamic media type not supported yet for Multi: please use the @Produces annotation when returning a Multi"); + if (result instanceof RestMulti) { + produces = REST_MULTI_DEFAULT_SERVER_MEDIA_TYPE; + } else { + throw new IllegalStateException( + "Negotiation or dynamic media type not supported yet for Multi: please use the @Produces annotation when returning a Multi"); + } + } MediaType[] mediaTypes = produces.getSortedOriginalMediaTypes(); if (mediaTypes.length != 1) { diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 2b02c489716d0..73c940418aeda 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -53,7 +53,7 @@ 3.24.2 - 2.15.0 + 2.15.2 4.0.1 5.9.3 1.23.0 diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index 6b2af30915ad4..0bc9b7d02b6e7 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.time.Duration; import java.util.List; import java.util.UUID; @@ -135,6 +136,7 @@ private static void persistNewPerson(EntityManager entityManager, String name) { person.setName(name); person.setStatus(Status.LIVING); person.setAddress(new SequencedAddress("Street " + randomName())); + person.setLatestLunchBreakDuration(Duration.ofMinutes(30)); entityManager.persist(person); } diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java index 0393fd951f115..f8c82d346ee3f 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java @@ -1,6 +1,9 @@ package io.quarkus.it.jpa.postgresql; +import java.time.Duration; + import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -10,6 +13,9 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.PostgreSQLIntervalSecondJdbcType; + @Entity @Table(schema = "myschema") @NamedQuery(name = "get_person_by_name", query = "select p from Person p where name = :name") @@ -19,6 +25,7 @@ public class Person { private String name; private SequencedAddress address; private Status status; + private Duration latestLunchBreakDuration = Duration.ZERO; public Person() { } @@ -64,8 +71,27 @@ public void setStatus(Status status) { this.status = status; } + /** + * Need to explicitly set the scale (and the precision so that the scale will actually be read from the annotation). + * Postgresql would only allow maximum scale of 6 for a `interval second`. + * + * @see org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType + */ + @Column(precision = 5, scale = 5) + //NOTE: while https://hibernate.atlassian.net/browse/HHH-16591 is open we cannot replace the currently used @JdbcType annotation + // with a @JdbcTypeCode( INTERVAL_SECOND ) + @JdbcType(PostgreSQLIntervalSecondJdbcType.class) + public Duration getLatestLunchBreakDuration() { + return latestLunchBreakDuration; + } + + public void setLatestLunchBreakDuration(Duration duration) { + this.latestLunchBreakDuration = duration; + } + public void describeFully(StringBuilder sb) { sb.append("Person with id=").append(id).append(", name='").append(name).append("', status='").append(status) + .append("', latestLunchBreakDuration='").append(latestLunchBreakDuration) .append("', address { "); getAddress().describeFully(sb); sb.append(" }"); diff --git a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java index c7273df4b8f4f..faa34417fa478 100644 --- a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java +++ b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java @@ -123,6 +123,9 @@ public byte[] apply(String className, byte[] bytes) { private void addProjectModule(ResolvedDependency module, JacocoConfig config, ReportInfo info, String includes, String excludes, Set classes, Set sources) throws Exception { info.savedData.add(new File(module.getWorkspaceModule().getBuildDir(), config.dataFile).getAbsolutePath()); + if (module.getSources() == null) { + return; + } for (SourceDir src : module.getSources().getSourceDirs()) { for (Path p : src.getSourceTree().getRoots()) { sources.add(p.toAbsolutePath().toString());