diff --git a/.gitignore b/.gitignore index ce463d6bc58d5..b6dbe45b555a1 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ documentation/rebuildpdf.bat .ai-commons/skills/manage-zephyr-test-cases/references/.env .claude/CLAUDE.md .github/copilot-instructions.md +.claude/settings.json diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/schema/FixedSchema.java b/component-api/src/main/java/org/talend/sdk/component/api/service/schema/FixedSchema.java index f79d48508bd6b..76d30845aad3b 100644 --- a/component-api/src/main/java/org/talend/sdk/component/api/service/schema/FixedSchema.java +++ b/component-api/src/main/java/org/talend/sdk/component/api/service/schema/FixedSchema.java @@ -27,7 +27,8 @@ @Retention(RUNTIME) @Documentation("Mark a connector as having a fixed schema. " + "Annotation's value must match the name of a DiscoverSchema or a DiscoverSchemaExtended annotation. " + - "The related action will return the fixed schema for the specified flows.") + "The related action will return the fixed schema for the specified flows. " + + "Use watch() to declare parameter paths whose changes should trigger a schema refresh at design time.") public @interface FixedSchema { String value() default ""; @@ -42,4 +43,19 @@ */ String[] flows() default {}; + /** + * The parameter paths to watch for changes at design time. When any of the listed parameters + * changes, the associated {@code DiscoverSchema} or {@code DiscoverSchemaExtended} service + * will be re-invoked to refresh the fixed schema dynamically. + *

+ * Uses the same relative-path syntax as {@code @Updatable.parameters()}: for example, + * {@code "configuration/someParam"} refers to a child parameter of the component configuration root, + * and {@code "../sibling"} refers to a sibling parameter. + *

+ * Requires {@link #value()} to be set. + * + * @return the parameter paths to watch. Default to none. + */ + String[] watch() default {}; + } diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricher.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricher.java index 3e3b92acb6a53..ebb35f021aab7 100644 --- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricher.java +++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricher.java @@ -46,6 +46,8 @@ public class ComponentSchemaEnricher implements ComponentMetadataEnricher { public static final String FIXED_SCHEMA_FLOWS_META_PREFIX = "tcomp::ui::schema::flows::fixed"; + public static final String FIXED_SCHEMA_WATCH_META_PREFIX = "tcomp::ui::schema::fixed::watch"; + private static final Set> SUPPORTED_ANNOTATIONS = new HashSet<>(Arrays.asList(PartitionMapper.class, Emitter.class, Processor.class, DriverRunner.class)); @@ -84,6 +86,9 @@ public Map onComponent(final Type type, final Annotation[] annot } else { metadata.put(FIXED_SCHEMA_FLOWS_META_PREFIX, Branches.DEFAULT_BRANCH); } + if (fixedSchema.watch().length > 0) { + metadata.put(FIXED_SCHEMA_WATCH_META_PREFIX, String.join(",", fixedSchema.watch())); + } return metadata; } diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricherTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricherTest.java index 7518f6caacad1..ac6849bc69c8f 100644 --- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricherTest.java +++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/extension/ComponentSchemaEnricherTest.java @@ -60,6 +60,18 @@ void fixedSchemaMetadataNotPresent() { assertEquals(emptyMap(), enricher.onComponent(MyProcessor.class, MyProcessor.class.getAnnotations())); } + @Test + void fixedSchemaMetadataWithWatchPresent() { + assertEquals(new HashMap() { + + { + put("tcomp::ui::schema::fixed", "discover"); + put("tcomp::ui::schema::flows::fixed", "__default__"); + put("tcomp::ui::schema::fixed::watch", "configuration/param1,configuration/param2"); + } + }, enricher.onComponent(EmitterWithWatch.class, EmitterWithWatch.class.getAnnotations())); + } + @Test void dbMappingMetadataPresent() { assertEquals(new HashMap() { @@ -103,4 +115,10 @@ private static class MyProcessor { private static class MyEmitter { } -} + + @Emitter + @FixedSchema(value = "discover", watch = { "configuration/param1", "configuration/param2" }) + private static class EmitterWithWatch { + + } +} \ No newline at end of file diff --git a/component-tools/src/main/java/org/talend/sdk/component/tools/validator/FixedSchemaValidator.java b/component-tools/src/main/java/org/talend/sdk/component/tools/validator/FixedSchemaValidator.java index 3c8750e031343..1dd922fcf337b 100644 --- a/component-tools/src/main/java/org/talend/sdk/component/tools/validator/FixedSchemaValidator.java +++ b/component-tools/src/main/java/org/talend/sdk/component/tools/validator/FixedSchemaValidator.java @@ -43,6 +43,14 @@ public Stream validate(final AnnotationFinder finder, final List String.format("Empty @FixedSchema annotation's value in class %s.", e.getKey().getSimpleName())) .toList()); + // search for watch() declared without value() + errors.addAll(finder.findAnnotatedClasses(FixedSchema.class) + .stream() + .filter(c -> c.getAnnotation(FixedSchema.class).watch().length > 0 + && c.getAnnotation(FixedSchema.class).value().isEmpty()) + .map(c -> String.format("@FixedSchema.watch() requires value() to be set in class %s.", + c.getSimpleName())) + .toList()); // search for missing methods final List methods = Stream .concat(finder.findAnnotatedMethods(DiscoverSchema.class) diff --git a/component-tools/src/test/java/org/talend/sdk/component/tools/validator/FixedSchemaValidatorTest.java b/component-tools/src/test/java/org/talend/sdk/component/tools/validator/FixedSchemaValidatorTest.java index 1c39da34af2d6..9d265a209e038 100644 --- a/component-tools/src/test/java/org/talend/sdk/component/tools/validator/FixedSchemaValidatorTest.java +++ b/component-tools/src/test/java/org/talend/sdk/component/tools/validator/FixedSchemaValidatorTest.java @@ -16,9 +16,11 @@ package org.talend.sdk.component.tools.validator; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.Serializable; import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; import org.apache.xbean.finder.AnnotationFinder; @@ -53,6 +55,20 @@ void validateFixedSchema() { assertEquals(2, errors.count()); } + @Test + void validateFixedSchemaWatchWithoutValue() { + final FixedSchemaValidator validator = new FixedSchemaValidator(); + final AnnotationFinder finder = + new AnnotationFinder(new ClassesArchive(MySourceWatchNoValue.class, FixedService.class)); + final Stream errors = + validator.validate(finder, Arrays.asList(MySourceWatchNoValue.class, FixedService.class)); + final List errorList = errors.toList(); + assertEquals(2, errorList.size()); + assertTrue(errorList.stream() + .anyMatch(e -> e.contains("@FixedSchema.watch() requires value() to be set") + && e.contains("MySourceWatchNoValue"))); + } + @Emitter(family = "test", name = "mysource0") @FixedSchema("discover") static class MySourceOk implements Serializable { @@ -113,4 +129,14 @@ public Schema discover(DS ds, String branch) { return null; } } + + @Emitter(family = "test", name = "mysourcewatchnovalue") + @FixedSchema(watch = { "configuration/param" }) + static class MySourceWatchNoValue implements Serializable { + + @Producer + public Record next() { + return null; + } + } } diff --git a/sample-parent/sample-features/fixed-schema/src/main/java/org/talend/sdk/component/sample/feature/fixedschema/input/FixedSchemaInput.java b/sample-parent/sample-features/fixed-schema/src/main/java/org/talend/sdk/component/sample/feature/fixedschema/input/FixedSchemaInput.java index 5b2e34c3b9b01..44f1266dc7d39 100644 --- a/sample-parent/sample-features/fixed-schema/src/main/java/org/talend/sdk/component/sample/feature/fixedschema/input/FixedSchemaInput.java +++ b/sample-parent/sample-features/fixed-schema/src/main/java/org/talend/sdk/component/sample/feature/fixedschema/input/FixedSchemaInput.java @@ -31,7 +31,7 @@ @Icon(value = Icon.IconType.CUSTOM, custom = "icon") @Emitter(name = "Input") @Documentation("Fixed schema sample input connector.") -@FixedSchema("fixedschemadse") +@FixedSchema(value = "fixedschemadse", watch = { "configuration/aBoolean" }) public class FixedSchemaInput implements Serializable { private final Config config;