From d1d4568dfaeac6d86969601d37d7aba770233913 Mon Sep 17 00:00:00 2001 From: yyin-talend Date: Wed, 24 Dec 2025 10:37:59 +0800 Subject: [PATCH 1/4] Add Json Schema --- .../server/front/model/JsonEntryModel.java | 152 ++++++++++++ .../server/front/model/JsonSchemaModel.java | 142 +++++++++++ .../component-server/pom.xml | 11 +- .../server/front/JsonSchemaTest.java | 234 ++++++++++++++++++ 4 files changed, 534 insertions(+), 5 deletions(-) create mode 100644 component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java create mode 100644 component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java create mode 100644 component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java new file mode 100644 index 0000000000000..711d87c44110a --- /dev/null +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java @@ -0,0 +1,152 @@ +package org.talend.sdk.component.server.front.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.json.JsonObject; +import javax.json.JsonValue; +import lombok.Getter; +import lombok.Setter; + +public class JsonEntryModel { + + private final JsonObject jsonEntry; + + @Getter + private final boolean isMetadata; + + /** + * The name of this entry. + */ + @Setter + @Getter + private String name; + + /** + * The raw name of this entry. + */ + @Setter + @Getter + private String rawName; + + /** + * Type of the entry, this determine which other fields are populated. + */ + @Setter + @Getter + private JsonSchemaModel.Type type; + + /** + * Is this entry nullable or always valued. + */ + @Setter + @Getter + private boolean nullable; + + /** + * Is this entry can be in error. + */ + private boolean errorCapable; + + /** + * Is this entry a metadata entry. + */ + @Setter + @Getter + private boolean metadata; + + /** + * Default value for this entry. + */ + @Setter + @Getter + private Object defaultValue; + + /** + * For type == record, the element type. + */ + @Getter + @Setter + private JsonSchemaModel elementSchema; + + /** + * Allows to associate to this field a comment - for doc purposes, no use in the runtime. + */ + @Setter + @Getter + private String comment; + + @Setter + @Getter + private boolean valid; + + /** + * metadata + */ + @Setter + @Getter + private Map props = new LinkedHashMap<>(0); + + JsonEntryModel(JsonObject jsonEntry, boolean isMetadata) { + this.jsonEntry = jsonEntry; + this.isMetadata = isMetadata; + this.type = JsonSchemaModel.Type.valueOf(jsonEntry.getString("type")); + this.props = parseProps(jsonEntry.getJsonObject("props")); + + // Parse element schema if present + this.elementSchema = jsonEntry.containsKey("elementSchema") + ? new JsonSchemaModel(jsonEntry.getJsonObject("elementSchema").toString()) + : null; + } + + private Map parseProps(JsonObject propsObj) { + if (propsObj == null) return Collections.emptyMap(); + + Map result = new HashMap<>(); + propsObj.forEach((key, value) -> + result.put(key, value.getValueType() == JsonValue.ValueType.STRING + ? ((javax.json.JsonString) value).getString() + : value.toString())); + return result; + } + + public String getName() { + return jsonEntry.getString("name"); + } + + public String getRawName() { + return jsonEntry.getString("rawName", getName()); + } + + public String getOriginalFieldName() { + return getRawName() != null ? getRawName() : getName(); + } + + public boolean isNullable() { + return jsonEntry.getBoolean("nullable", true); + } + + public boolean isErrorCapable() { + return jsonEntry.getBoolean("errorCapable", false); + } + + public boolean isValid() { + return jsonEntry.getBoolean("valid", true); + } + + public T getDefaultValue() { + return jsonEntry.containsKey("defaultValue") + ? (T) jsonEntry.get("defaultValue") + : null; + } + + public String getComment() { + return jsonEntry.getString("comment", null); + } + + public String getProp(String property) { + return props.get(property); + } + +} diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java new file mode 100644 index 0000000000000..5890ccec51425 --- /dev/null +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java @@ -0,0 +1,142 @@ +package org.talend.sdk.component.server.front.model; + +import java.io.StringReader; +import java.math.BigDecimal; +import java.time.temporal.Temporal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import java.util.stream.Collectors; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonValue; +import lombok.Getter; +import lombok.Setter; +//import org.json.JSONObject; + +public class JsonSchemaModel { + + @Getter + @Setter + private Type type; + + private final JsonObject jsonSchema; + + @Getter + private JsonSchemaModel elementSchema; + + @Getter + @Setter + private List entries = new ArrayList(); + + @Getter + private List metadataEntries = new ArrayList(); + ; + + @Getter + @Setter + private Map props = new HashMap(); + + @Getter + private Map entryMap = new HashMap<>(); + + public JsonSchemaModel(String jsonString) { + try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { + this.jsonSchema = reader.readObject(); + } + + this.type = Type.valueOf(jsonSchema.getString("type")); + this.props = parseProps(jsonSchema.getJsonObject("props")); + + // Parse entries + this.entries = parseEntries(jsonSchema.getJsonArray("entries"), false); + this.metadataEntries = parseEntries(jsonSchema.getJsonArray("metadata"), true); + + // Build entry map + this.entryMap = new HashMap<>(); + getEntries().forEach(e -> entryMap.put(e.getName(), e)); + + // Parse element schema for ARRAY types + this.elementSchema = jsonSchema.containsKey("elementSchema") + ? new JsonSchemaModel(jsonSchema.getJsonObject("elementSchema").toString()) + : null; + } + + private List parseEntries(JsonArray jsonArray, boolean isMetadata) { + if (jsonArray == null) return Collections.emptyList(); + + return jsonArray.stream() + .map(JsonValue::asJsonObject) + .map(obj -> new JsonEntryModel(obj, isMetadata)) + .collect(Collectors.toList()); + } + + private Map parseProps(JsonObject propsObj) { + if (propsObj == null) return Collections.emptyMap(); + + Map result = new HashMap<>(); + propsObj.forEach((key, value) -> + result.put(key, value.getValueType() == JsonValue.ValueType.STRING + ? ((javax.json.JsonString) value).getString() + : value.toString())); + return result; + } + + public void setEntryMap(Map entryMap) { + this.entryMap = entryMap; + } + + public void setElementSchema(JsonSchemaModel elementSchema) { + this.elementSchema = elementSchema; + } + + public enum Type { + + RECORD(new Class[] { Record.class }), + ARRAY(new Class[] { Collection.class }), + STRING(new Class[] { String.class, Object.class }), + BYTES(new Class[] { byte[].class, Byte[].class }), + INT(new Class[] { Integer.class }), + LONG(new Class[] { Long.class }), + FLOAT(new Class[] { Float.class }), + DOUBLE(new Class[] { Double.class }), + BOOLEAN(new Class[] { Boolean.class }), + DATETIME(new Class[] { Long.class, Date.class, Temporal.class }), + DECIMAL(new Class[] { BigDecimal.class }); + + /** + * All compatibles Java classes + */ + private final Class[] classes; + + Type(final Class[] classes) { + this.classes = classes; + } + + /** + * Check if input can be affected to an entry of this type. + * + * @param input : object. + * + * @return true if input is null or ok. + */ + public boolean isCompatible(final Object input) { + if (input == null) { + return true; + } + for (final Class clazz : classes) { + if (clazz.isInstance(input)) { + return true; + } + } + return false; + } + } +} diff --git a/component-server-parent/component-server/pom.xml b/component-server-parent/component-server/pom.xml index b923c9b166fe9..396c76c70503b 100644 --- a/component-server-parent/component-server/pom.xml +++ b/component-server-parent/component-server/pom.xml @@ -63,11 +63,6 @@ component-runtime-design-extension ${project.version} - - org.talend.sdk.component - component-server-api - ${project.version} - org.talend.sdk.component vault-client @@ -191,6 +186,12 @@ ${project.version} test + + org.talend.sdk.component + component-server-api + ${project.version} + test + org.talend.sdk.component component-form-core diff --git a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java new file mode 100644 index 0000000000000..93433cc4aa6fb --- /dev/null +++ b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java @@ -0,0 +1,234 @@ +package org.talend.sdk.component.server.front; + +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; +import static org.talend.sdk.component.api.record.Schema.Type.LONG; +import static org.talend.sdk.component.api.record.Schema.Type.RECORD; +import static org.talend.sdk.component.api.record.Schema.Type.STRING; +import static org.junit.jupiter.api.Assertions.*; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import org.junit.jupiter.api.Test; + +import org.talend.sdk.component.api.record.Schema; +import org.talend.sdk.component.runtime.record.SchemaImpl; +import org.talend.sdk.component.server.front.model.JsonEntryModel; +import org.talend.sdk.component.server.front.model.JsonSchemaModel; + +class JsonSchemaTest { + + private final Jsonb jsonb = JsonbBuilder.create(); + + @Test + void shouldSerializeSchemaImplAndDeserializeToJsonSchemaModel() { + // given + final Schema schema = new SchemaImpl.BuilderImpl() // + .withType(Schema.Type.RECORD) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("id") + .withType(STRING) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field2") + .withType(LONG) + .withNullable(false) + .withComment("field2 comment") + .build()) + .withProp("namespace", "test") + .build(); + + // when: serialize SchemaImpl + String json = jsonb.toJson(schema); + + // then: sanity check JSON + assertTrue(json.contains("\"type\":\"RECORD\"")); + assertTrue(json.contains("\"entries\"")); + + // when: deserialize into JsonSchemaModel + JsonSchemaModel model = new JsonSchemaModel(json); + + // then + assertEquals(JsonSchemaModel.Type.RECORD, model.getType()); + assertEquals("test", model.getProps().get("namespace")); + + assertEquals(2, model.getEntries().size()); + assertEquals("id", model.getEntries().get(0).getName()); + + // entryMap built correctly + assertTrue(model.getEntryMap().containsKey("id")); + } + + @Test + void shouldParseArrayEntryElementSchema() { + Schema.Entry nameEntry = new SchemaImpl.EntryImpl.BuilderImpl() + .withName("name") + .withNullable(true) + .withType(Schema.Type.STRING) + .build(); + Schema.Entry ageEntry = new SchemaImpl.EntryImpl.BuilderImpl() + .withName("age") + .withNullable(true) + .withType(Schema.Type.INT) + .build(); + Schema customerSchema = new SchemaImpl.BuilderImpl() // + .withType(Schema.Type.RECORD) + .withEntry(nameEntry) + .withEntry(ageEntry) + .build(); + + final Schema schema = new SchemaImpl.BuilderImpl() // + .withType(Schema.Type.ARRAY) // + .withElementSchema(customerSchema) + .withProp("namespace", "test") + .build(); + + String json = jsonb.toJson(schema); + JsonSchemaModel model = new JsonSchemaModel(json); + + JsonSchemaModel innerSchema = model.getElementSchema(); + + assertEquals(JsonSchemaModel.Type.ARRAY, model.getType()); + assertNotNull(innerSchema); + assertEquals(JsonSchemaModel.Type.RECORD, innerSchema.getType()); + //check entry name + final List entryNames = innerSchema.getEntries().stream().map(JsonEntryModel::getName) + .toList(); + Assertions.assertEquals(2, entryNames.size()); + Assertions.assertTrue(entryNames.contains("name")); + Assertions.assertTrue(entryNames.contains("age")); + + //check entry type + final List entryTypes = innerSchema.getEntries().stream().map(JsonEntryModel::getType) + .toList(); + Assertions.assertTrue(entryTypes.contains(JsonSchemaModel.Type.INT)); + Assertions.assertTrue(entryTypes.contains(JsonSchemaModel.Type.STRING)); + } + + + @Test + void shouldMarkMetadataEntries() { + Schema.Entry meta1 = new SchemaImpl.EntryImpl.BuilderImpl() // + .withName("meta1") // + .withType(Schema.Type.INT) // + .withMetadata(true) // + .build(); + + Schema.Entry meta2 = new SchemaImpl.EntryImpl.BuilderImpl() // + .withName("meta2") // + .withType(Schema.Type.STRING) // + .withMetadata(true) // + .withNullable(true) // + .build(); + + Schema.Entry data1 = new SchemaImpl.EntryImpl.BuilderImpl() // + .withName("data1") // + .withType(Schema.Type.INT) // + .build(); + Schema.Entry data2 = new SchemaImpl.EntryImpl.BuilderImpl() // + .withName("data2") // + .withType(Schema.Type.STRING) // + .withNullable(true) // + .build(); + + Schema schema = new SchemaImpl.BuilderImpl() // + .withType(Schema.Type.RECORD) // + .withEntry(data1) // + .withEntry(meta1) // + .withEntry(data2) // + .withEntry(meta2) // + .build(); + + String json = jsonb.toJson(schema); + JsonSchemaModel model = new JsonSchemaModel(json); + + JsonEntryModel metaEntry = model.getMetadataEntries().get(0); + + //check entry name + final List entryNames = model.getEntries().stream().map(JsonEntryModel::getName) + .toList(); + Assertions.assertEquals(2, entryNames.size()); + Assertions.assertTrue(entryNames.contains(data1.getName())); + Assertions.assertTrue(entryNames.contains(data2.getName())); + Assertions.assertEquals(4, schema.getAllEntries().count()); + + //check entry type + final List entryTypes = model.getEntries().stream().map(JsonEntryModel::getType) + .map(JsonSchemaModel.Type::name) + .toList(); + Assertions.assertEquals(2, entryTypes.size()); + Assertions.assertTrue(entryTypes.contains(data1.getType().name())); + Assertions.assertTrue(entryTypes.contains(data2.getType().name())); + + //check meta name + final List metaEntryNames = model.getMetadataEntries().stream().map(JsonEntryModel::getName) + .toList(); + Assertions.assertEquals(2, metaEntryNames.size()); + Assertions.assertTrue(metaEntryNames.contains(meta1.getName())); + Assertions.assertTrue(metaEntryNames.contains(meta2.getName())); + + //check meta type + final List metaEntryTypes = model.getMetadataEntries().stream().map(JsonEntryModel::getType) + .map(JsonSchemaModel.Type::name) + .toList(); + Assertions.assertEquals(2, metaEntryTypes.size()); + Assertions.assertTrue(metaEntryTypes.contains(meta1.getType().name())); + Assertions.assertTrue(metaEntryTypes.contains(meta2.getType().name())); + + assertTrue(metaEntry.isMetadata()); + assertEquals("meta1", metaEntry.getName()); + } + + @Test + void shouldParseEntryProps() { + Schema schema = new SchemaImpl.BuilderImpl() // + .withType(Schema.Type.RECORD) + //.type(Schema.Type.RECORD) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field") + .withType(Schema.Type.STRING) + .withProp("format", "email") + .build()) + .build(); + + String json = jsonb.toJson(schema); + JsonSchemaModel model = new JsonSchemaModel(json); + + JsonEntryModel entry = model.getEntries().get(0); + + assertEquals("email", entry.getProps().get("format")); + } + + @Test + void shouldFailForInvalidEntryType() { + String invalidJson = """ + { + "type": "RECORD", + "entries": [ + { "name": "x", "type": "INVALID" } + ] + } + """; + + assertThrows(IllegalArgumentException.class, + () -> new JsonSchemaModel(invalidJson)); + } + + + @Test + void shouldFailForInvalidSchemaType() { + String invalidJson = """ + { + "type": "NOT_VALID", + "entries": [] + } + """; + + assertThrows(IllegalArgumentException.class, + () -> new JsonSchemaModel(invalidJson)); + } + +} + + From 53bb857826e08f14ebacf4451068df526cfd47d4 Mon Sep 17 00:00:00 2001 From: yyin-talend Date: Wed, 24 Dec 2025 11:06:01 +0800 Subject: [PATCH 2/4] Add Json Schema --- .../server/front/model/JsonEntryModel.java | 27 ++++++++++-- .../server/front/model/JsonSchemaModel.java | 44 ++++++++++++------- .../component-server/pom.xml | 1 - .../server/front/JsonSchemaTest.java | 17 ++++++- 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java index 711d87c44110a..6671932d5b1b7 100644 --- a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java @@ -1,11 +1,28 @@ +/** + * 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.server.front.model; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; + import javax.json.JsonObject; import javax.json.JsonValue; + import lombok.Getter; import lombok.Setter; @@ -88,7 +105,7 @@ public class JsonEntryModel { @Getter private Map props = new LinkedHashMap<>(0); - JsonEntryModel(JsonObject jsonEntry, boolean isMetadata) { + JsonEntryModel(final JsonObject jsonEntry, final boolean isMetadata) { this.jsonEntry = jsonEntry; this.isMetadata = isMetadata; this.type = JsonSchemaModel.Type.valueOf(jsonEntry.getString("type")); @@ -100,8 +117,10 @@ public class JsonEntryModel { : null; } - private Map parseProps(JsonObject propsObj) { - if (propsObj == null) return Collections.emptyMap(); + private Map parseProps(final JsonObject propsObj) { + if (propsObj == null) { + return Collections.emptyMap(); + } Map result = new HashMap<>(); propsObj.forEach((key, value) -> @@ -145,7 +164,7 @@ public String getComment() { return jsonEntry.getString("comment", null); } - public String getProp(String property) { + public String getProp(final String property) { return props.get(property); } diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java index 5890ccec51425..5a083b6a34747 100644 --- a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java @@ -1,3 +1,18 @@ +/** + * 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.server.front.model; import java.io.StringReader; @@ -10,16 +25,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import java.util.stream.Collectors; + import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonValue; + import lombok.Getter; import lombok.Setter; -//import org.json.JSONObject; + public class JsonSchemaModel { @@ -29,6 +45,7 @@ public class JsonSchemaModel { private final JsonObject jsonSchema; + @Setter @Getter private JsonSchemaModel elementSchema; @@ -45,9 +62,10 @@ public class JsonSchemaModel { private Map props = new HashMap(); @Getter + @Setter private Map entryMap = new HashMap<>(); - public JsonSchemaModel(String jsonString) { + public JsonSchemaModel(final String jsonString) { try (JsonReader reader = Json.createReader(new StringReader(jsonString))) { this.jsonSchema = reader.readObject(); } @@ -69,8 +87,10 @@ public JsonSchemaModel(String jsonString) { : null; } - private List parseEntries(JsonArray jsonArray, boolean isMetadata) { - if (jsonArray == null) return Collections.emptyList(); + private List parseEntries(final JsonArray jsonArray, final boolean isMetadata) { + if (jsonArray == null) { + return Collections.emptyList(); + } return jsonArray.stream() .map(JsonValue::asJsonObject) @@ -78,8 +98,10 @@ private List parseEntries(JsonArray jsonArray, boolean isMetadat .collect(Collectors.toList()); } - private Map parseProps(JsonObject propsObj) { - if (propsObj == null) return Collections.emptyMap(); + private Map parseProps(final JsonObject propsObj) { + if (propsObj == null) { + return Collections.emptyMap(); + } Map result = new HashMap<>(); propsObj.forEach((key, value) -> @@ -89,14 +111,6 @@ private Map parseProps(JsonObject propsObj) { return result; } - public void setEntryMap(Map entryMap) { - this.entryMap = entryMap; - } - - public void setElementSchema(JsonSchemaModel elementSchema) { - this.elementSchema = elementSchema; - } - public enum Type { RECORD(new Class[] { Record.class }), diff --git a/component-server-parent/component-server/pom.xml b/component-server-parent/component-server/pom.xml index 396c76c70503b..e66b7a97df095 100644 --- a/component-server-parent/component-server/pom.xml +++ b/component-server-parent/component-server/pom.xml @@ -190,7 +190,6 @@ org.talend.sdk.component component-server-api ${project.version} - test org.talend.sdk.component diff --git a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java index 93433cc4aa6fb..470c87fde404d 100644 --- a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java +++ b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java @@ -1,10 +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.server.front; import java.util.List; -import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; import static org.talend.sdk.component.api.record.Schema.Type.LONG; -import static org.talend.sdk.component.api.record.Schema.Type.RECORD; import static org.talend.sdk.component.api.record.Schema.Type.STRING; import static org.junit.jupiter.api.Assertions.*; From f8878954822a0cc8e86fee322165c354963d7f0d Mon Sep 17 00:00:00 2001 From: yyin-talend Date: Thu, 25 Dec 2025 17:54:03 +0800 Subject: [PATCH 3/4] Add junit case --- .../server/front/model/JsonEntryModel.java | 9 +- .../server/front/JsonSchemaTest.java | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java index 6671932d5b1b7..61efd4ae0ce6b 100644 --- a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java @@ -30,21 +30,18 @@ public class JsonEntryModel { private final JsonObject jsonEntry; - @Getter private final boolean isMetadata; /** * The name of this entry. */ @Setter - @Getter private String name; /** * The raw name of this entry. */ @Setter - @Getter private String rawName; /** @@ -58,7 +55,6 @@ public class JsonEntryModel { * Is this entry nullable or always valued. */ @Setter - @Getter private boolean nullable; /** @@ -77,7 +73,6 @@ public class JsonEntryModel { * Default value for this entry. */ @Setter - @Getter private Object defaultValue; /** @@ -91,11 +86,9 @@ public class JsonEntryModel { * Allows to associate to this field a comment - for doc purposes, no use in the runtime. */ @Setter - @Getter private String comment; @Setter - @Getter private boolean valid; /** @@ -143,7 +136,7 @@ public String getOriginalFieldName() { } public boolean isNullable() { - return jsonEntry.getBoolean("nullable", true); + return !jsonEntry.containsKey("nullable") || jsonEntry.getBoolean("nullable"); } public boolean isErrorCapable() { diff --git a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java index 470c87fde404d..1bb2c4e0bbb38 100644 --- a/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java +++ b/component-server-parent/component-server/src/test/java/org/talend/sdk/component/server/front/JsonSchemaTest.java @@ -68,11 +68,121 @@ void shouldSerializeSchemaImplAndDeserializeToJsonSchemaModel() { assertEquals(2, model.getEntries().size()); assertEquals("id", model.getEntries().get(0).getName()); + assertEquals(JsonSchemaModel.Type.STRING, model.getEntries().get(0).getType()); + assertEquals("field2", model.getEntries().get(1).getName()); + assertEquals(JsonSchemaModel.Type.LONG, model.getEntries().get(1).getType()); + assertEquals("field2 comment", model.getEntries().get(1).getComment()); + assertEquals(false, model.getEntries().get(1).isNullable()); // entryMap built correctly assertTrue(model.getEntryMap().containsKey("id")); } + @Test + void testAllDataTypes() { + // given + final Schema schema = new SchemaImpl.BuilderImpl() // + .withType(Schema.Type.RECORD) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("id") + .withType(Schema.Type.INT) + .withNullable(false) + .withErrorCapable(true) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field_date") + .withType(Schema.Type.DATETIME) + .withNullable(false) + .withErrorCapable(false) + .withComment("field date") + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field_boolean") + .withType(Schema.Type.BOOLEAN) + .withNullable(true) + .withComment("field boolean") + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field_bytes") + .withType(Schema.Type.BYTES) + .withComment("field bytes") + .withNullable(true) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field_decimal") + .withType(Schema.Type.DECIMAL) + .withComment("field decimal") + .withNullable(true) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field_double") + .withType(Schema.Type.DOUBLE) + .withComment("field double") + .withNullable(true) + .build()) + .withEntry(new SchemaImpl.EntryImpl.BuilderImpl() + .withName("field_float") + .withType(Schema.Type.FLOAT) + .withComment("field float") + .withNullable(true) + .build()) + .withProp("namespace", "test") + .build(); + + // when: serialize SchemaImpl + String json = jsonb.toJson(schema); + + // then: sanity check JSON + assertTrue(json.contains("\"type\":\"RECORD\"")); + assertTrue(json.contains("\"entries\"")); + + // when: deserialize into JsonSchemaModel + JsonSchemaModel model = new JsonSchemaModel(json); + + // then + assertEquals(JsonSchemaModel.Type.RECORD, model.getType()); + assertEquals("test", model.getProps().get("namespace")); + + assertEquals(7, model.getEntries().size()); + assertEquals("id", model.getEntries().get(0).getName()); + assertEquals(JsonSchemaModel.Type.INT, model.getEntries().get(0).getType()); + assertFalse(model.getEntries().get(0).isNullable()); + assertTrue(model.getEntries().get(0).isErrorCapable()); + assertEquals("field_date", model.getEntries().get(1).getName()); + assertEquals(JsonSchemaModel.Type.DATETIME, model.getEntries().get(1).getType()); + assertEquals("field date", model.getEntries().get(1).getComment()); + assertFalse(model.getEntries().get(1).isNullable()); + assertFalse(model.getEntries().get(1).isErrorCapable()); + + assertEquals("field_boolean", model.getEntries().get(2).getName()); + assertEquals(JsonSchemaModel.Type.BOOLEAN, model.getEntries().get(2).getType()); + assertEquals("field boolean", model.getEntries().get(2).getComment()); + assertTrue(model.getEntries().get(2).isNullable()); + assertFalse(model.getEntries().get(2).isErrorCapable()); + assertEquals("field_bytes", model.getEntries().get(3).getName()); + assertEquals(JsonSchemaModel.Type.BYTES, model.getEntries().get(3).getType()); + assertEquals("field bytes", model.getEntries().get(3).getComment()); + assertTrue(model.getEntries().get(3).isNullable()); + + assertEquals("field_decimal", model.getEntries().get(4).getName()); + assertEquals(JsonSchemaModel.Type.DECIMAL, model.getEntries().get(4).getType()); + assertEquals("field decimal", model.getEntries().get(4).getComment()); + assertTrue(model.getEntries().get(4).isNullable()); + assertEquals("field_double", model.getEntries().get(5).getName()); + assertEquals(JsonSchemaModel.Type.DOUBLE, model.getEntries().get(5).getType()); + assertEquals("field double", model.getEntries().get(5).getComment()); + assertTrue(model.getEntries().get(5).isNullable()); + + assertEquals("field_float", model.getEntries().get(6).getName()); + assertEquals(JsonSchemaModel.Type.FLOAT, model.getEntries().get(6).getType()); + assertEquals("field float", model.getEntries().get(6).getComment()); + assertTrue(model.getEntries().get(6).isNullable()); + // entryMap built correctly + assertTrue(model.getEntryMap().containsKey("id")); + assertEquals("id,field_date,field_boolean,field_bytes,field_decimal,field_double,field_float", + model.getProps().get("talend.fields.order")); + } + @Test void shouldParseArrayEntryElementSchema() { Schema.Entry nameEntry = new SchemaImpl.EntryImpl.BuilderImpl() From 46bd8162ee0c18420987b091723e58e3aa8a3642 Mon Sep 17 00:00:00 2001 From: yyin-talend Date: Mon, 5 Jan 2026 15:43:07 +0800 Subject: [PATCH 4/4] fix sonar issue --- .../server/front/model/JsonEntryModel.java | 39 +------------------ .../server/front/model/JsonSchemaModel.java | 9 ++--- 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java index 61efd4ae0ce6b..a5302e565df3e 100644 --- a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonEntryModel.java @@ -30,20 +30,9 @@ public class JsonEntryModel { private final JsonObject jsonEntry; + @Getter private final boolean isMetadata; - /** - * The name of this entry. - */ - @Setter - private String name; - - /** - * The raw name of this entry. - */ - @Setter - private String rawName; - /** * Type of the entry, this determine which other fields are populated. */ @@ -51,17 +40,6 @@ public class JsonEntryModel { @Getter private JsonSchemaModel.Type type; - /** - * Is this entry nullable or always valued. - */ - @Setter - private boolean nullable; - - /** - * Is this entry can be in error. - */ - private boolean errorCapable; - /** * Is this entry a metadata entry. */ @@ -69,12 +47,6 @@ public class JsonEntryModel { @Getter private boolean metadata; - /** - * Default value for this entry. - */ - @Setter - private Object defaultValue; - /** * For type == record, the element type. */ @@ -82,15 +54,6 @@ public class JsonEntryModel { @Setter private JsonSchemaModel elementSchema; - /** - * Allows to associate to this field a comment - for doc purposes, no use in the runtime. - */ - @Setter - private String comment; - - @Setter - private boolean valid; - /** * metadata */ diff --git a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java index 5a083b6a34747..426321f2ff046 100644 --- a/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java +++ b/component-server-parent/component-server-model/src/main/java/org/talend/sdk/component/server/front/model/JsonSchemaModel.java @@ -51,15 +51,14 @@ public class JsonSchemaModel { @Getter @Setter - private List entries = new ArrayList(); + private List entries = new ArrayList<>(); @Getter - private List metadataEntries = new ArrayList(); - ; + private List metadataEntries = new ArrayList<>(); @Getter @Setter - private Map props = new HashMap(); + private Map props = new HashMap<>(); @Getter @Setter @@ -95,7 +94,7 @@ private List parseEntries(final JsonArray jsonArray, final boole return jsonArray.stream() .map(JsonValue::asJsonObject) .map(obj -> new JsonEntryModel(obj, isMetadata)) - .collect(Collectors.toList()); + .toList(); } private Map parseProps(final JsonObject propsObj) {