diff --git a/component-api/src/main/java/org/talend/sdk/component/api/record/Record.java b/component-api/src/main/java/org/talend/sdk/component/api/record/Record.java index b9258587bdbee..341eb178d0783 100644 --- a/component-api/src/main/java/org/talend/sdk/component/api/record/Record.java +++ b/component-api/src/main/java/org/talend/sdk/component/api/record/Record.java @@ -34,6 +34,8 @@ public interface Record { + String RECORD_ERROR_SUPPORT = "talend.component.record.error.support"; + /** * @return the schema of this record. */ @@ -311,6 +313,11 @@ default Optional getOptionalRecord(final String name) { return ofNullable(get(Record.class, name)); } + default boolean isValid() { + return !getSchema().getAllEntries() + .anyMatch(entry -> !entry.isValid()); + } + /** * Allows to create a record with a fluent API. This is the unique recommended way to create a record. */ diff --git a/component-api/src/main/java/org/talend/sdk/component/api/record/Schema.java b/component-api/src/main/java/org/talend/sdk/component/api/record/Schema.java index 5050102b4879e..210d903cca1e4 100644 --- a/component-api/src/main/java/org/talend/sdk/component/api/record/Schema.java +++ b/component-api/src/main/java/org/talend/sdk/component/api/record/Schema.java @@ -232,6 +232,16 @@ interface Entry { */ boolean isMetadata(); + /** + * @return Is this entry can be in error. + */ + boolean isErrorCapable(); + + /** + * @return true if the value of this entry is valid; false for invalid value. + */ + boolean isValid(); + /** * @param the default value type. * @@ -296,6 +306,14 @@ default Entry.Builder toBuilder() { throw new UnsupportedOperationException("#toBuilder is not implemented"); } + default String getErrorMessage() { + return getProp(SchemaProperty.ENTRY_ERROR_MESSAGE); + } + + default String getErrorFallbackValue() { + return getProp(SchemaProperty.ENTRY_ERROR_FALLBACK_VALUE); + } + /** * Plain builder matching {@link Entry} structure. */ @@ -317,6 +335,8 @@ default Builder withLogicalType(String logicalType) { Builder withNullable(boolean nullable); + Builder withErrorCapable(boolean errorCapable); + Builder withMetadata(boolean metadata); Builder withDefaultValue(T value); diff --git a/component-api/src/main/java/org/talend/sdk/component/api/record/SchemaProperty.java b/component-api/src/main/java/org/talend/sdk/component/api/record/SchemaProperty.java index 076467334ce3c..25954bba2adb9 100644 --- a/component-api/src/main/java/org/talend/sdk/component/api/record/SchemaProperty.java +++ b/component-api/src/main/java/org/talend/sdk/component/api/record/SchemaProperty.java @@ -37,6 +37,12 @@ public interface SchemaProperty { String ALLOW_SPECIAL_NAME = "field.special.name"; + String ENTRY_IS_ON_ERROR = "entry.on.error"; + + String ENTRY_ERROR_MESSAGE = "entry.error.message"; + + String ENTRY_ERROR_FALLBACK_VALUE = "entry.error.fallback.value"; + enum LogicalType { DATE("date"), diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/schema/Schema.java b/component-api/src/main/java/org/talend/sdk/component/api/service/schema/Schema.java index 355e321ebedce..5192dc9f5c4c5 100644 --- a/component-api/src/main/java/org/talend/sdk/component/api/service/schema/Schema.java +++ b/component-api/src/main/java/org/talend/sdk/component/api/service/schema/Schema.java @@ -129,6 +129,11 @@ public boolean isNullable() { return true; } + @Override + public boolean isErrorCapable() { + return false; + } + @Override public boolean isMetadata() { return false; @@ -163,6 +168,12 @@ public String getProp(final String property) { public Builder toBuilder() { throw new UnsupportedOperationException("#toBuilder()"); } + + @Override + public boolean isValid() { + return true; + } + } } diff --git a/component-api/src/test/java/org/talend/sdk/component/api/record/RecordTest.java b/component-api/src/test/java/org/talend/sdk/component/api/record/RecordTest.java index c734c2bc50a55..63a9852487f4b 100644 --- a/component-api/src/test/java/org/talend/sdk/component/api/record/RecordTest.java +++ b/component-api/src/test/java/org/talend/sdk/component/api/record/RecordTest.java @@ -106,6 +106,11 @@ public boolean isNullable() { return false; } + @Override + public boolean isErrorCapable() { + return false; + } + @Override public boolean isMetadata() { return false; @@ -135,6 +140,11 @@ public Map getProps() { public String getProp(String property) { return null; } + + @Override + public boolean isValid() { + return true; + } }; Assertions.assertEquals("value", record.get(String.class, e1)); } diff --git a/component-api/src/test/java/org/talend/sdk/component/api/record/SchemaTest.java b/component-api/src/test/java/org/talend/sdk/component/api/record/SchemaTest.java index 0157dabdff610..d1b8e6bb8094b 100644 --- a/component-api/src/test/java/org/talend/sdk/component/api/record/SchemaTest.java +++ b/component-api/src/test/java/org/talend/sdk/component/api/record/SchemaTest.java @@ -291,6 +291,11 @@ public boolean isNullable() { throw new UnsupportedOperationException("#isNullable()"); } + @Override + public boolean isErrorCapable() { + throw new UnsupportedOperationException("#isErrorCapable()"); + } + @Override public boolean isMetadata() { throw new UnsupportedOperationException("#isMetadata()"); @@ -326,6 +331,11 @@ public JsonValue getJsonProp(final String name) { return Entry.super.getJsonProp(name); } + @Override + public boolean isValid() { + return true; + } + } class SchemaBuilder implements Schema.Builder { diff --git a/component-api/src/test/java/org/talend/sdk/component/api/service/record/RecordBuilderFactoryTest.java b/component-api/src/test/java/org/talend/sdk/component/api/service/record/RecordBuilderFactoryTest.java index 006ffddb6d3d2..ff2c4fba06b14 100644 --- a/component-api/src/test/java/org/talend/sdk/component/api/service/record/RecordBuilderFactoryTest.java +++ b/component-api/src/test/java/org/talend/sdk/component/api/service/record/RecordBuilderFactoryTest.java @@ -78,6 +78,8 @@ static class MockEntry implements Schema.Entry { private final boolean nullable; + private final boolean errorCapable; + private final boolean metadata; private final Object defaultVal; @@ -102,6 +104,11 @@ public String getProp(final String property) { public Builder toBuilder() { throw new UnsupportedOperationException("#toBuilder()"); } + + @Override + public boolean isValid() { + return true; + } } @RequiredArgsConstructor @@ -135,6 +142,12 @@ public Entry.Builder withNullable(boolean nullable) { return this; } + @Override + public Entry.Builder withErrorCapable(boolean errorCapable) { + this.builder.withErrorCapable(errorCapable); + return this; + } + @Override public Entry.Builder withMetadata(boolean metadata) { this.builder.withMetadata(metadata); diff --git a/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java b/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java index 07fb3da564bf3..4a7078b7b0d91 100644 --- a/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java +++ b/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/AvroRecordBuilderTest.java @@ -209,6 +209,53 @@ void mixedRecordTest() { Assertions.assertNotNull(arrayType); } + @Test + void testWithError() { + final String val = System.getProperty(Record.RECORD_ERROR_SUPPORT); + System.setProperty(Record.RECORD_ERROR_SUPPORT, "true"); + final String val2 = System.getProperty(Record.RECORD_ERROR_SUPPORT); + + org.talend.sdk.component.api.record.Schema.Builder schemaBuilder = factory.newSchemaBuilder(Schema.Type.RECORD); + Schema.Entry nameEntry = factory + .newEntryBuilder() + .withName("name") + .withNullable(false) + .withType(Schema.Type.STRING) + .build(); + Schema.Entry nmEntry = factory + .newEntryBuilder() + .withName("normal") + .withNullable(true) + .withType(Schema.Type.STRING) + .build(); + Schema.Entry ageEntry = factory + .newEntryBuilder() + .withName("age") + .withNullable(false) + .withType(Schema.Type.INT) + .build(); + Schema customerSchema = schemaBuilder.withEntry(nameEntry).withEntry(nmEntry).withEntry(ageEntry).build(); + // record 1 + Record.Builder recordBuilder = factory.newRecordBuilder(customerSchema); + Record record1 = recordBuilder.with(nameEntry, null) + .with(nmEntry, "normal") + .with(ageEntry, "is not an int") + .build(); + assertFalse(record1.isValid()); + + final Schema.Entry entry = + record1.getSchema().getEntries().stream().filter(e -> "name".equals(e.getName())).findAny().get(); + assertNotNull(entry); + Assertions.assertFalse(entry.isValid()); + + final Schema.Entry entry2 = + record1.getSchema().getEntries().stream().filter(e -> "age".equals(e.getName())).findAny().get(); + assertNotNull(entry2); + Assertions.assertFalse(entry2.isValid()); + + System.setProperty(Record.RECORD_ERROR_SUPPORT, "false"); + } + @Test void recordWithNewSchema() { final Schema schema0 = new AvroSchemaBuilder()// diff --git a/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/JsonSchemaSerializationTest.java b/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/JsonSchemaSerializationTest.java index 607083f593ef2..1993f10373c98 100644 --- a/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/JsonSchemaSerializationTest.java +++ b/component-runtime-beam/src/test/java/org/talend/sdk/component/runtime/beam/spi/record/JsonSchemaSerializationTest.java @@ -49,7 +49,7 @@ void toJson() throws Exception { .create(new JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) { final String json = jsonb.toJson(schema); assertEquals( - "{\"entries\":[{\"elementSchema\":{\"entries\":[],\"metadata\":[],\"props\":{},\"type\":\"STRING\"},\"metadata\":false,\"name\":\"array\",\"nullable\":true,\"props\":{\"talend.component.label\":\"array\"},\"rawName\":\"array\",\"type\":\"ARRAY\"}],\"metadata\":[],\"props\":{\"talend.fields.order\":\"array\"},\"type\":\"RECORD\"}", + "{\"entries\":[{\"elementSchema\":{\"entries\":[],\"metadata\":[],\"props\":{},\"type\":\"STRING\"},\"metadata\":false,\"name\":\"array\",\"nullable\":true,\"props\":{\"talend.component.label\":\"array\"},\"rawName\":\"array\",\"type\":\"ARRAY\",\"valid\":true}],\"metadata\":[],\"props\":{\"talend.fields.order\":\"array\"},\"type\":\"RECORD\"}", json); } } @@ -71,7 +71,7 @@ void toJsonWithMeta() throws Exception { try (final Jsonb jsonb = JsonbBuilder .create(new JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) { assertEquals( - "{\"entries\":[],\"metadata\":[{\"elementSchema\":{\"entries\":[],\"metadata\":[],\"props\":{},\"type\":\"STRING\"},\"metadata\":true,\"name\":\"array\",\"nullable\":true,\"props\":{\"talend.component.label\":\"array\"},\"rawName\":\"array\",\"type\":\"ARRAY\"}],\"props\":{\"talend.fields.order\":\"array\"},\"type\":\"RECORD\"}", + "{\"entries\":[],\"metadata\":[{\"elementSchema\":{\"entries\":[],\"metadata\":[],\"props\":{},\"type\":\"STRING\"},\"metadata\":true,\"name\":\"array\",\"nullable\":true,\"props\":{\"talend.component.label\":\"array\"},\"rawName\":\"array\",\"type\":\"ARRAY\",\"valid\":true}],\"props\":{\"talend.fields.order\":\"array\"},\"type\":\"RECORD\"}", jsonb.toJson(schema)); } diff --git a/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/RecordImpl.java b/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/RecordImpl.java index 47c77ce50c529..1bc5d45484878 100644 --- a/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/RecordImpl.java +++ b/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/RecordImpl.java @@ -61,6 +61,7 @@ import org.talend.sdk.component.api.record.Schema; import org.talend.sdk.component.api.record.Schema.EntriesOrder; import org.talend.sdk.component.api.record.Schema.Entry; +import org.talend.sdk.component.api.record.SchemaProperty; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -129,6 +130,8 @@ public static class BuilderImpl implements Builder { private OrderState orderState; + private final Map entriesInError = new HashMap<>(); + public BuilderImpl() { this(null); } @@ -150,7 +153,7 @@ private void initOrderState() { } else { final List fields = this.providedSchema.naturalOrder() .getFieldsOrder() - .map(this.providedSchema::getEntry) + .map(n -> this.getEntryWithErrorIfAny(this.providedSchema.getEntry(n))) .collect(Collectors.toList()); this.orderState = new OrderState(fields); } @@ -171,11 +174,16 @@ public Object getValue(final String name) { @Override public Builder with(final Entry entry, final Object value) { - validateTypeAgainstProvidedSchema(entry.getName(), entry.getType(), value); + try { + validateTypeAgainstProvidedSchema(entry.getName(), entry.getType(), value); + } catch (Exception e) { + return withError(entry, value, e.getMessage()); + } if (!entry.getType().isCompatible(value)) { - throw new IllegalArgumentException(String - .format("Entry '%s' of type %s is not compatible with value of type '%s'", entry.getName(), - entry.getType(), value.getClass().getName())); + return withError(entry, value, String + .format("Entry '%s' of type %s is not compatible with given value of type '%s': '%s'.", + entry.getName(), + entry.getType(), value.getClass().getName(), value)); } if (entry.getType() == Schema.Type.DATETIME) { @@ -323,25 +331,53 @@ private Schema.Entry validateTypeAgainstProvidedSchema(final String name, final return entry; } + /** + * This method return the updated entry with error information if any. + * + * @param e The entry to check. + * @return The entry updated with error information or the given one. + */ + private Entry getEntryWithErrorIfAny(final Entry e) { + if (!e.isErrorCapable()) { + // The entry doesn't support error management + return e; + } + + return entriesInError.getOrDefault(e.getOriginalFieldName(), e); + } + public Record build() { final Schema currentSchema; if (this.providedSchema != null) { final String missing = this.providedSchema .getAllEntries() + .map(this::getEntryWithErrorIfAny) .filter(it -> !it.isNullable() && !values.containsKey(it.getName())) .map(Schema.Entry::getName) .collect(joining(", ")); if (!missing.isEmpty()) { throw new IllegalArgumentException("Missing entries: " + missing); } + + Schema schemaWithErrors = this.providedSchema; + if (!this.entriesInError.isEmpty()) { + Schema.Builder schemaBuilder = new SchemaImpl.BuilderImpl() + .withType(this.providedSchema.getType()); + this.providedSchema.getEntries() + .stream() + .map(this::getEntryWithErrorIfAny) + .forEach(schemaBuilder::withEntry); + schemaWithErrors = schemaBuilder.build(); + } + if (orderState != null && orderState.isOverride()) { - currentSchema = this.providedSchema.toBuilder().build(this.orderState.buildComparator()); + currentSchema = schemaWithErrors.toBuilder().build(this.orderState.buildComparator()); } else { - currentSchema = this.providedSchema; + currentSchema = schemaWithErrors; } } else { final Schema.Builder builder = new SchemaImpl.BuilderImpl().withType(RECORD); - this.entries.forEachValue(builder::withEntry); + this.entries.streams().map(this::getEntryWithErrorIfAny).forEach(builder::withEntry); initOrderState(); currentSchema = builder.build(orderState.buildComparator()); } @@ -513,6 +549,25 @@ public Builder withArray(final Schema.Entry entry, final Collection value return append(entry, values); } + private Builder withError(final Entry entry, final Object value, final String errorMessage) { + final boolean supportError = Boolean.parseBoolean(System.getProperty(RECORD_ERROR_SUPPORT, "false")); + if (!supportError || !entry.isErrorCapable()) { + throw new IllegalArgumentException(errorMessage); + } else { + // duplicate the schema instance with a modified Entry + final Entry updatedEntry = entry.toBuilder() + .withName(entry.getName()) + .withNullable(true) + .withType(entry.getType()) + .withProp(SchemaProperty.ENTRY_IS_ON_ERROR, "true") + .withProp(SchemaProperty.ENTRY_ERROR_MESSAGE, errorMessage) + .withProp(SchemaProperty.ENTRY_ERROR_FALLBACK_VALUE, String.valueOf(value)) + .build(); + this.entriesInError.put(updatedEntry.getOriginalFieldName(), updatedEntry); + return this; + } + } + private void assertType(final Schema.Type actual, final Schema.Type expected) { if (actual != expected) { throw new IllegalArgumentException("Expected entry type: " + expected + ", got: " + actual); diff --git a/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/SchemaImpl.java b/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/SchemaImpl.java index 035aec97f83aa..41886e2ee38b2 100644 --- a/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/SchemaImpl.java +++ b/component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/record/SchemaImpl.java @@ -352,6 +352,7 @@ private EntryImpl(final EntryImpl.BuilderImpl builder) { this.type = builder.type; } this.nullable = builder.nullable; + this.errorCapable = builder.errorCapable; this.metadata = builder.metadata; this.defaultValue = builder.defaultValue; this.elementSchema = builder.elementSchema; @@ -379,6 +380,11 @@ private EntryImpl(final EntryImpl.BuilderImpl builder) { */ private final boolean nullable; + /** + * Is this entry can be in error. + */ + private final boolean errorCapable; + /** * Is this entry a metadata entry. */ @@ -440,6 +446,11 @@ public boolean isNullable() { return this.nullable; } + @Override + public boolean isErrorCapable() { + return this.errorCapable; + } + @Override public boolean isMetadata() { return this.metadata; @@ -465,6 +476,15 @@ public Map getProps() { return this.props; } + @Override + public boolean isValid() { + String property = this.getProp(SchemaProperty.ENTRY_IS_ON_ERROR); + if (property == null) { + return true; + } + return !Boolean.parseBoolean(property); + } + /** * Plain builder matching {@link Entry} structure. */ @@ -478,6 +498,8 @@ public static class BuilderImpl implements Entry.Builder { private boolean nullable; + private boolean errorCapable; + private boolean metadata = false; private Object defaultValue; @@ -497,6 +519,7 @@ private BuilderImpl(final Entry entry) { this.name = entry.getName(); this.rawName = entry.getRawName(); this.nullable = entry.isNullable(); + this.errorCapable = entry.isErrorCapable(); this.type = entry.getType(); this.comment = entry.getComment(); this.elementSchema = entry.getElementSchema(); @@ -540,6 +563,12 @@ public Builder withNullable(final boolean nullable) { return this; } + @Override + public Builder withErrorCapable(final boolean errorCapable) { + this.errorCapable = errorCapable; + return this; + } + @Override public Builder withMetadata(final boolean metadata) { this.metadata = metadata; diff --git a/component-runtime-impl/src/test/java/org/talend/sdk/component/runtime/record/RecordBuilderImplTest.java b/component-runtime-impl/src/test/java/org/talend/sdk/component/runtime/record/RecordBuilderImplTest.java index dec6e7ceb3260..85e233722a835 100644 --- a/component-runtime-impl/src/test/java/org/talend/sdk/component/runtime/record/RecordBuilderImplTest.java +++ b/component-runtime-impl/src/test/java/org/talend/sdk/component/runtime/record/RecordBuilderImplTest.java @@ -17,6 +17,7 @@ import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -248,6 +249,98 @@ void dateTime() { assertThrows(IllegalArgumentException.class, () -> builder2.withDateTime("date", (ZonedDateTime) null)); } + @Test + void withErrorWhenNotSupported() { + Assertions.assertThrows(IllegalArgumentException.class, () -> withError("false")); + } + + @Test + void withError() { + Record record = withError("true"); + + assertFalse(record.isValid()); + + final Entry retrievedDateEntry = record.getSchema().getEntry("date"); + assertNotNull(retrievedDateEntry); + Assertions.assertFalse(retrievedDateEntry.isValid()); + assertEquals( + "Entry 'date' of type DATETIME is not compatible with given value of type 'java.lang.String': 'not a date'.", + retrievedDateEntry.getErrorMessage()); + Assertions.assertNull(record.getDateTime("date")); + + final Entry retrievedIntEntry = record.getSchema().getEntry("intValue"); + assertNotNull(retrievedIntEntry); + Assertions.assertFalse(retrievedIntEntry.isValid()); + assertEquals( + "Entry 'intValue' of type INT is not compatible with given value of type 'java.lang.String': 'wrong int value'.", + retrievedIntEntry.getErrorMessage()); + Assertions.assertNull(record.getDateTime("intValue")); + + final Entry retrievedStringEntry = record.getSchema().getEntry("normal"); + assertNotNull(retrievedStringEntry); + Assertions.assertTrue(retrievedStringEntry.isValid()); + Assertions.assertEquals("No error", record.getString("normal")); + + } + + private Record withError(final String supported) { + final String errorSupportBackup = System.getProperty(Record.RECORD_ERROR_SUPPORT); + System.setProperty(Record.RECORD_ERROR_SUPPORT, supported); + + Entry dateEntry = new EntryImpl.BuilderImpl() + .withName("date") + .withNullable(false) + .withErrorCapable(true) + .withType(Type.DATETIME) + .build(); + Entry stringEntry = new EntryImpl.BuilderImpl() + .withName("normal") + .withNullable(true) + .withErrorCapable(true) + .withType(Type.STRING) + .build(); + Entry intEntry = new EntryImpl.BuilderImpl() + .withName("intValue") + .withNullable(false) + .withErrorCapable(true) + .withType(Type.INT) + .build(); + final Schema schema = new SchemaImpl.BuilderImpl() + .withType(Schema.Type.RECORD) + .withEntry(dateEntry) + .withEntry(stringEntry) + .withEntry(intEntry) + .build(); + + final RecordImpl.BuilderImpl builder = new RecordImpl.BuilderImpl(schema); + + builder.with(stringEntry, "No error"); + builder.with(dateEntry, "not a date"); + builder.with(intEntry, "wrong int value"); + final Record record = builder.build(); + + System.setProperty(Record.RECORD_ERROR_SUPPORT, errorSupportBackup == null ? "false" : errorSupportBackup); + + return record; + } + + @Test + void testWithWrongEntryType() { + Entry entry = new SchemaImpl.EntryImpl.BuilderImpl() + .withName("date") + .withNullable(false) + .withType(Schema.Type.DATETIME) + .build(); + final Schema schema = new SchemaImpl.BuilderImpl() + .withType(Schema.Type.RECORD) + .withEntry(entry) + .build(); + final RecordImpl.BuilderImpl builder = new RecordImpl.BuilderImpl(schema); + assertNotNull(builder.getEntry("date")); + + assertThrows(IllegalArgumentException.class, () -> builder.with(entry, "String")); + } + @Test void zonedDateTimeVSInstant() { final Schema schema = new SchemaImpl.BuilderImpl() diff --git a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/ActionResourceImplTest.java b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/ActionResourceImplTest.java index e357820eecc57..85bb91a1e62c7 100644 --- a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/ActionResourceImplTest.java +++ b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/ActionResourceImplTest.java @@ -199,7 +199,8 @@ void checkSchemaSerialization() { " \"metadata\":[\n ],\n \"props\":{\n\n },\n \"type\":\"STRING\"\n" + " },\n \"metadata\":false,\n \"name\":\"array\",\n \"nullable\":false,\n" + - " \"props\":{\n\n },\n \"type\":\"ARRAY\"\n }\n ],\n \"metadata\":[\n" + + " \"props\":{\n\n },\n \"type\":\"ARRAY\",\n" + + " \"valid\":true\n }\n ],\n \"metadata\":[\n" + " ],\n \"props\":{\n \"talend.fields.order\":\"array\"\n },\n \"type\":\"RECORD\"\n}"; assertEquals(expected, schema); } @@ -238,7 +239,7 @@ void checkDiscoverProcessorSchema() { }, APPLICATION_JSON_TYPE), JsonObject.class); assertNotNull(guessed); final String expected = - "{\"entries\":[{\"metadata\":false,\"name\":\"field1\",\"nullable\":false,\"props\":{},\"type\":\"STRING\"},{\"comment\":\"field2 comment\",\"metadata\":false,\"name\":\"field2\",\"nullable\":false,\"props\":{},\"type\":\"LONG\"},{\"metadata\":false,\"name\":\"V1\",\"nullable\":false,\"props\":{},\"type\":\"STRING\"},{\"metadata\":false,\"name\":\"driver\",\"nullable\":false,\"props\":{},\"type\":\"STRING\"}],\"metadata\":[],\"props\":{\"talend.fields.order\":\"field1,field2,V1,driver\"},\"type\":\"RECORD\"}"; + "{\"entries\":[{\"metadata\":false,\"name\":\"field1\",\"nullable\":false,\"props\":{},\"type\":\"STRING\",\"valid\":true},{\"comment\":\"field2 comment\",\"metadata\":false,\"name\":\"field2\",\"nullable\":false,\"props\":{},\"type\":\"LONG\",\"valid\":true},{\"metadata\":false,\"name\":\"V1\",\"nullable\":false,\"props\":{},\"type\":\"STRING\",\"valid\":true},{\"metadata\":false,\"name\":\"driver\",\"nullable\":false,\"props\":{},\"type\":\"STRING\",\"valid\":true}],\"metadata\":[],\"props\":{\"talend.fields.order\":\"field1,field2,V1,driver\"},\"type\":\"RECORD\"}"; assertEquals(expected, guessed.toString()); } diff --git a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/beam/BeamActionSerializationTest.java b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/beam/BeamActionSerializationTest.java index 553bfe64c4870..e30e2286e2d3c 100644 --- a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/beam/BeamActionSerializationTest.java +++ b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/beam/BeamActionSerializationTest.java @@ -46,7 +46,8 @@ void checkSchemaSerialization() { + " \"entries\":[\n" + " ],\n" + " \"metadata\":[\n" + " ],\n" + " \"props\":{\n" + "\n" + " },\n" + " \"type\":\"STRING\"\n" + " },\n" + " \"metadata\":false,\n" + " \"name\":\"array\",\n" + " \"nullable\":false,\n" - + " \"props\":{\n" + "\n" + " },\n" + " \"type\":\"ARRAY\"\n" + " }\n" + " ],\n" + + " \"props\":{\n" + "\n" + " },\n" + " \"type\":\"ARRAY\",\n" + + " \"valid\":true\n }\n" + " ],\n" + " \"metadata\":[\n" + " ],\n" + " \"props\":{\n" + " \"talend.fields.order\":\"array\"\n" + " },\n" + " \"type\":\"RECORD\"\n" + "}"; assertEquals(attended, schema); diff --git a/sample-parent/sample-features/entry-with-error/README.md b/sample-parent/sample-features/entry-with-error/README.md new file mode 100644 index 0000000000000..a37bef27b3eeb --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/README.md @@ -0,0 +1,89 @@ +# Component Runtime :: Sample Feature :: Entry with error + +## Table of Contents +- [Overview](#overview) +- [Usage](#usage) + - [How to build the sample connector plugin](#how-to-build-the-sample-connector-plugin) + - [How to run](#how-to-run) + - [How to use](#how-to-use) +- [Plugin artifact](#plugin-artifact) +- [Execution examples](#execution-examples) + - [Run supporterror connector with default behavior](#run-checkpoint-connector-with-default-behavior) + - [Run with framework feature disabled](#run-with-framework-feature-disabled) + + +## Overview +This is a simple TCK connector plugin to test and validate the feature about Improve Record API to add error info for invalid entries. + +This project contains two things: + 1. A sample connector plugin that implements the supporterror feature. + 2. A simple Cli runner that can be used to run the connector plugin and test the supporterror feature. + + +## Usage +### How to build the sample connector plugin +Checkout the code from the repository and build the project using `mvn clean install` +Alternatively build the feature module using `mvn install -am -pl :supporterror` + +### How to run +To run the connector, you need exec the generated artifact `org.talend.sdk.component.sample.feature:supporterror`. +* You can run it directly from `target` folder or repository folder + * `java -jar target/supporterror-1.81.0-SNAPSHOT.jar` +* or you can run it from the maven repository + * `java -jar ~/.m2/repository/org/talend/sdk/component/sample/feature/supporterror/1.81.0-SNAPSHOT/supporterror-1.81.0-SNAPSHOT.jar` + +For later usage, will use the variable `$RUNCMD` as the way you may choose. +⚠️ If you're using jdk17, don't forget to add the `--add-opens` option to the command line +(see profile _jdk9_ in master pom at the repository's root) or use instead jdk11. + +### How to use +Run supporterror with the option "-s" for execution: + +```bash +$ $RUNCMD supporterror + +Usage: supporterror [-s] + +Options: + --s use supporterror or not. + +``` + +## Plugin artifact +There are two ways to run the supporterror runner with a specific plugin artifact: +- Using the `--gav` option to specify the GAV of the plugin artifact. + Syntax: `groupId:artifactId:version[:packaging[:classifier]]` + +- Using the `--jar` option to specify the path to the plugin artifact. + Syntax: `/path/to/plugin.jar` + +⚠️ You cannot use both options at the same time. + +## Execution examples +### Run supporterror connector with default behavior +`java -jar target/supporterror-1.81.0-SNAPSHOT.jar supporterror -s` + +```bash +[INFO] Manager is using plugin: supporterror from GAV org.talend.sdk.component.sample.feature:supporterror:jar:1.81.0-SNAPSHOT. +[INFO] support true +[INFO] create input now. +[INFO] getting the record. +[INFO] Record isValid = false +[INFO] Record 'name': example connector +[INFO] ERROR: date is null +[INFO] ERROR: wrong int value +[INFO] finished. +``` + +### Run with framework feature disabled +In this example we turn off the supporterror capability. +`% java -jar target/supporterror-1.81.0-SNAPSHOT.jar supporterror` + +```bash +[INFO] Manager is using plugin: supporterror from GAV org.talend.sdk.component.sample.feature:supporterror:jar:1.81.0-SNAPSHOT. +[INFO] support false +[INFO] create input now. +[INFO] getting the record. +[ERROR] date is null +``` +We can see that no record is returned because throw errors. diff --git a/sample-parent/sample-features/entry-with-error/pom.xml b/sample-parent/sample-features/entry-with-error/pom.xml new file mode 100644 index 0000000000000..b136e3b977d73 --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/pom.xml @@ -0,0 +1,142 @@ + + + + 4.0.0 + + + org.talend.sdk.component + sample-features + 1.81.0-SNAPSHOT + + + org.talend.sdk.component.sample.feature + supporterror + jar + + Component Runtime :: Sample Feature :: Entry with error support + + + + org.talend.sdk.component + component-api + ${project.version} + + + org.talend.sdk.component + component-runtime-manager + ${project.version} + + + org.tomitribe + tomitribe-crest + 0.32 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.talend.sdk.component + talend-component-maven-plugin + ${project.version} + + + talend-component-validate + + validate + + process-classes + + true + true + false + true + true + true + true + true + true + false + + false + + true + true + true + true + true + true + true + true + true + true + true + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + org.talend.sdk.component.sample.feature.entrywitherror.Cli + + + + + + maven-shade-plugin + 2.1 + + + + shade + + package + + + + org.talend.sdk.component.sample.feature.entrywitherror.Cli + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + org.talend.sdk.component.sample.feature.entrywitherror.Cli + + + + + diff --git a/sample-parent/sample-features/entry-with-error/src/main/java-templates/org/talend/sdk/component/sample/feature/entrywitherror/Versions.java b/sample-parent/sample-features/entry-with-error/src/main/java-templates/org/talend/sdk/component/sample/feature/entrywitherror/Versions.java new file mode 100644 index 0000000000000..fef316911253f --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/java-templates/org/talend/sdk/component/sample/feature/entrywitherror/Versions.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2006-2025 Talend Inc. - www.talend.com + * + * 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 org.talend.sdk.component.sample.feature.entrywitherror; + +public interface Versions { + + String KIT_VERSION = "${project.version}"; + +} diff --git a/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/Cli.java b/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/Cli.java new file mode 100644 index 0000000000000..9570ec019fc37 --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/Cli.java @@ -0,0 +1,214 @@ +/** + * Copyright (C) 2006-2025 Talend Inc. - www.talend.com + * + * 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 org.talend.sdk.component.sample.feature.entrywitherror; + +import static java.util.Optional.ofNullable; +import static lombok.AccessLevel.PRIVATE; +import static org.talend.sdk.component.runtime.manager.ComponentManager.findM2; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.talend.sdk.component.api.record.Record; +import org.talend.sdk.component.api.record.Schema.Entry; +import org.talend.sdk.component.api.service.record.RecordBuilderFactory; +import org.talend.sdk.component.dependencies.maven.Artifact; +import org.talend.sdk.component.dependencies.maven.MvnCoordinateToFileConverter; +import org.talend.sdk.component.runtime.input.InputImpl; +import org.talend.sdk.component.runtime.input.Mapper; +import org.talend.sdk.component.runtime.manager.ComponentManager; +import org.talend.sdk.component.runtime.record.RecordBuilderFactoryImpl; +import org.talend.sdk.component.runtime.serialization.ContainerFinder; +import org.talend.sdk.component.runtime.serialization.LightContainer; +import org.tomitribe.crest.Main; +import org.tomitribe.crest.api.Command; +import org.tomitribe.crest.api.Default; +import org.tomitribe.crest.api.Option; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = PRIVATE) +public final class Cli { + + static final String GAV = "org.talend.sdk.component.sample.feature:supporterror:jar:" + + Versions.KIT_VERSION; + + @Command("supporterror") + public static void runInput( + @Option("gav") @Default(GAV) final String gav, + @Option("support") @Default("false") final boolean support, + @Option("gen-some-errors") @Default("true") final boolean genErrors, + @Option("gen-nbrecords") @Default("10") final int nbRecords, + @Option("jar") final File jar, + @Option("family") @Default("supporterror") final String family, + @Option("mapper") @Default("SupportErrorMapper") final String mapper) { + + info("support " + support); + if (support) { + setSupportError(support); + } + + Map config = new HashMap<>(); + config.put("configuration.generateErrors", String.valueOf(genErrors)); + config.put("configuration.nbRecords", String.valueOf(nbRecords)); + run(jar, gav, config, "sampleRecordWithEntriesInError", "RecordWithEntriesInErrorEmitter"); + } + + private static void run(final File jar, final String gav, final Map configuration, + final String family, final String mapper) { + try (final ComponentManager manager = manager(jar, gav)) { + info("configuration: " + configuration); + + // create the mapper + final Mapper mpr = manager.findMapper(family, mapper, 1, configuration) + .orElseThrow(() -> new IllegalStateException( + String.format("No mapper found for: %s/%s.", family, manager))); + + List mappers = mpr.split(1); + Record data; + + int count = 0; + for (Mapper currentMapper : mappers) { + final InputImpl input = InputImpl.class.cast(currentMapper.create()); + input.start(); + while ((data = (Record) input.next()) != null) { + count++; + recordOut(count, data); + } + input.stop(); + } + info("finished."); + } catch (Exception e) { + error(e); + } + } + + private static void recordOut(final int count, final Record record) { + System.out.printf("Record no %s is valid ? %s\n", count, record.isValid() ? "yes" : "no"); + System.out.printf("\tName: %s\n", record.getString("name")); + Entry date = record.getSchema().getEntry("date"); + if (date.isValid()) { + System.out.printf("\tDate: %s\n", record.getDateTime("date")); + } else { + System.out.printf("\tDate is on error: \n\t\tMessage:%s\n\t\tFallback value: %s\n", + date.getErrorMessage(), date.getErrorFallbackValue()); + } + + Entry age = record.getSchema().getEntry("age"); + if (age.isValid()) { + System.out.printf("\tAge: %s\n", record.getInt("age")); + } else { + System.out.printf("\tAge is on error: \n\t\tMessage:%s\n\t\tFallback value: %s\n", + age.getErrorMessage(), age.getErrorFallbackValue()); + } + } + + // set support or not. + public static void setSupportError(final boolean supportError) { + System.setProperty(Record.RECORD_ERROR_SUPPORT, String.valueOf(supportError)); + } + + public static void main(final String[] args) throws Exception { + ofNullable(run(args)).ifPresent(System.out::println); + } + + public static Object run(final String[] args) throws Exception { + return new Main(Cli.class).exec(args); + } + + static final String ERROR = "[ERROR] "; + + static final String WARN = "[WARN] "; + + static final String INFO = "[INFO] "; + + static MvnCoordinateToFileConverter mvnCoordinateToFileConverter = new MvnCoordinateToFileConverter(); + + public static ComponentManager manager(final File jar, final String artifact) { + return new ComponentManager(findM2()) { + + final ContainerFinder containerFinder = ContainerFinder.Instance.get(); + + final ComponentManager originalMgr = contextualInstance().get(); + + { + contextualInstance().set(this); + String containerId; + if (jar != null) { + containerId = addPlugin(jar.getAbsolutePath()); + Cli.info(String.format("Manager is using plugin %s from %s.", containerId, jar)); + } else { + final String pluginPath = ofNullable(artifact) + .map(gav -> mvnCoordinateToFileConverter.toArtifact(gav)) + .map(Artifact::toPath) + .orElseThrow(() -> new IllegalArgumentException("Plugin GAV can't be empty")); + String p = findM2().resolve(pluginPath).toAbsolutePath().toString(); + containerId = addPlugin(p); + Cli.info(String.format("Manager is using plugin: %s from GAV %s.", containerId, artifact)); + } + DynamicContainerFinder.SERVICES.put(RecordBuilderFactory.class, + new RecordBuilderFactoryImpl(containerId)); + } + + @Override + public void close() { + DynamicContainerFinder.SERVICES.clear(); + super.close(); + contextualInstance().set(originalMgr); + } + }; + } + + public static class DynamicContainerFinder implements ContainerFinder { + + static final Map LOADERS = new ConcurrentHashMap<>(); + + static final Map, Object> SERVICES = new ConcurrentHashMap<>(); + + @Override + public LightContainer find(final String plugin) { + return new LightContainer() { + + @Override + public ClassLoader classloader() { + return LOADERS.get(plugin); + } + + @Override + public T findService(final Class key) { + return key.cast(SERVICES.get(key)); + } + }; + } + } + + public static void info(final String message) { + System.out.println(INFO + message); + } + + public static void warn(final String message) { + System.err.println(WARN + message); + } + + public static void error(final Throwable e) { + System.err.println(ERROR + e.getMessage()); + System.exit(501); + } + +} diff --git a/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/RecordWithEntriesInErrorEmitter.java b/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/RecordWithEntriesInErrorEmitter.java new file mode 100644 index 0000000000000..576732e75298f --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/RecordWithEntriesInErrorEmitter.java @@ -0,0 +1,150 @@ +/** + * Copyright (C) 2006-2025 Talend Inc. - www.talend.com + * + * 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 org.talend.sdk.component.sample.feature.entrywitherror; + +import java.io.Serializable; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.function.Function; + +import javax.annotation.PostConstruct; + +import org.talend.sdk.component.api.component.Icon; +import org.talend.sdk.component.api.component.Version; +import org.talend.sdk.component.api.configuration.Option; +import org.talend.sdk.component.api.configuration.ui.layout.GridLayout; +import org.talend.sdk.component.api.input.Emitter; +import org.talend.sdk.component.api.input.Producer; +import org.talend.sdk.component.api.meta.Documentation; +import org.talend.sdk.component.api.record.Record; +import org.talend.sdk.component.api.record.Record.Builder; +import org.talend.sdk.component.api.record.Schema; +import org.talend.sdk.component.api.record.Schema.Entry; +import org.talend.sdk.component.api.service.record.RecordBuilderFactory; +import org.talend.sdk.component.runtime.record.SchemaImpl; + +import lombok.Data; + +@Version +@Icon(value = Icon.IconType.CUSTOM, custom = "mapper") +@Emitter(name = "RecordWithEntriesInErrorEmitter") +@Documentation("Generated record with entries in error.") +public class RecordWithEntriesInErrorEmitter implements Serializable { + + private final RecordBuilderFactory recordBuilderFactory; + + private final Config config; + + private transient Schema recordSchema; + + private transient Function createRecordFunction; + + private transient int index; + + public RecordWithEntriesInErrorEmitter( + final RecordBuilderFactory recordBuilderFactory, + final @Option("configuration") Config config) { + this.recordBuilderFactory = recordBuilderFactory; + this.config = config; + } + + @PostConstruct + public void init() { + recordSchema = new SchemaImpl.BuilderImpl() + .withType(Schema.Type.RECORD) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("name") + .withNullable(false) + .withErrorCapable(true) + .withType(Schema.Type.STRING) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("date") + .withNullable(false) + .withErrorCapable(true) + .withType(Schema.Type.DATETIME) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("age") + .withNullable(false) + .withErrorCapable(true) + .withType(Schema.Type.INT) + .build()) + .build(); + + createRecordFunction = i -> { + Builder builder = recordBuilderFactory.newRecordBuilder(recordSchema).withString("name", "name " + i); + + // Generate error only on odd generated records + boolean generateErrors = config.isGenerateErrors() && i % 2 == 0; + + if (generateErrors) { + Entry dateEntry = recordSchema.getEntry("date"); + builder.with(dateEntry, "789-555"); + } else { + ZonedDateTime dateTime = ZonedDateTime.of( + 2025, // Year + Month.APRIL.getValue(), // Month + 1 + i, // Day + 15, // Hours + 30, // Minutes + 0, // seconds + 0, // nanoseconds + ZoneId.of("UTC") // Timezone + ); + builder.withDateTime("date", dateTime); + } + + if (generateErrors) { + Entry ageEntry = recordSchema.getEntry("age"); + builder.with(ageEntry, "-78"); + } else { + builder.withInt("age", 50 + i); + } + + return builder.build(); + }; + } + + @Producer + public Record data() { + index++; + if (index <= config.getNbRecords()) { + return createRecordFunction.apply(index); + } + + return null; + } + + @Data + @GridLayout(value = { + @GridLayout.Row("generateErrors"), + @GridLayout.Row("nbRecords"), + }) + public static class Config implements Serializable { + + @Option + @Documentation("If true, generate some errors.") + private boolean generateErrors = true; + + @Option + @Documentation("Number of generated records.") + private int nbRecords = 5; + + } + +} diff --git a/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/package-info.java b/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/package-info.java new file mode 100644 index 0000000000000..0668f6bcaa05c --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/java/org/talend/sdk/component/sample/feature/entrywitherror/package-info.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2006-2025 Talend Inc. - www.talend.com + * + * 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. + */ +@Components(family = "sampleRecordWithEntriesInError", categories = "sample") +@Icon(value = Icon.IconType.CUSTOM, custom = "icon") +package org.talend.sdk.component.sample.feature.entrywitherror; + +import org.talend.sdk.component.api.component.Components; +import org.talend.sdk.component.api.component.Icon; diff --git a/sample-parent/sample-features/entry-with-error/src/main/resources/META-INF/services/org.talend.sdk.component.runtime.serialization.ContainerFinder b/sample-parent/sample-features/entry-with-error/src/main/resources/META-INF/services/org.talend.sdk.component.runtime.serialization.ContainerFinder new file mode 100644 index 0000000000000..0d23def70f392 --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/resources/META-INF/services/org.talend.sdk.component.runtime.serialization.ContainerFinder @@ -0,0 +1 @@ +org.talend.sdk.component.runtime.manager.finder.StandaloneContainerFinder diff --git a/sample-parent/sample-features/entry-with-error/src/main/resources/icons/dark/icon.svg b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/dark/icon.svg new file mode 100644 index 0000000000000..3351e2c0bc9e9 --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/dark/icon.svg @@ -0,0 +1,5 @@ + + + + CheckpointInput + \ No newline at end of file diff --git a/sample-parent/sample-features/entry-with-error/src/main/resources/icons/dark/mapper.svg b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/dark/mapper.svg new file mode 100644 index 0000000000000..4f0fafff9a769 --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/dark/mapper.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + diff --git a/sample-parent/sample-features/entry-with-error/src/main/resources/icons/light/icon.svg b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/light/icon.svg new file mode 100644 index 0000000000000..3351e2c0bc9e9 --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/light/icon.svg @@ -0,0 +1,5 @@ + + + + CheckpointInput + \ No newline at end of file diff --git a/sample-parent/sample-features/entry-with-error/src/main/resources/icons/light/mapper.svg b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/light/mapper.svg new file mode 100644 index 0000000000000..2958a0056838e --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/resources/icons/light/mapper.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + diff --git a/sample-parent/sample-features/entry-with-error/src/main/resources/org/talend/sdk/component/sample/feature/entrywitherror/Messages.properties b/sample-parent/sample-features/entry-with-error/src/main/resources/org/talend/sdk/component/sample/feature/entrywitherror/Messages.properties new file mode 100644 index 0000000000000..87398b474113c --- /dev/null +++ b/sample-parent/sample-features/entry-with-error/src/main/resources/org/talend/sdk/component/sample/feature/entrywitherror/Messages.properties @@ -0,0 +1,19 @@ +# Copyright (C) 2006-2025 Talend Inc. - www.talend.com +# +# 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. +# Here you can change all your configuration display names to use more explicit labels +# You can also translate your configuration by adding one file by local Messages_fr.properties for french for example +sampleRecordWithEntriesInError.RecordWithEntriesInErrorEmitter._displayName = Record with entries in error emitter +Config.generateErrors._displayName = Generate some errors +Config.nbRecords._displayName = Nb generated records +Config.nbRecords._placeholder = diff --git a/sample-parent/sample-features/pom.xml b/sample-parent/sample-features/pom.xml index 3214d3ab785d6..4b0d77c93bc41 100644 --- a/sample-parent/sample-features/pom.xml +++ b/sample-parent/sample-features/pom.xml @@ -32,6 +32,7 @@ aftergroup-lastgroup conditional-outputs checkpoint-runner + entry-with-error