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..01282a4d7 --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/AnnotationProcessorPlugin.java @@ -0,0 +1,25 @@ +/* + * 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; + +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 81% 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..d1eb313e9 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,15 @@ * 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(RoundEnvironment roundEnv, LoggerPreProcessor logger); } 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..d30ece9ad --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/processor/ConfigurationPropertiesProvider.java @@ -0,0 +1,26 @@ +/* + * 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 io.flamingock.internal.common.core.util.LoggerPreProcessor; + +import javax.annotation.processing.RoundEnvironment; +import java.util.Map; + +public interface ConfigurationPropertiesProvider { + + Map getConfigurationProperties(RoundEnvironment roundEnv, LoggerPreProcessor logger); +} 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 69a416d00..3dc02189b 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; @@ -34,12 +35,16 @@ import io.flamingock.internal.common.core.preview.PreviewStage; import io.flamingock.internal.common.core.preview.SystemPreviewStage; import io.flamingock.internal.common.core.preview.TemplatePreviewChange; +import io.flamingock.internal.common.core.processor.AnnotationProcessorPlugin; +import io.flamingock.internal.common.core.processor.ConfigurationPropertiesProvider; import io.flamingock.internal.common.core.task.TaskDescriptor; import io.flamingock.internal.common.core.template.ChangeTemplateFileContent; import io.flamingock.internal.common.core.template.TemplateValidator; import io.flamingock.internal.common.core.util.LoggerPreProcessor; import io.flamingock.internal.common.core.util.Serializer; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import javax.annotation.processing.AbstractProcessor; @@ -164,6 +169,7 @@ public class FlamingockAnnotationProcessor extends AbstractProcessor { private static final List DEFAULT_SOURCE_DIRS = Arrays.asList( "src/main/java", "src/main/kotlin", "src/main/scala", "src/main/groovy" ); + private static final Logger log = LoggerFactory.getLogger(FlamingockAnnotationProcessor.class); private static boolean hasProcessed = false; @@ -213,9 +219,17 @@ public boolean process(Set annotations, RoundEnvironment 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 + + // Find plugins and initialize + List plugins = PluginFinder.findAnnotationProcessorPlugins(); + plugins.forEach(p -> p.initialize(roundEnv, logger)); + + // Process plugins (ChangeDiscoverer) + Collection allChanges = annotationFinder.findAnnotatedChanges(plugins); + + // Process plugins (ConfigurationPropertiesProvider) Map properties = new HashMap<>(); - Collection allChanges = annotationFinder.findAnnotatedChanges(properties); + processConfigurationPropertiesPlugins(plugins, properties, roundEnv, logger); // Find @FlamingockCliBuilder annotated method Optional builderProvider = annotationFinder.findBuilderProvider(); @@ -258,6 +272,21 @@ public boolean process(Set annotations, RoundEnvironment return true; } + private void processConfigurationPropertiesPlugins(List plugins, + Map properties, + RoundEnvironment roundEnv, + LoggerPreProcessor logger) { + // Process all plugins that implements ConfigurationPropertiesProvider interfaces, to get custom properties + // and finally add them to 'properties' map. + plugins.stream() + .filter(p -> p instanceof ConfigurationPropertiesProvider) + .map(p -> (ConfigurationPropertiesProvider)p) + .map(p -> p.getConfigurationProperties(roundEnv, logger)) + .flatMap(m -> m.entrySet().stream()) + .forEach(e -> properties.put(e.getKey(), e.getValue())); + + } + 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 80% 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..fe92e6611 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,19 @@ 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 { @Override - public Collection findAnnotatedChanges(RoundEnvironment roundEnv, LoggerPreProcessor logger, Map properties) { + public void initialize(RoundEnvironment roundEnv, LoggerPreProcessor logger) { + // No-op. + } + + @Override + public Collection findAnnotatedChanges(RoundEnvironment roundEnv, LoggerPreProcessor logger) { 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..0178f2ee2 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,18 @@ public Optional getPipelineAnnotation() { .findFirst(); } - public Collection findAnnotatedChanges(Map properties) { + public Collection findAnnotatedChanges(List plugins) { logger.info("Searching for code-based changes"); - return getAllChangeDiscoverers() + return plugins .stream() + .filter(p -> p instanceof ChangeDiscoverer) + .map(p -> (ChangeDiscoverer)p) .peek(cd -> logger.info(String.format("Using %s for discover changes", cd.getClass().getName()))) - .map(cd -> cd.findAnnotatedChanges(roundEnv, logger, properties)) + .map(cd -> cd.findAnnotatedChanges(roundEnv, logger)) .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..06cb75bad --- /dev/null +++ b/core/flamingock-processor/src/main/java/io/flamingock/core/processor/util/PluginFinder.java @@ -0,0 +1,56 @@ +/* + * 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.processor.AnnotationProcessorPlugin; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; + +public final class PluginFinder { + + private PluginFinder() { + // No-op. + } + + public static 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; + } +} 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 61% 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..474ca5fb1 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,54 @@ 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 MongockSupport mongockSupport; + @Override + public void initialize(RoundEnvironment roundEnv, LoggerPreProcessor 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(RoundEnvironment roundEnv, LoggerPreProcessor logger) { + + 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(RoundEnvironment roundEnv, LoggerPreProcessor logger) { + + 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 +138,18 @@ private void processConfigurationProperties(MongockSupport mongockSupport, Map