>.
-The JAXB Resteasy Reactive extension will automatically detect the classes that are used in the resources and require JAXB serialization. Then, it will register these classes into the default `JAXBContext` which is internally used by the JAXB message reader and writer.
+The JAXB Resteasy Reactive extension will automatically detect the classes that are used in the resources and require JAXB serialization. Then, it will register these classes into the default `JAXBContext` which is internally used by the JAXB message reader and writer.
-However, in some situations, these classes cause the `JAXBContext` to fail: for example, when you're using the same class name in different java packages. In these cases, the application will fail at build time and print the JAXB exception that caused the issue, so you can properly fix it. Alternatively, you can also exclude the classes that cause the issue by using the property `quarkus.jaxb.exclude-classes`. When excluding classes that are required by any resource, the JAXB resteasy reactive extension will create and cache a custom `JAXBContext` that will include the excluded class, causing a minimal performance degradance.
+However, in some situations, these classes cause the `JAXBContext` to fail: for example, when you're using the same class name in different java packages. In these cases, the application will fail at build time and print the JAXB exception that caused the issue, so you can properly fix it. Alternatively, you can also exclude the classes that cause the issue by using the property `quarkus.jaxb.exclude-classes`. When excluding classes that are required by any resource, the JAXB resteasy reactive extension will create and cache a custom `JAXBContext` that will include the excluded class, causing a minimal performance degradance.
[NOTE]
====
-The property `quarkus.jaxb.exclude-classes` accepts a comma separated list of fully qualified class names, for example: `quarkus.jaxb.exclude-classes=org.acme.one.Model,org.acme.two.Model`.
+The property `quarkus.jaxb.exclude-classes` accepts a comma separated list of either fully qualified class names
+or package names. Package names must be suffixed by `.*` and all classes in the specified package and its subpackages will be excluded.
+
+For instance, when setting `quarkus.jaxb.exclude-classes=org.acme.one.Model,org.acme.two.Model,org.acme.somemodel.*`, the following elements are excluded:
+
+- The class `org.acme.one.Model`
+- The class `org.acme.two.Model`
+- All the classes in the `org.acme.somemodel` package and its subpackages
====
==== Advanced JAXB-specific features
@@ -1604,7 +1609,6 @@ Note that if you provide your custom JAXB context instance, you will need to reg
To enable Web Links support, add the `quarkus-resteasy-reactive-links` extension to your project.
-.Table Context object
|===
|GAV|Usage
@@ -1720,7 +1724,6 @@ The https://tools.ietf.org/id/draft-kelly-json-hal-01.html[HAL] standard is a si
To enable the HAL support, add the `quarkus-hal` extension to your project. Also, as HAL needs JSON support, you need to add either the `quarkus-resteasy-reactive-jsonb` or the `quarkus-resteasy-reactive-jackson` extension.
-.Table Context object
|===
|GAV|Usage
@@ -2100,7 +2103,7 @@ You can also declare link:{jaxrsspec}#exceptionmapper[exception mappers in the J
Your exception mapper may declare any of the following parameter types:
-.Table Exception mapper parameters
+.Exception mapper parameters
|===
|Type|Usage
@@ -2117,7 +2120,7 @@ Your exception mapper may declare any of the following parameter types:
It may declare any of the following return types:
-.Table Exception mapper return types
+.Exception mapper return types
|===
|Type|Usage
@@ -2217,7 +2220,7 @@ class Filters {
Your filters may declare any of the following parameter types:
-.Table Filter parameters
+.Filter parameters
|===
|Type|Usage
@@ -2237,7 +2240,7 @@ Your filters may declare any of the following parameter types:
It may declare any of the following return types:
-.Table Filter return types
+.Filter return types
|===
|Type|Usage
diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java
index d7966a647e460..f7a7040fe886d 100644
--- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java
+++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java
@@ -239,7 +239,7 @@ void generateDataSourceSupportBean(AgroalRecorder recorder,
.setDefaultScope(DotNames.SINGLETON).build());
// add the @DataSource class otherwise it won't be registered as a qualifier
additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(DataSource.class).build());
- // make source datasources are initialized at startup
+ // make sure datasources are initialized at startup
additionalBeans.produce(new AdditionalBeanBuildItem(AgroalDataSourcesInitializer.class));
// make AgroalPoolInterceptor beans unremovable, users still have to make them beans
diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java
index bff38eee02e67..341e2cca19966 100644
--- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java
+++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java
@@ -113,7 +113,10 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig,
* (which makes sense because {@code DataSource} is a {@code Singleton} bean).
*
* This method is thread-safe
+ *
+ * @deprecated This method should not be used as it can very easily lead to timing issues during bean creation
*/
+ @Deprecated
public static AgroalDataSource fromName(String dataSourceName) {
return Arc.container().instance(DataSources.class).get()
.getDataSource(dataSourceName);
diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java
index 412b185a89f26..1fb06fce2b9c2 100644
--- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java
+++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java
@@ -18,18 +18,21 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Default;
+import jakarta.inject.Singleton;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.Location;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.migration.JavaMigration;
import org.flywaydb.core.extensibility.Plugin;
+import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.logging.Logger;
+import io.quarkus.agroal.runtime.DataSources;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem;
import io.quarkus.agroal.spi.JdbcInitialSQLGeneratorBuildItem;
@@ -62,6 +65,7 @@
import io.quarkus.deployment.logging.LoggingSetupBuildItem;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.flyway.runtime.FlywayBuildTimeConfig;
+import io.quarkus.flyway.runtime.FlywayContainer;
import io.quarkus.flyway.runtime.FlywayContainerProducer;
import io.quarkus.flyway.runtime.FlywayRecorder;
import io.quarkus.flyway.runtime.FlywayRuntimeConfig;
@@ -71,6 +75,7 @@ class FlywayProcessor {
private static final String CLASSPATH_APPLICATION_MIGRATIONS_PROTOCOL = "classpath";
+ private static final String FLYWAY_CONTAINER_BEAN_NAME_PREFIX = "flyway_container_";
private static final String FLYWAY_BEAN_NAME_PREFIX = "flyway_";
private static final DotName JAVA_MIGRATION = DotName.createSimple(JavaMigration.class.getName());
@@ -172,8 +177,6 @@ void createBeans(FlywayRecorder recorder,
// add the @FlywayDataSource class otherwise it won't be registered as a qualifier
additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(FlywayDataSource.class).build());
- recorder.resetFlywayContainers();
-
Collection dataSourceNames = getDataSourceNames(jdbcDataSourceBuildItems);
for (String dataSourceName : dataSourceNames) {
@@ -182,25 +185,62 @@ void createBeans(FlywayRecorder recorder,
if (!hasMigrations) {
createPossible = sqlGeneratorBuildItems.stream().anyMatch(s -> s.getDatabaseName().equals(dataSourceName));
}
- SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
+
+ SyntheticBeanBuildItem.ExtendedBeanConfigurator flywayContainerConfigurator = SyntheticBeanBuildItem
+ .configure(FlywayContainer.class)
+ .scope(Singleton.class)
+ .setRuntimeInit()
+ .unremovable()
+ .addInjectionPoint(ClassType.create(DotName.createSimple(FlywayContainerProducer.class)))
+ .addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class)))
+ .createWith(recorder.flywayContainerFunction(dataSourceName, hasMigrations, createPossible));
+
+ AnnotationInstance flywayContainerQualifier;
+
+ if (DataSourceUtil.isDefault(dataSourceName)) {
+ flywayContainerConfigurator.addQualifier(Default.class);
+
+ // Flyway containers used to be ordered with the default database coming first.
+ // Some multitenant tests are relying on this order.
+ flywayContainerConfigurator.priority(10);
+
+ flywayContainerQualifier = AnnotationInstance.builder(Default.class).build();
+ } else {
+ String beanName = FLYWAY_CONTAINER_BEAN_NAME_PREFIX + dataSourceName;
+ flywayContainerConfigurator.name(beanName);
+
+ flywayContainerConfigurator.addQualifier().annotation(DotNames.NAMED).addValue("value", beanName).done();
+ flywayContainerConfigurator.addQualifier().annotation(FlywayDataSource.class).addValue("value", dataSourceName)
+ .done();
+ flywayContainerConfigurator.priority(5);
+
+ flywayContainerQualifier = AnnotationInstance.builder(FlywayDataSource.class).add("value", dataSourceName)
+ .build();
+ }
+
+ syntheticBeanBuildItemBuildProducer.produce(flywayContainerConfigurator.done());
+
+ SyntheticBeanBuildItem.ExtendedBeanConfigurator flywayConfigurator = SyntheticBeanBuildItem
.configure(Flyway.class)
- .scope(Dependent.class) // this is what the existing code does, but it doesn't seem reasonable
+ .scope(Singleton.class)
.setRuntimeInit()
.unremovable()
- .supplier(recorder.flywaySupplier(dataSourceName,
- hasMigrations, createPossible));
+ .addInjectionPoint(ClassType.create(DotName.createSimple(FlywayContainer.class)), flywayContainerQualifier)
+ .createWith(recorder.flywayFunction(dataSourceName));
if (DataSourceUtil.isDefault(dataSourceName)) {
- configurator.addQualifier(Default.class);
+ flywayConfigurator.addQualifier(Default.class);
+ flywayConfigurator.priority(10);
} else {
String beanName = FLYWAY_BEAN_NAME_PREFIX + dataSourceName;
- configurator.name(beanName);
+ flywayConfigurator.name(beanName);
+ flywayConfigurator.priority(5);
- configurator.addQualifier().annotation(DotNames.NAMED).addValue("value", beanName).done();
- configurator.addQualifier().annotation(FlywayDataSource.class).addValue("value", dataSourceName).done();
+ flywayConfigurator.addQualifier().annotation(DotNames.NAMED).addValue("value", beanName).done();
+ flywayConfigurator.addQualifier().annotation(FlywayDataSource.class).addValue("value", dataSourceName).done();
}
- syntheticBeanBuildItemBuildProducer.produce(configurator.done());
+ syntheticBeanBuildItemBuildProducer.produce(flywayConfigurator.done());
}
}
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainersSupplier.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainersSupplier.java
index 47537af91e9bd..11261c88c30a1 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainersSupplier.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainersSupplier.java
@@ -1,24 +1,30 @@
package io.quarkus.flyway.runtime;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Supplier;
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
public class FlywayContainersSupplier implements Supplier> {
@Override
public Collection get() {
- if (FlywayRecorder.FLYWAY_CONTAINERS.isEmpty()) {
- return Collections.emptySet();
+ List> flywayContainerHandles = Arc.container().listAll(FlywayContainer.class);
+
+ if (flywayContainerHandles.isEmpty()) {
+ return Set.of();
}
Set containers = new TreeSet<>(FlywayContainerComparator.INSTANCE);
- containers.addAll(FlywayRecorder.FLYWAY_CONTAINERS);
+ for (InstanceHandle flywayContainerHandle : flywayContainerHandles) {
+ containers.add(flywayContainerHandle.get());
+ }
return containers;
}
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java
index 94c7919fe4615..2b0f724a9a16f 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java
@@ -1,13 +1,13 @@
package io.quarkus.flyway.runtime;
-import java.util.ArrayList;
+import java.lang.annotation.Annotation;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
-import java.util.function.Supplier;
+import java.util.function.Function;
import javax.sql.DataSource;
+import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.UnsatisfiedResolutionException;
import org.flywaydb.core.Flyway;
@@ -18,6 +18,10 @@
import io.quarkus.agroal.runtime.DataSources;
import io.quarkus.agroal.runtime.UnconfiguredDataSource;
import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.SyntheticCreationalContext;
+import io.quarkus.datasource.common.runtime.DataSourceUtil;
+import io.quarkus.flyway.FlywayDataSource.FlywayDataSourceLiteral;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
@@ -26,8 +30,6 @@ public class FlywayRecorder {
private static final Logger log = Logger.getLogger(FlywayRecorder.class);
- static final List FLYWAY_CONTAINERS = new ArrayList<>(2);
-
private final RuntimeValue config;
public FlywayRecorder(RuntimeValue config) {
@@ -49,27 +51,37 @@ public void setApplicationCallbackClasses(Map> call
QuarkusPathLocationScanner.setApplicationCallbackClasses(callbackClasses);
}
- public void resetFlywayContainers() {
- FLYWAY_CONTAINERS.clear();
- }
-
- public Supplier flywaySupplier(String dataSourceName, boolean hasMigrations, boolean createPossible) {
- DataSource dataSource = DataSources.fromName(dataSourceName);
- if (dataSource instanceof UnconfiguredDataSource) {
- return new Supplier() {
- @Override
- public Flyway get() {
+ public Function, FlywayContainer> flywayContainerFunction(String dataSourceName,
+ boolean hasMigrations,
+ boolean createPossible) {
+ return new Function<>() {
+ @Override
+ public FlywayContainer apply(SyntheticCreationalContext context) {
+ DataSource dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName);
+ if (dataSource instanceof UnconfiguredDataSource) {
throw new UnsatisfiedResolutionException("No datasource present");
}
- };
- }
- FlywayContainerProducer flywayProducer = Arc.container().instance(FlywayContainerProducer.class).get();
- FlywayContainer flywayContainer = flywayProducer.createFlyway(dataSource, dataSourceName, hasMigrations,
- createPossible);
- FLYWAY_CONTAINERS.add(flywayContainer);
- return new Supplier() {
+
+ FlywayContainerProducer flywayProducer = context.getInjectedReference(FlywayContainerProducer.class);
+ FlywayContainer flywayContainer = flywayProducer.createFlyway(dataSource, dataSourceName, hasMigrations,
+ createPossible);
+ return flywayContainer;
+ }
+ };
+ }
+
+ public Function, Flyway> flywayFunction(String dataSourceName) {
+ return new Function<>() {
@Override
- public Flyway get() {
+ public Flyway apply(SyntheticCreationalContext context) {
+ Annotation flywayContainerQualifier;
+ if (DataSourceUtil.isDefault(dataSourceName)) {
+ flywayContainerQualifier = Default.Literal.INSTANCE;
+ } else {
+ flywayContainerQualifier = FlywayDataSourceLiteral.of(dataSourceName);
+ }
+
+ FlywayContainer flywayContainer = context.getInjectedReference(FlywayContainer.class, flywayContainerQualifier);
return flywayContainer.getFlyway();
}
};
@@ -79,7 +91,10 @@ public void doStartActions() {
if (!config.getValue().enabled) {
return;
}
- for (FlywayContainer flywayContainer : FLYWAY_CONTAINERS) {
+
+ for (InstanceHandle flywayContainerHandle : Arc.container().listAll(FlywayContainer.class)) {
+ FlywayContainer flywayContainer = flywayContainerHandle.get();
+
if (flywayContainer.isCleanAtStart()) {
flywayContainer.getFlyway().clean();
}
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java
index b5026a133c9ea..57dab412c16d1 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java
@@ -1,23 +1,25 @@
package io.quarkus.flyway.runtime;
+import io.quarkus.arc.Arc;
import io.quarkus.datasource.runtime.DatabaseSchemaProvider;
public class FlywaySchemaProvider implements DatabaseSchemaProvider {
+
@Override
public void resetDatabase(String dbName) {
- for (FlywayContainer i : FlywayRecorder.FLYWAY_CONTAINERS) {
- if (i.getDataSourceName().equals(dbName)) {
- i.getFlyway().clean();
- i.getFlyway().migrate();
+ for (FlywayContainer flywayContainer : Arc.container().select(FlywayContainer.class)) {
+ if (flywayContainer.getDataSourceName().equals(dbName)) {
+ flywayContainer.getFlyway().clean();
+ flywayContainer.getFlyway().migrate();
}
}
}
@Override
public void resetAllDatabases() {
- for (FlywayContainer i : FlywayRecorder.FLYWAY_CONTAINERS) {
- i.getFlyway().clean();
- i.getFlyway().migrate();
+ for (FlywayContainer flywayContainer : Arc.container().select(FlywayContainer.class)) {
+ flywayContainer.getFlyway().clean();
+ flywayContainer.getFlyway().migrate();
}
}
}
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java
index 43916e2285fe9..827437c97de90 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/config/DialectVersions.java
@@ -22,7 +22,7 @@ public static final class Defaults {
public static final String ORACLE = "12";
// This must be aligned on the H2 version in the Quarkus BOM
- public static final String H2 = "2.1.214";
+ public static final String H2 = "2.2.220";
private Defaults() {
}
diff --git a/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java b/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java
index f83906ba47abd..1f44be5e5f393 100644
--- a/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java
+++ b/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java
@@ -1,5 +1,7 @@
package io.quarkus.jdbc.h2.deployment;
+import java.util.Set;
+
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.processor.BuiltinScope;
@@ -12,10 +14,12 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.RemovedResourceBuildItem;
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageEnableModule;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.jdbc.h2.runtime.H2AgroalConnectionConfigurer;
+import io.quarkus.maven.dependency.ArtifactKey;
public class JDBCH2Processor {
@@ -62,4 +66,10 @@ NativeImageEnableModule registerNetModuleForNative() {
//Compiling H2 to native requires activating the jdk.net module of the JDK
return new NativeImageEnableModule("jdk.net");
}
+
+ @BuildStep
+ void excludeNativeImageDirectives(BuildProducer removedResources) {
+ removedResources.produce(new RemovedResourceBuildItem(ArtifactKey.fromString("com.h2database:h2"),
+ Set.of("META-INF/native-image/reflect-config.json")));
+ }
}
diff --git a/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java
new file mode 100644
index 0000000000000..c5f16883dd5d6
--- /dev/null
+++ b/extensions/jdbc/jdbc-h2/runtime/src/main/java/io/quarkus/jdbc/h2/runtime/graalvm/DeleteFullTextLucene.java
@@ -0,0 +1,10 @@
+package io.quarkus.jdbc.h2.runtime.graalvm;
+
+import com.oracle.svm.core.annotate.Delete;
+import com.oracle.svm.core.annotate.TargetClass;
+
+@Delete
+@TargetClass(className = "org.h2.fulltext.FullTextLucene")
+public final class DeleteFullTextLucene {
+
+}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java
index 47927799855d8..58fb10d804e16 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java
@@ -16,6 +16,7 @@
import io.quarkus.kubernetes.spi.KubernetesJobBuildItem;
import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem;
import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem;
+import io.quarkus.kubernetes.spi.KubernetesServiceAccountBuildItem;
import io.quarkus.kubernetes.spi.PolicyRule;
public class InitTaskProcessor {
@@ -34,6 +35,7 @@ static void process(
BuildProducer env,
BuildProducer roles,
BuildProducer roleBindings,
+ BuildProducer serviceAccount,
BuildProducer decorators) {
boolean generateRoleForJobs = false;
@@ -73,6 +75,7 @@ static void process(
List.of("get"))),
target));
roleBindings.produce(new KubernetesRoleBindingBuildItem(null, "view-jobs", false, target));
+ serviceAccount.produce(new KubernetesServiceAccountBuildItem(true));
}
}
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java
index 78bc5a2f8b4f9..511a1d5f8f1e8 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java
@@ -692,8 +692,24 @@ public static List createInitJobDecorators(String target, St
.map(Optional::get)
.collect(Collectors.toList());
+ List imagePullSecretDecorators = decorators.stream()
+ .filter(d -> d.getGroup() == null || d.getGroup().equals(target))
+ .map(d -> d.getDecorator(AddImagePullSecretDecorator.class))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toList());
+
items.stream().filter(item -> item.getTarget() == null || item.getTarget().equals(target)).forEach(item -> {
+ for (final AddImagePullSecretDecorator delegate : imagePullSecretDecorators) {
+ result.add(new DecoratorBuildItem(target, new NamedResourceDecorator("Job", item.getName()) {
+ @Override
+ public void andThenVisit(PodSpecBuilder builder, ObjectMeta meta) {
+ delegate.andThenVisit(builder, meta);
+ }
+ }));
+ }
+
result.add(new DecoratorBuildItem(target, new NamedResourceDecorator("Job", item.getName()) {
@Override
public void andThenVisit(ContainerBuilder builder, ObjectMeta meta) {
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java
index ffcbf904b52f7..7c6f38b8697fc 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java
@@ -402,11 +402,12 @@ void externalizeInitTasks(
BuildProducer env,
BuildProducer roles,
BuildProducer roleBindings,
+ BuildProducer serviceAccount,
BuildProducer decorators) {
final String name = ResourceNameUtil.getResourceName(config, applicationInfo);
if (config.externalizeInit) {
InitTaskProcessor.process(OPENSHIFT, name, image, initTasks, config.initTasks,
- jobs, initContainers, env, roles, roleBindings, decorators);
+ jobs, initContainers, env, roles, roleBindings, serviceAccount, decorators);
}
}
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
index 3e35fbaac5fc6..1e1e904a25acd 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
@@ -314,11 +314,13 @@ void externalizeInitTasks(
BuildProducer env,
BuildProducer roles,
BuildProducer roleBindings,
+ BuildProducer serviceAccount,
+
BuildProducer decorators) {
final String name = ResourceNameUtil.getResourceName(config, applicationInfo);
if (config.externalizeInit) {
InitTaskProcessor.process(KUBERNETES, name, image, initTasks, config.initTasks,
- jobs, initContainers, env, roles, roleBindings, decorators);
+ jobs, initContainers, env, roles, roleBindings, serviceAccount, decorators);
}
}
}
diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java
index 33188df5e78e6..4d6c65e2b30f5 100644
--- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java
+++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java
@@ -28,6 +28,7 @@
import org.jboss.jandex.DotName;
import org.jboss.logging.Logger;
+import io.quarkus.agroal.runtime.DataSources;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
@@ -285,6 +286,7 @@ void createBeans(LiquibaseRecorder recorder,
.setRuntimeInit()
.unremovable()
.addInjectionPoint(ClassType.create(DotName.createSimple(LiquibaseFactoryProducer.class)))
+ .addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class)))
.createWith(recorder.liquibaseFunction(dataSourceName));
if (DataSourceUtil.isDefault(dataSourceName)) {
diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java
index 2b9429b73d5bf..d85785d61e35b 100644
--- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java
+++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java
@@ -29,21 +29,16 @@ public LiquibaseRecorder(RuntimeValue config) {
}
public Function, LiquibaseFactory> liquibaseFunction(String dataSourceName) {
- DataSource dataSource = DataSources.fromName(dataSourceName);
- if (dataSource instanceof UnconfiguredDataSource) {
- return new Function, LiquibaseFactory>() {
- @Override
- public LiquibaseFactory apply(SyntheticCreationalContext context) {
- throw new UnsatisfiedResolutionException("No datasource has been configured");
- }
- };
- }
return new Function, LiquibaseFactory>() {
@Override
public LiquibaseFactory apply(SyntheticCreationalContext context) {
+ DataSource dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName);
+ if (dataSource instanceof UnconfiguredDataSource) {
+ throw new UnsatisfiedResolutionException("No datasource has been configured");
+ }
+
LiquibaseFactoryProducer liquibaseProducer = context.getInjectedReference(LiquibaseFactoryProducer.class);
- LiquibaseFactory liquibaseFactory = liquibaseProducer.createLiquibaseFactory(dataSource, dataSourceName);
- return liquibaseFactory;
+ return liquibaseProducer.createLiquibaseFactory(dataSource, dataSourceName);
}
};
}
diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java
index c73a1cddb39ca..08ed5e00f0961 100644
--- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java
+++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java
@@ -155,7 +155,7 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti
}
public void handleShutdown(ShutdownContext context, TransactionManagerConfiguration transactions) {
- context.addLastShutdownTask(() -> {
+ context.addShutdownTask(() -> {
if (transactions.enableRecovery) {
try {
QuarkusRecoveryService.getInstance().stop();
@@ -166,6 +166,8 @@ public void handleShutdown(ShutdownContext context, TransactionManagerConfigurat
QuarkusRecoveryService.getInstance().destroy();
}
}
+ });
+ context.addLastShutdownTask(() -> {
TransactionReaper.terminate(false);
});
}
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java
index a6cde6e299745..fb7fe0aba6d61 100644
--- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java
@@ -27,6 +27,7 @@
public class KubernetesWithFlywayInitTest {
private static final String NAME = "kubernetes-with-flyway";
+ private static final String IMAGE_PULL_SECRET = "my-pull-secret";
@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
@@ -34,6 +35,7 @@ public class KubernetesWithFlywayInitTest {
.setApplicationName(NAME)
.setApplicationVersion("0.1-SNAPSHOT")
.setLogFileName("k8s.log")
+ .overrideConfigKey("quarkus.kubernetes.image-pull-secrets", IMAGE_PULL_SECRET)
.setForcedDependencies(Arrays.asList(
new AppArtifact("io.quarkus", "quarkus-kubernetes", Version.getVersion()),
new AppArtifact("io.quarkus", "quarkus-flyway", Version.getVersion())));
@@ -65,6 +67,9 @@ public void assertGeneratedResources() throws IOException {
assertThat(d.getSpec()).satisfies(deploymentSpec -> {
assertThat(deploymentSpec.getTemplate()).satisfies(t -> {
assertThat(t.getSpec()).satisfies(podSpec -> {
+ assertThat(podSpec.getImagePullSecrets()).singleElement()
+ .satisfies(s -> assertThat(s.getName()).isEqualTo(IMAGE_PULL_SECRET));
+ assertThat(podSpec.getServiceAccountName()).isEqualTo(NAME);
assertThat(podSpec.getInitContainers()).singleElement().satisfies(container -> {
assertThat(container.getName()).isEqualTo("init");
assertThat(container.getImage()).isEqualTo("groundnuty/k8s-wait-for:no-root-v1.7");
@@ -86,6 +91,8 @@ public void assertGeneratedResources() throws IOException {
assertThat(jobSpec.getCompletionMode()).isEqualTo("NonIndexed");
assertThat(jobSpec.getTemplate()).satisfies(t -> {
assertThat(t.getSpec()).satisfies(podSpec -> {
+ assertThat(podSpec.getImagePullSecrets()).singleElement()
+ .satisfies(s -> assertThat(s.getName()).isEqualTo(IMAGE_PULL_SECRET));
assertThat(podSpec.getRestartPolicy()).isEqualTo("OnFailure");
assertThat(podSpec.getContainers()).singleElement().satisfies(container -> {
assertThat(container.getName()).isEqualTo(NAME + "-flyway-init");
diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java
index 86e576f03b12a..2859505ab1f51 100644
--- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java
+++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java
@@ -2,6 +2,7 @@
import static org.assertj.core.api.Assertions.assertThat;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -27,6 +28,7 @@
import io.quarkus.maven.it.verifier.MavenProcessInvocationResult;
import io.quarkus.maven.it.verifier.RunningInvoker;
+import io.quarkus.runtime.util.HashUtil;
@DisableForNative
public class PackageIT extends MojoTestBase {
@@ -34,6 +36,42 @@ public class PackageIT extends MojoTestBase {
private RunningInvoker running;
private File testDir;
+ @Test
+ public void testConfigTracking() throws Exception {
+ testDir = initProject("projects/config-tracking");
+ running = new RunningInvoker(testDir, false);
+ var configDump = new File(new File(testDir, ".quarkus"), "quarkus-prod-config-dump");
+ var configCheck = new File(new File(testDir, "target"), "quarkus-prod-config-check");
+
+ // initial build that generates .quarkus/quarkus-prod-config-dump
+ var result = running.execute(List.of("clean package -DskipTests"), Map.of());
+ assertThat(result.getProcess().waitFor()).isEqualTo(0);
+ assertThat(configDump).exists();
+ assertThat(configCheck).doesNotExist();
+
+ // rebuild and compare the files
+ result = running.execute(List.of("package -DskipTests"), Map.of());
+ assertThat(result.getProcess().waitFor()).isEqualTo(0);
+ assertThat(configDump).exists();
+ assertThat(configCheck).exists();
+ assertThat(configDump).hasSameTextualContentAs(configCheck);
+
+ var props = new Properties();
+ try (BufferedReader reader = Files.newBufferedReader(configDump.toPath())) {
+ props.load(reader);
+ }
+ assertThat(props).containsEntry("quarkus.application.name", HashUtil.sha512("code-with-quarkus"));
+
+ assertThat(props).doesNotContainKey("quarkus.platform.group-id");
+ for (var name : props.stringPropertyNames()) {
+ assertThat(name).doesNotStartWith("quarkus.test.");
+ }
+
+ result = running.execute(List.of("package -DskipTests -Dquarkus.package.type=uber-jar"), Map.of());
+ assertThat(result.getProcess().waitFor()).isEqualTo(0);
+ assertThat(running.log()).contains("Option quarkus.package.type has changed since the last build from jar to uber-jar");
+ }
+
@Test
public void testPluginClasspathConfig() throws Exception {
testDir = initProject("projects/test-plugin-classpath-config");
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml
new file mode 100644
index 0000000000000..434437b4d2610
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+ org.acme
+ code-with-quarkus
+ 1.0.0-SNAPSHOT
+
+ ${compiler-plugin.version}
+ ${maven.compiler.release}
+ UTF-8
+ UTF-8
+ quarkus-bom
+ io.quarkus
+ ${project.version}
+
+
+
+
+ \${quarkus.platform.group-id}
+ \${quarkus.platform.artifact-id}
+ \${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+ io.quarkus
+ quarkus-resteasy-reactive
+
+
+
+
+
+ \${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ \${quarkus.platform.version}
+ true
+
+
+ track-prod-config-changes
+ process-resources
+
+ track-config-changes
+
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ maven-compiler-plugin
+ \${compiler-plugin.version}
+
+
+ -parameters
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java
new file mode 100644
index 0000000000000..6938062ec8ff7
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/java/org/acme/GreetingResource.java
@@ -0,0 +1,16 @@
+package org.acme;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/hello")
+public class GreetingResource {
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String hello() {
+ return "Hello from RESTEasy Reactive";
+ }
+}
diff --git a/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties
new file mode 100644
index 0000000000000..ff43978c7f6ca
--- /dev/null
+++ b/integration-tests/maven/src/test/resources-filtered/projects/config-tracking/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+quarkus.config-tracking.enabled=true
+quarkus.config-tracking.hash-options=quarkus.application.*
+quarkus.config-tracking.exclude=quarkus.test.*,quarkus.platform.group-id
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e12a0b521db88..016defe17e9b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
1.2.1
3.22.0
${protoc.version}
- 2.19.1
+ 2.23.0
7.4.0
diff --git a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java
new file mode 100644
index 0000000000000..8d2050fa26e34
--- /dev/null
+++ b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java
@@ -0,0 +1,91 @@
+package io.quarkus.test.devmode.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * @deprecated Use {@link DevModeClient} instead (the methods on that class are non-static to allow ports to be specified).
+ */
+@Deprecated(since = "3.3", forRemoval = true)
+public class DevModeTestUtils {
+
+ private static final DevModeClient devModeClient = new DevModeClient();
+
+ public static List killDescendingProcesses() {
+ return DevModeClient.killDescendingProcesses();
+ }
+
+ public static void filter(File input, Map variables) throws IOException {
+ DevModeClient.filter(input, variables);
+ }
+
+ public static void awaitUntilServerDown() {
+ devModeClient.awaitUntilServerDown();
+ }
+
+ public static String getHttpResponse() {
+ return devModeClient.getHttpResponse();
+ }
+
+ public static String getHttpResponse(Supplier brokenReason) {
+ return devModeClient.getHttpResponse(brokenReason);
+ }
+
+ public static String getHttpErrorResponse() {
+ return devModeClient.getHttpErrorResponse();
+ }
+
+ public static String getHttpErrorResponse(Supplier brokenReason) {
+ return devModeClient.getHttpErrorResponse(brokenReason);
+ }
+
+ public static String getHttpResponse(String path) {
+ return devModeClient.getHttpResponse(path);
+ }
+
+ public static String getHttpResponse(String path, Supplier brokenReason) {
+ return devModeClient.getHttpResponse(path, brokenReason);
+ }
+
+ public static String getHttpResponse(String path, boolean allowError) {
+ return devModeClient.getHttpResponse(path, allowError);
+ }
+
+ public static String getHttpResponse(String path, boolean allowError, Supplier brokenReason) {
+ return devModeClient.getHttpResponse(path, allowError, brokenReason);
+ }
+
+ public static String getHttpResponse(String path, boolean allowError, Supplier brokenReason, long timeout,
+ TimeUnit tu) {
+ return devModeClient.getHttpResponse(path, allowError, brokenReason, timeout, tu);
+ }
+
+ public static boolean getHttpResponse(String path, int expectedStatus) {
+ return devModeClient.getHttpResponse(path, expectedStatus);
+ }
+
+ public static boolean getHttpResponse(String path, int expectedStatus, long timeout, TimeUnit tu) {
+ return devModeClient.getHttpResponse(path, expectedStatus, timeout, tu);
+ }
+
+ // will fail if it receives any http response except the expected one
+ public static boolean getStrictHttpResponse(String path, int expectedStatus) {
+ return devModeClient.getStrictHttpResponse(path, expectedStatus);
+ }
+
+ public static String get() throws IOException {
+ return devModeClient.get();
+ }
+
+ public static String get(String urlStr) throws IOException {
+ return devModeClient.get(urlStr);
+ }
+
+ public static boolean isCode(String path, int code) {
+ return devModeClient.isCode(path, code);
+ }
+}