diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/metadata/Constants.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/metadata/Constants.java
index b045a7f75..07e327669 100644
--- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/metadata/Constants.java
+++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/metadata/Constants.java
@@ -24,7 +24,7 @@ public final class Constants {
public static final String DEFAULT_MONGOCK_ORIGIN = "mongockChangeLog";
public static final String MONGOCK_IMPORT_ORIGIN_PROPERTY_KEY = "internal.mongock.import.origin";
- public static final String MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY = "internal.mongock.import.emptyOriginAllowed";
+ public static final String MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY = "internal.mongock.import.emptyOriginAllowed";
private Constants() {}
diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/AnnotationProcessorPlugin.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/AnnotationProcessorPlugin.java
new file mode 100644
index 000000000..b77625eae
--- /dev/null
+++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/AnnotationProcessorPlugin.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2026 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.internal.common.core.processor;
+
+import io.flamingock.internal.common.core.util.LoggerPreProcessor;
+
+import javax.annotation.processing.RoundEnvironment;
+
+/**
+ * Extension point for annotation processing plugins.
+ *
+ * Implementations are discovered using {@link java.util.ServiceLoader}. For an implementation to be found at
+ * processing time, the JAR that provides it must be present on the application's {@code annotationProcessor}
+ * classpath.
+ *
+ * The implementation JAR must also include the standard service registration file
+ * {@code META-INF/services/io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin},
+ * listing the implementation class name.
+ */
+public interface AnnotationProcessorPlugin {
+
+ void initialize(RoundEnvironment roundEnv, LoggerPreProcessor logger);
+}
diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/discover/ChangeDiscoverer.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ChangeDiscoverer.java
similarity index 65%
rename from core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/discover/ChangeDiscoverer.java
rename to core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ChangeDiscoverer.java
index a7a6fa703..be464f08f 100644
--- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/discover/ChangeDiscoverer.java
+++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ChangeDiscoverer.java
@@ -13,17 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.flamingock.internal.common.core.discover;
+package io.flamingock.internal.common.core.processor;
import io.flamingock.internal.common.core.preview.CodePreviewChange;
-import io.flamingock.internal.common.core.util.LoggerPreProcessor;
-import javax.annotation.processing.RoundEnvironment;
import java.util.Collection;
-import java.util.Map;
public interface ChangeDiscoverer {
- //TODO: move configuration properties to another interface
- Collection findAnnotatedChanges(RoundEnvironment roundEnv, LoggerPreProcessor logger, Map properties);
+ Collection findAnnotatedChanges();
}
diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ConfigurationPropertiesProvider.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ConfigurationPropertiesProvider.java
new file mode 100644
index 000000000..c9549ea6c
--- /dev/null
+++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ConfigurationPropertiesProvider.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2025 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.internal.common.core.processor;
+
+import java.util.Map;
+
+public interface ConfigurationPropertiesProvider {
+
+ Map getConfigurationProperties();
+}
diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/util/ConfigValueParser.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/util/ConfigValueParser.java
new file mode 100644
index 000000000..e9d53c8b3
--- /dev/null
+++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/util/ConfigValueParser.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2026 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.internal.common.core.util;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Generic configuration value parser for Flamingock configuration values.
+ *
+ * Supported placeholder syntax:
+ *
+ * - {@code ${name}}
+ * - {@code ${name:defaultValue}}
+ *
+ *
+ * Notes:
+ *
+ * - The placeholder {@code name} may include separators such as {@code .}, {@code -} and {@code _}.
+ * - The first {@code :} (if present) separates the name from the default value.
+ * - Whitespace around the name is allowed and ignored.
+ * - Nested placeholders (e.g. {@code ${a:${b}}}) are intentionally not supported.
+ * - If a value starts with ${ but is not a valid placeholder, this class throws an exception.
+ * It must not be treated as a literal.
+ *
+ */
+public final class ConfigValueParser {
+
+ private ConfigValueParser() {
+ // Utility class
+ }
+
+ private static final Pattern PLACEHOLDER_PATTERN =
+ Pattern.compile("^\\$\\{\\s*([^}:\\s]+)\\s*(?::([^}]*))?}$");
+
+ /**
+ * Validator suitable for boolean-like values:
+ * allowed values: "", "true", "false" (case-insensitive).
+ */
+ public static final Predicate BOOLEAN_VALUE_VALIDATOR =
+ value -> value == null
+ || value.trim().isEmpty()
+ || "true".equalsIgnoreCase(value.trim())
+ || "false".equalsIgnoreCase(value.trim());
+
+ /**
+ * Parses the provided value and classifies it as EMPTY, PLACEHOLDER, or LITERAL.
+ *
+ * This overload performs only placeholder syntax validation. Literals and placeholder defaults
+ * are accepted as-is.
+ *
+ * @param valueName logical name used in exception messages (e.g. "origin", "emptyOriginAllowed")
+ * @param raw raw input value
+ * @return parsed configuration value (never invalid; invalid inputs throw)
+ */
+ public static ConfigValue parse(String valueName, String raw) {
+ return parse(valueName, raw, null);
+ }
+
+ /**
+ * Parses the provided value and classifies it as EMPTY, PLACEHOLDER, or LITERAL, while also validating:
+ *
+ * - Placeholder syntax (always, when value starts with ${)
+ * - Placeholder default value (only when present, using {@code valueValidator} if provided)
+ * - Literal value (using {@code valueValidator} if provided)
+ *
+ *
+ * Fail-fast rule: if the value starts with ${} but is not a valid placeholder,
+ * this method throws {@link IllegalArgumentException}.
+ *
+ * @param valueName logical name used in exception messages (e.g. "origin", "emptyOriginAllowed")
+ * @param raw raw input value
+ * @param valueValidator validator applied to literal values and placeholder default values (may be null)
+ * @return parsed configuration value (never invalid; invalid inputs throw)
+ */
+ public static ConfigValue parse(String valueName, String raw, Predicate valueValidator) {
+ String value = normalise(raw);
+
+ if (value.isEmpty()) {
+ return ConfigValue.empty(raw);
+ }
+
+ if (value.startsWith("${")) {
+ Placeholder placeholder = parsePlaceholderOrThrow(valueName, raw, value);
+
+ if (placeholder.hasDefault()) {
+ validateValue(valueName, raw, placeholder.getDefaultValue(), valueValidator);
+ }
+
+ return ConfigValue.placeholder(raw, placeholder);
+ }
+
+ // Literal
+ validateValue(valueName, raw, value, valueValidator);
+ return ConfigValue.literal(raw, value);
+ }
+
+ /**
+ * Parses a placeholder if (and only if) the entire value is a placeholder.
+ *
+ * Returns {@link Optional#empty()} if the input is {@code null}, blank, or not a placeholder.
+ *
+ * Important: if the value starts with ${} but is not a valid placeholder,
+ * this method throws {@link IllegalArgumentException}.
+ */
+ public static Optional parsePlaceholder(String valueName, String raw) {
+ ConfigValue parsed = parse(valueName, raw, null);
+ return parsed.getPlaceholder();
+ }
+
+ /**
+ * Returns {@code true} if the supplied value is syntactically a valid placeholder.
+ *
+ * This method never throws. If you need fail-fast behaviour for values starting with ${,
+ * use {@link #parse(String, String)}.
+ */
+ public static boolean isValidPlaceholder(String raw) {
+ String value = normalise(raw);
+ if (value.isEmpty()) {
+ return false;
+ }
+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
+ if (!matcher.matches()) {
+ return false;
+ }
+ String name = matcher.group(1);
+ return name != null && !name.trim().isEmpty();
+ }
+
+ private static Placeholder parsePlaceholderOrThrow(String valueName, String raw, String normalised) {
+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(normalised);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(buildPlaceholderSyntaxError(valueName, raw));
+ }
+
+ String name = matcher.group(1);
+ String defaultValue = matcher.group(2); // null if ':' not present; may be "" if ${name:}
+
+ if (name == null || name.trim().isEmpty()) {
+ throw new IllegalArgumentException(buildPlaceholderSyntaxError(valueName, raw));
+ }
+
+ return new Placeholder(name.trim(), defaultValue);
+ }
+
+ private static void validateValue(String valueName,
+ String raw,
+ String value,
+ Predicate validator) {
+ Predicate effectiveValidator = validator != null ? validator : v -> true;
+ if (!effectiveValidator.test(value)) {
+ throw new IllegalArgumentException(buildLiteralOrDefaultError(valueName, raw));
+ }
+ }
+
+ private static String buildPlaceholderSyntaxError(String valueName, String raw) {
+ return "Invalid placeholder syntax for " + valueName + ": '" + raw + "'. "
+ + "Expected ${name} or ${name:defaultValue}.";
+ }
+
+ private static String buildLiteralOrDefaultError(String valueName, String raw) {
+ return "Invalid value for " + valueName + ": '" + raw + "'.";
+ }
+
+ private static String normalise(String s) {
+ return s == null ? "" : s.trim();
+ }
+
+ // --------------------------------------------------------
+ // Value Objects
+ // --------------------------------------------------------
+
+ public enum ValueType {
+ EMPTY,
+ LITERAL,
+ PLACEHOLDER
+ }
+
+ /**
+ * Result of parsing an input configuration value.
+ *
+ * Exactly one of {@code literal} or {@code placeholder} will be present when type is LITERAL / PLACEHOLDER.
+ * When type is EMPTY, neither is present.
+ */
+ public static final class ConfigValue {
+
+ private final ValueType type;
+ private final String raw;
+ private final String literal;
+ private final Placeholder placeholder;
+
+ private ConfigValue(ValueType type, String raw, String literal, Placeholder placeholder) {
+ this.type = Objects.requireNonNull(type, "type");
+ this.raw = raw;
+ this.literal = literal;
+ this.placeholder = placeholder;
+ }
+
+ public static ConfigValue empty(String raw) {
+ return new ConfigValue(ValueType.EMPTY, raw, null, null);
+ }
+
+ public static ConfigValue literal(String raw, String literal) {
+ return new ConfigValue(ValueType.LITERAL, raw, Objects.requireNonNull(literal, "literal"), null);
+ }
+
+ public static ConfigValue placeholder(String raw, Placeholder placeholder) {
+ return new ConfigValue(ValueType.PLACEHOLDER, raw, null, Objects.requireNonNull(placeholder, "placeholder"));
+ }
+
+ public ValueType getType() {
+ return type;
+ }
+
+ public String getRaw() {
+ return raw;
+ }
+
+ public boolean isEmpty() {
+ return type == ValueType.EMPTY;
+ }
+
+ public boolean isLiteral() {
+ return type == ValueType.LITERAL;
+ }
+
+ public boolean isPlaceholder() {
+ return type == ValueType.PLACEHOLDER;
+ }
+
+ public Optional getLiteral() {
+ return Optional.ofNullable(literal);
+ }
+
+ public Optional getPlaceholder() {
+ return Optional.ofNullable(placeholder);
+ }
+ }
+
+ /**
+ * Immutable representation of a parsed placeholder.
+ *
+ * The {@code defaultValue} is:
+ *
+ * - {@code null} when no {@code :} is present (e.g. {@code ${name}})
+ * - possibly empty when {@code :} is present (e.g. {@code ${name:}})
+ *
+ */
+ public static final class Placeholder {
+
+ private final String name;
+ private final String defaultValue;
+
+ private Placeholder(String name, String defaultValue) {
+ this.name = Objects.requireNonNull(name, "name");
+ this.defaultValue = defaultValue;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public boolean hasDefault() {
+ return defaultValue != null;
+ }
+ }
+}
diff --git a/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/util/ConfigValueParserTest.java b/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/util/ConfigValueParserTest.java
new file mode 100644
index 000000000..07fa54d8d
--- /dev/null
+++ b/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/util/ConfigValueParserTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2026 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.internal.common.core.util;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import static io.flamingock.internal.common.core.util.ConfigValueParser.BOOLEAN_VALUE_VALIDATOR;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link ConfigValueParser}.
+ *
+ * These tests cover:
+ *
+ * - Placeholder syntactic validation
+ * - Placeholder parsing
+ * - Config value classification (EMPTY, LITERAL, PLACEHOLDER)
+ * - Fail-fast behaviour for invalid placeholder syntax
+ * - Validation of literal and placeholder default values using a single validator
+ *
+ */
+class ConfigValueParserTest {
+
+ @Test
+ @DisplayName("isValidPlaceholder: should accept valid placeholders without defaults")
+ void isValidPlaceholder_shouldAcceptValidWithoutDefaults() {
+ assertTrue(ConfigValueParser.isValidPlaceholder("${a}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${my.custom.property}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${my-custom-property}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${my_custom_property}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${A1._-b}"));
+
+ assertTrue(ConfigValueParser.isValidPlaceholder("${ my.custom.property }"));
+ }
+
+ @Test
+ @DisplayName("isValidPlaceholder: should accept valid placeholders with defaults")
+ void isValidPlaceholder_shouldAcceptValidWithDefaults() {
+ assertTrue(ConfigValueParser.isValidPlaceholder("${a:b}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${a:}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${my.prop:someValue}"));
+ assertTrue(ConfigValueParser.isValidPlaceholder("${a:b:c}"));
+ }
+
+ @Test
+ @DisplayName("isValidPlaceholder: should reject invalid syntax")
+ void isValidPlaceholder_shouldRejectInvalidSyntax() {
+ assertFalse(ConfigValueParser.isValidPlaceholder(null));
+ assertFalse(ConfigValueParser.isValidPlaceholder(""));
+ assertFalse(ConfigValueParser.isValidPlaceholder(" "));
+ assertFalse(ConfigValueParser.isValidPlaceholder("${}"));
+ assertFalse(ConfigValueParser.isValidPlaceholder("${:x}"));
+ assertFalse(ConfigValueParser.isValidPlaceholder("${x"));
+ assertFalse(ConfigValueParser.isValidPlaceholder("${my custom}"));
+ }
+
+ @Test
+ @DisplayName("parsePlaceholder: should parse name and default correctly")
+ void parsePlaceholder_shouldParseCorrectly() {
+
+ Optional p1 =
+ ConfigValueParser.parsePlaceholder("value", "${my.property}");
+ assertTrue(p1.isPresent());
+ assertEquals("my.property", p1.get().getName());
+ assertFalse(p1.get().hasDefault());
+ assertNull(p1.get().getDefaultValue());
+
+ Optional p2 =
+ ConfigValueParser.parsePlaceholder("value", "${my.property:default}");
+ assertTrue(p2.isPresent());
+ assertEquals("my.property", p2.get().getName());
+ assertTrue(p2.get().hasDefault());
+ assertEquals("default", p2.get().getDefaultValue());
+
+ Optional p3 =
+ ConfigValueParser.parsePlaceholder("value", "${a:b:c}");
+ assertTrue(p3.isPresent());
+ assertEquals("a", p3.get().getName());
+ assertEquals("b:c", p3.get().getDefaultValue());
+ }
+
+ @Test
+ @DisplayName("parse: should classify EMPTY, LITERAL and PLACEHOLDER")
+ void parse_shouldClassifyCorrectly() {
+
+ ConfigValueParser.ConfigValue empty =
+ ConfigValueParser.parse("value", " ");
+ assertTrue(empty.isEmpty());
+
+ ConfigValueParser.ConfigValue literal =
+ ConfigValueParser.parse("value", "literalValue");
+ assertTrue(literal.isLiteral());
+ assertEquals("literalValue", literal.getLiteral().get());
+
+ ConfigValueParser.ConfigValue placeholder =
+ ConfigValueParser.parse("value", "${my.property}");
+ assertTrue(placeholder.isPlaceholder());
+ assertEquals("my.property", placeholder.getPlaceholder().get().getName());
+ }
+
+ @Test
+ @DisplayName("parse: should throw for invalid placeholder syntax")
+ void parse_shouldThrowForInvalidPlaceholder() {
+ assertThrows(IllegalArgumentException.class,
+ () -> ConfigValueParser.parse("value", "${}"));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> ConfigValueParser.parse("value", "${:x}"));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> ConfigValueParser.parse("value", "${x"));
+ }
+
+ @Test
+ @DisplayName("parse with validator: should validate literal values")
+ void parse_withValidator_shouldValidateLiteral() {
+
+ assertDoesNotThrow(() ->
+ ConfigValueParser.parse("flag", "true", BOOLEAN_VALUE_VALIDATOR));
+
+ assertDoesNotThrow(() ->
+ ConfigValueParser.parse("flag", "false", BOOLEAN_VALUE_VALIDATOR));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> ConfigValueParser.parse("flag", "yes", BOOLEAN_VALUE_VALIDATOR));
+ }
+
+ @Test
+ @DisplayName("parse with validator: should validate placeholder default values")
+ void parse_withValidator_shouldValidatePlaceholderDefault() {
+
+ assertDoesNotThrow(() ->
+ ConfigValueParser.parse("flag", "${flag:true}", BOOLEAN_VALUE_VALIDATOR));
+
+ assertDoesNotThrow(() ->
+ ConfigValueParser.parse("flag", "${flag:}", BOOLEAN_VALUE_VALIDATOR));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> ConfigValueParser.parse("flag", "${flag:yes}", BOOLEAN_VALUE_VALIDATOR));
+ }
+
+ @Test
+ @DisplayName("parse with null validator: should accept any literal or default")
+ void parse_withNullValidator_shouldAcceptAny() {
+
+ assertDoesNotThrow(() ->
+ ConfigValueParser.parse("value", "anything", null));
+
+ assertDoesNotThrow(() ->
+ ConfigValueParser.parse("value", "${x:anything}", null));
+ }
+}
diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractChangeRunnerBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractChangeRunnerBuilder.java
index 3bb757e18..0ebb3eebf 100644
--- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractChangeRunnerBuilder.java
+++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/builder/AbstractChangeRunnerBuilder.java
@@ -20,8 +20,10 @@
import io.flamingock.internal.common.core.context.ContextInjectable;
import io.flamingock.internal.common.core.context.ContextResolver;
import io.flamingock.internal.common.core.context.Dependency;
+import io.flamingock.internal.common.core.error.FlamingockException;
import io.flamingock.internal.common.core.metadata.FlamingockMetadata;
import io.flamingock.internal.common.core.template.ChangeTemplateManager;
+import io.flamingock.internal.common.core.util.ConfigValueParser;
import io.flamingock.internal.core.configuration.EventLifecycleConfigurator;
import io.flamingock.internal.core.configuration.core.CoreConfiguration;
import io.flamingock.internal.core.context.PriorityContext;
@@ -80,6 +82,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -247,23 +250,48 @@ private PriorityContext buildContext(FlamingockMetadata flamingockMetadata) {
logger.trace("injecting internal configuration");
addDependency(coreConfiguration);
addDependency(targetSystemManager);
- if (flamingockMetadata != null && flamingockMetadata.getProperties() != null) {
- flamingockMetadata.getProperties().forEach(this::setProperty);
- }
updateContextSpecific();
List dependencyContextsFromPlugins = pluginManager.getPlugins()
.stream()
.map(Plugin::getDependencyContext)
.flatMap(CollectionUtil::optionalToStream)
.collect(Collectors.toList());
- return dependencyContextsFromPlugins
+ PriorityContext priorityContext = dependencyContextsFromPlugins
.stream()
.filter(Objects::nonNull)
.reduce((previous, current) -> new PriorityContextResolver(current, previous))
.map(accumulated -> new PriorityContext(context, accumulated))
.orElse(new PriorityContext(new SimpleContext(), context));
+
+ if (flamingockMetadata != null && flamingockMetadata.getProperties() != null) {
+ flamingockMetadata.getProperties().forEach((k,v) -> this.processInternalProperty(priorityContext, k, v));
+ }
+
+ return priorityContext;
}
+ private void processInternalProperty(PriorityContext priorityContext, String propertyKey, String propertyValue) {
+
+ ConfigValueParser.ConfigValue configValue = ConfigValueParser.parse(propertyKey, propertyValue);
+
+ if (configValue.getPlaceholder().isPresent()) {
+ ConfigValueParser.Placeholder placeHolder = configValue.getPlaceholder().get();
+ Optional optPropertyValue = priorityContext.getProperty(placeHolder.getName());
+ if (optPropertyValue.isPresent()) {
+ propertyValue = optPropertyValue.get();
+ }
+ else if (placeHolder.hasDefault()) {
+ propertyValue = placeHolder.getDefaultValue();
+ }
+ else {
+ throw new FlamingockException("No property found named '" + placeHolder.getName() + "'.");
+ }
+ }
+
+ if (propertyValue != null && !propertyValue.trim().isEmpty()) {
+ this.setProperty(propertyKey, propertyValue);
+ }
+ }
@NotNull
private EventPublisher buildEventPublisher() {
diff --git a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessor.java b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessor.java
index 83646c775..2fdc7e7db 100644
--- a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessor.java
+++ b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessor.java
@@ -22,6 +22,7 @@
import io.flamingock.api.annotations.Stage;
import io.flamingock.core.processor.util.AnnotationFinder;
import io.flamingock.core.processor.util.PathResolver;
+import io.flamingock.core.processor.util.PluginFinder;
import io.flamingock.core.processor.util.ProjectRootDetector;
import io.flamingock.internal.common.core.metadata.BuilderProviderInfo;
import io.flamingock.internal.common.core.metadata.FlamingockMetadata;
@@ -30,6 +31,7 @@
import io.flamingock.internal.common.core.preview.PreviewPipeline;
import io.flamingock.internal.common.core.preview.PreviewStage;
import io.flamingock.internal.common.core.preview.SystemPreviewStage;
+import io.flamingock.internal.common.core.processor.ConfigurationPropertiesProvider;
import io.flamingock.internal.common.core.task.TaskDescriptor;
import io.flamingock.internal.common.core.util.LoggerPreProcessor;
import io.flamingock.internal.common.core.util.Serializer;
@@ -204,12 +206,21 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
}
logger.info("Processing pipeline configuration");
+ PluginFinder pluginFinder = new PluginFinder();
AnnotationFinder annotationFinder = new AnnotationFinder(roundEnv, logger, processingEnv);
EnableFlamingock flamingockAnnotation = annotationFinder.getPipelineAnnotation()
.orElseThrow(() -> new RuntimeException("@EnableFlamingock annotation is mandatory. Please annotate a class with @EnableFlamingock to configure the pipeline."));
- //TODO: get configuration properties from another interface
- Map properties = new HashMap<>();
- Collection allChanges = annotationFinder.findAnnotatedChanges(properties);
+
+ // Load and initialize plugins
+ pluginFinder.loadAndInitializePlugins(roundEnv, logger);
+
+ // Process plugins (ChangeDiscoverer)
+ Collection allChanges =
+ annotationFinder.findAnnotatedChanges(pluginFinder.getChangeDiscoverers());
+
+ // Process plugins (ConfigurationPropertiesProvider)
+ Map properties =
+ getPluginsConfigurationProperties(pluginFinder.getConfigurationPropertiesProviders());
// Find @FlamingockCliBuilder annotated method
Optional builderProvider = annotationFinder.findBuilderProvider();
@@ -251,6 +262,17 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
return true;
}
+ private Map getPluginsConfigurationProperties(List providers) {
+ // Process all plugins that implements ConfigurationPropertiesProvider interfaces, to get custom properties
+ // and finally add them to 'properties' map.
+ Map properties = new HashMap<>();
+ providers.stream()
+ .map(ConfigurationPropertiesProvider::getConfigurationProperties)
+ .flatMap(m -> m.entrySet().stream())
+ .forEach(e -> properties.put(e.getKey(), e.getValue()));
+ return properties;
+ }
+
private Map> getCodeChangesMapByPackage(Collection changes) {
Map> mapByPackage = new HashMap<>();
for (CodePreviewChange item : changes) {
diff --git a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/discover/FlamingockChangeDiscoverer.java b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessorPlugin.java
similarity index 75%
rename from core/flamingock-processor/src/main/java/io/flamingock/core/processor/discover/FlamingockChangeDiscoverer.java
rename to core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessorPlugin.java
index 86cccd17b..0ad9ec582 100644
--- a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/discover/FlamingockChangeDiscoverer.java
+++ b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessorPlugin.java
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.flamingock.core.processor.discover;
+package io.flamingock.core.processor;
import io.flamingock.api.annotations.Change;
+import io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin;
import io.flamingock.internal.common.core.util.LoggerPreProcessor;
-import io.flamingock.internal.common.core.discover.ChangeDiscoverer;
+import io.flamingock.internal.common.core.processor.ChangeDiscoverer;
import io.flamingock.internal.common.core.preview.CodePreviewChange;
import io.flamingock.internal.common.core.preview.builder.CodePreviewTaskBuilder;
@@ -25,15 +26,23 @@
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import java.util.Collection;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
-public class FlamingockChangeDiscoverer implements ChangeDiscoverer {
+public class FlamingockAnnotationProcessorPlugin implements AnnotationProcessorPlugin, ChangeDiscoverer {
+
+ private RoundEnvironment roundEnv;
+ private LoggerPreProcessor logger;
+
+ @Override
+ public void initialize(RoundEnvironment roundEnv, LoggerPreProcessor logger) {
+ this.roundEnv = roundEnv;
+ this.logger = logger;
+ }
@Override
- public Collection findAnnotatedChanges(RoundEnvironment roundEnv, LoggerPreProcessor logger, Map properties) {
+ public Collection findAnnotatedChanges() {
logger.info("Searching for code-based changes (Java classes annotated with @Change annotation)");
return roundEnv.getElementsAnnotatedWith(Change.class)
.stream()
diff --git a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/AnnotationFinder.java b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/AnnotationFinder.java
index 7deee1439..5f5dc7ea9 100644
--- a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/AnnotationFinder.java
+++ b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/AnnotationFinder.java
@@ -17,7 +17,8 @@
import io.flamingock.api.annotations.EnableFlamingock;
import io.flamingock.api.annotations.FlamingockCliBuilder;
-import io.flamingock.internal.common.core.discover.ChangeDiscoverer;
+import io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin;
+import io.flamingock.internal.common.core.processor.ChangeDiscoverer;
import io.flamingock.internal.common.core.metadata.BuilderProviderInfo;
import io.flamingock.internal.common.core.preview.CodePreviewChange;
import io.flamingock.internal.common.core.util.LoggerPreProcessor;
@@ -63,38 +64,16 @@ public Optional getPipelineAnnotation() {
.findFirst();
}
- public Collection findAnnotatedChanges(Map properties) {
+ public Collection findAnnotatedChanges(List changeDiscoverers) {
logger.info("Searching for code-based changes");
- return getAllChangeDiscoverers()
+ return changeDiscoverers
.stream()
.peek(cd -> logger.info(String.format("Using %s for discover changes", cd.getClass().getName())))
- .map(cd -> cd.findAnnotatedChanges(roundEnv, logger, properties))
+ .map(ChangeDiscoverer::findAnnotatedChanges)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
- private List getAllChangeDiscoverers() {
- Set seen = new LinkedHashSet<>();
- List result = new ArrayList<>();
-
- ClassLoader[] loaders = new ClassLoader[] {
- Thread.currentThread().getContextClassLoader(),
- ChangeDiscoverer.class.getClassLoader(),
- ClassLoader.getSystemClassLoader()
- };
-
- for (ClassLoader cl : loaders) {
- if (cl == null) continue;
- ServiceLoader sl = ServiceLoader.load(ChangeDiscoverer.class, cl);
- for (ChangeDiscoverer d : sl) {
- if (seen.add(d.getClass().getName())) {
- result.add(d);
- }
- }
- }
- return result;
- }
-
/**
* Finds the @FlamingockCliBuilder annotated method and validates it.
*
diff --git a/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/PluginFinder.java b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/PluginFinder.java
new file mode 100644
index 000000000..3076f956d
--- /dev/null
+++ b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/PluginFinder.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2026 Flamingock (https://www.flamingock.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.flamingock.core.processor.util;
+
+import io.flamingock.internal.common.core.error.FlamingockException;
+import io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin;
+import io.flamingock.internal.common.core.processor.ChangeDiscoverer;
+import io.flamingock.internal.common.core.processor.ConfigurationPropertiesProvider;
+import io.flamingock.internal.common.core.util.LoggerPreProcessor;
+
+import javax.annotation.processing.RoundEnvironment;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class PluginFinder {
+
+ private boolean initialized = false;
+ private List plugins;
+
+ public PluginFinder() {
+ // No-op.
+ }
+
+ public void loadAndInitializePlugins(RoundEnvironment roundEnv, LoggerPreProcessor logger) {
+ plugins = findAnnotationProcessorPlugins();
+ plugins.forEach(p -> p.initialize(roundEnv, logger));
+ initialized = true;
+ }
+
+ public List getChangeDiscoverers() {
+ return getPluginsByType(ChangeDiscoverer.class);
+ }
+
+ public List getConfigurationPropertiesProviders() {
+ return getPluginsByType(ConfigurationPropertiesProvider.class);
+ }
+
+
+ private void checkInitialized() {
+ if (!initialized) {
+ throw new FlamingockException("%s must be initialized before using it.", PluginFinder.class.getSimpleName());
+ }
+ }
+
+ private List findAnnotationProcessorPlugins() {
+ Set seen = new LinkedHashSet<>();
+ List result = new ArrayList<>();
+
+ ClassLoader[] loaders = new ClassLoader[] {
+ Thread.currentThread().getContextClassLoader(),
+ AnnotationProcessorPlugin.class.getClassLoader(),
+ ClassLoader.getSystemClassLoader()
+ };
+
+ for (ClassLoader cl : loaders) {
+ if (cl == null) continue;
+
+ ServiceLoader sl =
+ ServiceLoader.load(AnnotationProcessorPlugin.class, cl);
+
+ for (AnnotationProcessorPlugin plugin : sl) {
+ if (seen.add(plugin.getClass().getName())) {
+ result.add(plugin);
+ }
+ }
+ }
+ return result;
+ }
+
+ private List getPluginsByType(Class type) {
+ checkInitialized();
+ return plugins.stream()
+ .filter(type::isInstance)
+ .map(type::cast)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/core/flamingock-processor/src/main/resources/META-INF/services/io.flamingock.internal.common.core.discover.ChangeDiscoverer b/core/flamingock-processor/src/main/resources/META-INF/services/io.flamingock.internal.common.core.discover.ChangeDiscoverer
deleted file mode 100644
index 0047a5109..000000000
--- a/core/flamingock-processor/src/main/resources/META-INF/services/io.flamingock.internal.common.core.discover.ChangeDiscoverer
+++ /dev/null
@@ -1 +0,0 @@
-io.flamingock.core.processor.discover.FlamingockChangeDiscoverer
\ No newline at end of file
diff --git a/core/flamingock-processor/src/main/resources/META-INF/services/io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin b/core/flamingock-processor/src/main/resources/META-INF/services/io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin
new file mode 100644
index 000000000..ae25bc244
--- /dev/null
+++ b/core/flamingock-processor/src/main/resources/META-INF/services/io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin
@@ -0,0 +1 @@
+io.flamingock.core.processor.FlamingockAnnotationProcessorPlugin
\ No newline at end of file
diff --git a/legacy/mongock-importer-couchbase/src/test/java/io/flamingock/importer/mongock/couchbase/CouchbaseImporterTest.java b/legacy/mongock-importer-couchbase/src/test/java/io/flamingock/importer/mongock/couchbase/CouchbaseImporterTest.java
index dc2101d9e..513e87d60 100644
--- a/legacy/mongock-importer-couchbase/src/test/java/io/flamingock/importer/mongock/couchbase/CouchbaseImporterTest.java
+++ b/legacy/mongock-importer-couchbase/src/test/java/io/flamingock/importer/mongock/couchbase/CouchbaseImporterTest.java
@@ -46,7 +46,7 @@
import java.util.List;
import java.util.stream.Collectors;
-import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
+import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_ORIGIN_PROPERTY_KEY;
import static io.flamingock.internal.util.constants.AuditEntryFieldConstants.KEY_CREATED_AT;
import static io.flamingock.internal.util.constants.AuditEntryFieldConstants.KEY_STATE;
@@ -233,7 +233,7 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginEnabled_WHEN_migratingToF
.withScopeName(FLAMINGOCK_SCOPE_NAME)
.withAuditRepositoryName(FLAMINGOCK_COLLECTION_NAME))
.addTargetSystem(targetSystem)
- .setProperty(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.FALSE.toString())
+ .setProperty(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.FALSE.toString())
.build();
FlamingockException ex = assertThrows(FlamingockException.class, flamingock::run);
@@ -256,7 +256,7 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginDisabled_WHEN_migratingTo
.withScopeName(FLAMINGOCK_SCOPE_NAME)
.withAuditRepositoryName(FLAMINGOCK_COLLECTION_NAME))
.addTargetSystem(targetSystem)
- .setProperty(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.TRUE.toString())
+ .setProperty(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.TRUE.toString())
.build();
flamingock.run();
diff --git a/legacy/mongock-importer-dynamodb/src/test/java/io/flamingock/importer/mongock/dynamodb/DynamoDBImporterTest.java b/legacy/mongock-importer-dynamodb/src/test/java/io/flamingock/importer/mongock/dynamodb/DynamoDBImporterTest.java
index b8670217a..dc7214891 100644
--- a/legacy/mongock-importer-dynamodb/src/test/java/io/flamingock/importer/mongock/dynamodb/DynamoDBImporterTest.java
+++ b/legacy/mongock-importer-dynamodb/src/test/java/io/flamingock/importer/mongock/dynamodb/DynamoDBImporterTest.java
@@ -27,7 +27,6 @@
import io.flamingock.support.mongock.annotations.MongockSupport;
import io.flamingock.targetsystem.dynamodb.DynamoDBTargetSystem;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -44,13 +43,11 @@
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
import static io.flamingock.core.kit.audit.AuditEntryExpectation.APPLIED;
import static io.flamingock.core.kit.audit.AuditEntryExpectation.STARTED;
import static io.flamingock.internal.common.core.metadata.Constants.DEFAULT_MONGOCK_ORIGIN;
-import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
+import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_ORIGIN_PROPERTY_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -235,7 +232,7 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginEnabled_WHEN_migratingToF
Runner flamingock = testKit.createBuilder()
.addTargetSystem(dynamodbTargetSystem)
- .setProperty(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.FALSE.toString())
+ .setProperty(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.FALSE.toString())
.build();
FlamingockException ex = assertThrows(FlamingockException.class, flamingock::run);
@@ -255,7 +252,7 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginDisabled_WHEN_migratingTo
Runner flamingock = testKit.createBuilder()
.addTargetSystem(dynamodbTargetSystem)
- .setProperty(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.TRUE.toString())
+ .setProperty(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.TRUE.toString())
.build();
flamingock.run();
diff --git a/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java b/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java
index 8eb3022ae..a14e0cf49 100644
--- a/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java
+++ b/legacy/mongock-importer-mongodb/src/test/java/io/flamingock/importer/mongock/mongodb/MongoDBImporterTest.java
@@ -47,7 +47,7 @@
import static io.flamingock.core.kit.audit.AuditEntryExpectation.APPLIED;
import static io.flamingock.core.kit.audit.AuditEntryExpectation.STARTED;
import static io.flamingock.internal.common.core.metadata.Constants.DEFAULT_MONGOCK_ORIGIN;
-import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
+import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_ORIGIN_PROPERTY_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -244,7 +244,7 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginEnabled_WHEN_migratingToF
Runner flamingock = testKit.createBuilder()
.addTargetSystem(mongodbTargetSystem)
- .setProperty(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.FALSE.toString())
+ .setProperty(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.FALSE.toString())
.build();
FlamingockException ex = assertThrows(FlamingockException.class, flamingock::run);
@@ -265,7 +265,7 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginDisabled_WHEN_migratingTo
Runner flamingock = testKit.createBuilder()
.addTargetSystem(mongodbTargetSystem)
- .setProperty(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.TRUE.toString())
+ .setProperty(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY, Boolean.TRUE.toString())
.build();
flamingock.run();
@@ -307,11 +307,11 @@ void GIVEN_mongockAuditHistoryEmptyAndFailIfEmptyOriginDisabled_WHEN_migratingTo
@Test
@DisplayName("GIVEN all Mongock changeUnits already executed" +
- "AND custom origin repository name provided" +
+ "AND custom origin repository name provided by literal value " +
"WHEN migrating to Flamingock Community " +
"THEN should import the entire history " +
"AND execute the pending flamingock changes")
- void GIVEN_allMongockChangeUnitsAlreadyExecutedAndCustomOriginProvided_WHEN_migratingToFlamingockCommunity_THEN_shouldImportEntireHistory() {
+ void GIVEN_allMongockChangeUnitsAlreadyExecutedAndCustomOriginProvidedByLiteralValue_WHEN_migratingToFlamingockCommunity_THEN_shouldImportEntireHistory() {
// Setup Mongock entries
final String customMongockOrigin = "mongockCustomOriginCollection";
diff --git a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java
index b8b09ad42..33a3454aa 100644
--- a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java
+++ b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/MongockImportChange.java
@@ -23,6 +23,7 @@
import io.flamingock.internal.common.core.audit.AuditWriter;
import io.flamingock.internal.common.core.error.FlamingockException;
import io.flamingock.internal.common.core.pipeline.PipelineDescriptor;
+import io.flamingock.internal.common.core.util.ConfigValueParser;
import io.flamingock.internal.core.external.targets.TargetSystemManager;
import io.flamingock.internal.core.external.targets.operations.TargetSystemOps;
import io.flamingock.internal.core.external.targets.operations.TransactionalTargetSystemOps;
@@ -33,7 +34,7 @@
import java.util.List;
import static io.flamingock.internal.common.core.audit.AuditReaderType.MONGOCK;
-import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
+import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
/**
* This ChangeUnit is intentionally not annotated with @Change, @Apply, or similar,
@@ -47,7 +48,7 @@ public void importHistory(@Named("change.targetSystem.id") String targetSystemId
@NonLockGuarded TargetSystemManager targetSystemManager,
@NonLockGuarded AuditWriter auditWriter,
@NonLockGuarded PipelineDescriptor pipelineDescriptor,
- @Nullable @Named(MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY) String emptyOriginAllowed) {
+ @Nullable @Named(MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY) String emptyOriginAllowed) {
logger.info("Starting audit log migration from Mongock to Flamingock community audit store");
AuditHistoryReader legacyHistoryReader = getAuditHistoryReader(targetSystemId, targetSystemManager);
PipelineHelper pipelineHelper = new PipelineHelper(pipelineDescriptor);
@@ -98,7 +99,7 @@ private boolean resolveEmptyOriginAllowed(String raw) {
String v = raw.trim();
if ("true".equalsIgnoreCase(v)) return true;
if ("false".equalsIgnoreCase(v)) return false;
- throw new FlamingockException("Invalid value for emptyOriginAllowed: " + raw
+ throw new FlamingockException("Invalid value for " + MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY + ": " + raw
+ " (expected \"true\" or \"false\" or empty)");
}
}
diff --git a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/discover/MongockChangeDiscoverer.java b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java
similarity index 60%
rename from legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/discover/MongockChangeDiscoverer.java
rename to legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java
index 0dffc7a3d..43147d885 100644
--- a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/discover/MongockChangeDiscoverer.java
+++ b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java
@@ -13,19 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.flamingock.support.mongock.processor.discover;
+package io.flamingock.support.mongock.processor;
import com.github.cloudyrock.mongock.ChangeLog;
import io.flamingock.internal.common.core.audit.AuditWriter;
-import io.flamingock.internal.common.core.discover.ChangeDiscoverer;
+import io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin;
+import io.flamingock.internal.common.core.processor.ChangeDiscoverer;
import io.flamingock.internal.common.core.error.FlamingockException;
import io.flamingock.internal.common.core.pipeline.PipelineDescriptor;
import io.flamingock.internal.common.core.preview.CodePreviewChange;
import io.flamingock.internal.common.core.preview.PreviewConstructor;
import io.flamingock.internal.common.core.preview.PreviewMethod;
import io.flamingock.internal.common.core.preview.builder.CodePreviewTaskBuilder;
+import io.flamingock.internal.common.core.processor.ConfigurationPropertiesProvider;
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
import io.flamingock.internal.common.core.task.TargetSystemDescriptor;
+import io.flamingock.internal.common.core.util.ConfigValueParser;
import io.flamingock.internal.common.core.util.LoggerPreProcessor;
import io.flamingock.internal.core.external.targets.TargetSystemManager;
import io.flamingock.support.mongock.MongockImportChange;
@@ -38,6 +41,7 @@
import javax.lang.model.element.TypeElement;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -45,43 +49,58 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
+import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_EMPTY_ORIGIN_ALLOWED_PROPERTY_KEY;
import static io.flamingock.internal.common.core.metadata.Constants.MONGOCK_IMPORT_ORIGIN_PROPERTY_KEY;
@SuppressWarnings("deprecation")
-public class MongockChangeDiscoverer implements ChangeDiscoverer {
+public class MongockAnnotationProcessorPlugin implements AnnotationProcessorPlugin, ChangeDiscoverer, ConfigurationPropertiesProvider {
- @Override
- public Collection findAnnotatedChanges(RoundEnvironment roundEnv, LoggerPreProcessor logger, Map properties) {
+ private RoundEnvironment roundEnv;
+ private LoggerPreProcessor logger;
+ private MongockSupport mongockSupport;
+ @Override
+ public void initialize(RoundEnvironment roundEnv, LoggerPreProcessor logger) {
+ this.roundEnv = roundEnv;
+ this.logger = logger;
Optional mongockSupportOpt = this.getMongockSupportAnnotation(roundEnv, logger);
- final String mongockTargetSystemId = mongockSupportOpt.map(MongockSupport::targetSystem).orElse(null);
-
logger.info(String.format("Searching for @MongockSupport annotation: %s", mongockSupportOpt.isPresent() ? "Found" : "Not found"));
+ mongockSupport = mongockSupportOpt.orElseThrow(() -> new FlamingockException("@MongockSupport annotation must be provided when mongock-support module is present."));
+ }
- if (mongockSupportOpt.isPresent()) {
- logger.info("Searching for code-based changes (Java classes annotated with @ChangeUnit or @ChangeLog annotations)");
- List changes = Stream.concat(
- roundEnv.getElementsAnnotatedWith(ChangeUnit.class).stream(),
- roundEnv.getElementsAnnotatedWith(ChangeLog.class).stream()
- )
- .filter(e -> e.getKind() == ElementKind.CLASS)
- .map(e -> (TypeElement) e)
- .map(e -> buildCodePreviewChange(e, mongockTargetSystemId))
- .flatMap(List::stream)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- changes.add(getImporterChange(mongockTargetSystemId));
-
- // Adding Mongock specific configuration properties
- processConfigurationProperties(mongockSupportOpt.get(), properties);
-
- return changes;
- } else {
- throw new FlamingockException("@MongockSupport annotation must be provided when mongock-support module is present.");
- }
+ @Override
+ public Collection findAnnotatedChanges() {
+
+ final String mongockTargetSystemId = mongockSupport.targetSystem();
+
+ logger.info("Searching for code-based changes (Java classes annotated with @ChangeUnit or @ChangeLog annotations)");
+ List changes = Stream.concat(
+ roundEnv.getElementsAnnotatedWith(ChangeUnit.class).stream(),
+ roundEnv.getElementsAnnotatedWith(ChangeLog.class).stream()
+ )
+ .filter(e -> e.getKind() == ElementKind.CLASS)
+ .map(e -> (TypeElement) e)
+ .map(e -> buildCodePreviewChange(e, mongockTargetSystemId))
+ .flatMap(List::stream)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ changes.add(getImporterChange(mongockTargetSystemId));
+
+ return changes;
}
+ @Override
+ public Map getConfigurationProperties() {
+
+ Map properties = new HashMap<>();
+
+ // Adding Mongock specific configuration properties
+ processConfigurationProperties(mongockSupport, properties);
+
+ return properties;
+ }
+
+
private CodePreviewChange getImporterChange(String targetSystemId) {
CodePreviewTaskBuilder builder = CodePreviewTaskBuilder.instance();
builder.setId("migration-mongock-to-flamingock-community");
@@ -123,23 +142,18 @@ private void processConfigurationProperties(MongockSupport mongockSupport, Map