diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ResettableSystemProperties.java b/core/runtime/src/main/java/io/quarkus/runtime/ResettableSystemProperties.java new file mode 100644 index 0000000000000..80fa859ebf0b3 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ResettableSystemProperties.java @@ -0,0 +1,47 @@ +package io.quarkus.runtime; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Utility that allows for setting system properties when it's created and resetting them when it's closed. + * This is meant to be used in try-with-resources statements + */ +public class ResettableSystemProperties implements AutoCloseable { + + private final Map toRestore; + + public ResettableSystemProperties(Map toSet) { + Objects.requireNonNull(toSet); + if (toSet.isEmpty()) { + toRestore = Collections.emptyMap(); + return; + } + toRestore = new HashMap<>(); + for (var entry : toSet.entrySet()) { + String oldValue = System.setProperty(entry.getKey(), entry.getValue()); + toRestore.put(entry.getKey(), oldValue); + } + } + + public static ResettableSystemProperties of(String name, String value) { + return new ResettableSystemProperties(Map.of(name, value)); + } + + public static ResettableSystemProperties empty() { + return new ResettableSystemProperties(Collections.emptyMap()); + } + + @Override + public void close() { + for (var entry : toRestore.entrySet()) { + if (entry.getValue() != null) { + System.setProperty(entry.getKey(), entry.getValue()); + } else { + System.clearProperty(entry.getKey()); + } + } + } +} diff --git a/core/runtime/src/test/java/io/quarkus/runtime/ResettableSystemPropertiesTest.java b/core/runtime/src/test/java/io/quarkus/runtime/ResettableSystemPropertiesTest.java new file mode 100644 index 0000000000000..82e4d03ce5692 --- /dev/null +++ b/core/runtime/src/test/java/io/quarkus/runtime/ResettableSystemPropertiesTest.java @@ -0,0 +1,42 @@ +package io.quarkus.runtime; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +class ResettableSystemPropertiesTest { + + @Test + public void happyPath() { + System.setProperty("prop1", "val1"); + assertThat(System.getProperty("prop1")).isEqualTo("val1"); + try (var ignored = new ResettableSystemProperties( + Map.of("prop1", "val11", "prop2", "val2"))) { + assertThat(System.getProperty("prop1")).isEqualTo("val11"); + assertThat(System.getProperty("prop2")).isEqualTo("val2"); + } + assertThat(System.getProperty("prop1")).isEqualTo("val1"); + assertThat(System.getProperties()).doesNotContainKey("prop2"); + } + + @Test + public void exceptionThrown() { + System.setProperty("prop1", "val1"); + int initCount = System.getProperties().size(); + assertThat(System.getProperty("prop1")).isEqualTo("val1"); + try (var ignored = new ResettableSystemProperties( + Map.of("prop1", "val11", "prop2", "val2"))) { + assertThat(System.getProperty("prop1")).isEqualTo("val11"); + assertThat(System.getProperty("prop2")).isEqualTo("val2"); + + throw new RuntimeException("dummy"); + } catch (Exception ignored) { + + } + assertThat(System.getProperty("prop1")).isEqualTo("val1"); + assertThat(System.getProperties()).doesNotContainKey("prop2"); + assertThat(System.getProperties().size()).isEqualTo(initCount); + } +} diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java index db1c460fa1b64..1616bdb3262af 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java @@ -10,6 +10,7 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.liquibase.runtime.LiquibaseConfig; +import io.quarkus.runtime.ResettableSystemProperties; import io.quarkus.runtime.util.StringUtil; import liquibase.Contexts; import liquibase.LabelExpression; @@ -150,4 +151,12 @@ public Contexts createContexts() { public String getDataSourceName() { return dataSourceName; } + + public ResettableSystemProperties createResettableSystemProperties() { + if (config.allowDuplicatedChangesetIdentifiers.isEmpty()) { + return ResettableSystemProperties.empty(); + } + return ResettableSystemProperties.of("liquibase.allowDuplicatedChangesetIdentifiers", + config.allowDuplicatedChangesetIdentifiers.get().toString()); + } } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java index de1f2e47a1f36..a9fc7b70e7469 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java @@ -97,4 +97,9 @@ public class LiquibaseConfig { */ public Optional password = Optional.empty(); + /** + * Allows duplicated changeset identifiers without failing Liquibase execution. + */ + public Optional allowDuplicatedChangesetIdentifiers; + } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java index a6c06d69fb47a..5dfae13603e4f 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java @@ -43,6 +43,7 @@ public LiquibaseFactory createLiquibaseFactory(DataSource dataSource, String dat config.migrateAtStart = liquibaseRuntimeConfig.migrateAtStart; config.cleanAtStart = liquibaseRuntimeConfig.cleanAtStart; config.validateOnMigrate = liquibaseRuntimeConfig.validateOnMigrate; + config.allowDuplicatedChangesetIdentifiers = liquibaseRuntimeConfig.allowDuplicatedChangesetIdentifiers; return new LiquibaseFactory(config, dataSource, dataSourceName); } } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java index c8cb0d1cf3e56..b68d4b47e32f8 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java @@ -133,4 +133,10 @@ public static final LiquibaseDataSourceRuntimeConfig defaultConfig() { @ConfigItem public Optional liquibaseTablespaceName = Optional.empty(); + /** + * Allows duplicated changeset identifiers without failing Liquibase execution. + */ + @ConfigItem + public Optional allowDuplicatedChangesetIdentifiers = Optional.empty(); + } 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 f368806383203..01bde1120a577 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 @@ -14,6 +14,7 @@ import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.runtime.ResettableSystemProperties; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; import liquibase.Liquibase; @@ -66,7 +67,9 @@ public void doStartActions(String dataSourceName) { if (!config.cleanAtStart && !config.migrateAtStart) { return; } - try (Liquibase liquibase = liquibaseFactory.createLiquibase()) { + try (Liquibase liquibase = liquibaseFactory.createLiquibase(); + ResettableSystemProperties resettableSystemProperties = liquibaseFactory + .createResettableSystemProperties()) { if (config.cleanAtStart) { liquibase.dropAll(); }