001/** 002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.talend.sdk.component.runtime.manager.xbean.converter; 017 018import java.io.StringReader; 019import java.math.BigDecimal; 020import java.math.BigInteger; 021import java.util.Iterator; 022import java.util.Map; 023import java.util.ServiceLoader; 024import java.util.function.BiConsumer; 025import java.util.function.Consumer; 026import java.util.stream.Collectors; 027import java.util.stream.Stream; 028 029import javax.json.Json; 030import javax.json.JsonArray; 031import javax.json.JsonArrayBuilder; 032import javax.json.JsonNumber; 033import javax.json.JsonObject; 034import javax.json.JsonObjectBuilder; 035import javax.json.JsonString; 036import javax.json.JsonValue; 037import javax.json.spi.JsonProvider; 038 039import org.apache.xbean.propertyeditor.AbstractConverter; 040import org.talend.sdk.component.api.record.Schema; 041import org.talend.sdk.component.api.service.record.RecordBuilderFactory; 042import org.talend.sdk.component.runtime.manager.service.record.RecordBuilderFactoryProvider; 043import org.talend.sdk.component.runtime.record.RecordBuilderFactoryImpl; 044 045public class SchemaConverter extends AbstractConverter { 046 047 public static final String ELEMENT_SCHEMA = "elementSchema"; 048 049 public static final String PROPS = "props"; 050 051 public static final String TYPE = "type"; 052 053 public static final String ENTRIES = "entries"; 054 055 private final RecordBuilderFactory factory; 056 057 public SchemaConverter() { 058 super(Schema.class); 059 try { 060 final RecordBuilderFactoryProvider recordBuilderFactoryProvider; 061 final Iterator<RecordBuilderFactoryProvider> spiIterator = 062 ServiceLoader.load(RecordBuilderFactoryProvider.class).iterator(); 063 if (spiIterator.hasNext()) { 064 final RecordBuilderFactoryProvider spi = spiIterator.next(); 065 recordBuilderFactoryProvider = spi; 066 } else { 067 recordBuilderFactoryProvider = RecordBuilderFactoryImpl::new; 068 } 069 factory = recordBuilderFactoryProvider.apply("null"); 070 } catch (RuntimeException e) { 071 throw new IllegalStateException(e); 072 } 073 } 074 075 @Override 076 public Object toObjectImpl(final String s) { 077 if (!s.isEmpty()) { 078 final JsonObject json = JsonProvider.provider().createReader(new StringReader(s)).readObject(); 079 return toSchema(json); 080 } 081 return null; 082 } 083 084 private Schema toSchema(final JsonObject json) { 085 if (json == null || json.isNull(TYPE)) { 086 return null; 087 } 088 final String type = json.getString(TYPE); 089 final Schema.Type schemaType = Enum.valueOf(Schema.Type.class, type); 090 final Schema.Builder builder = factory.newSchemaBuilder(schemaType); 091 092 if (schemaType == Schema.Type.RECORD) { 093 this.addEntries(builder, json.getJsonArray(ENTRIES)); 094 this.addEntries(builder, json.getJsonArray("metadatas")); 095 } else if (schemaType == Schema.Type.ARRAY) { 096 this.treatElementSchema(json, builder::withElementSchema); 097 } 098 this.addProps(builder::withProp, json); 099 100 final JsonValue orderValue = json.get("order"); 101 if (orderValue instanceof JsonString) { 102 final Schema.EntriesOrder order = Schema.EntriesOrder.of(((JsonString) orderValue).getString()); 103 return builder.build(order); 104 } else { 105 return builder.build(); 106 } 107 } 108 109 private void treatElementSchema(final JsonObject json, 110 final Consumer<Schema> setter) { 111 final JsonValue elementSchema = json.get(ELEMENT_SCHEMA); 112 if (elementSchema instanceof JsonObject) { 113 final Schema schema = this.toSchema((JsonObject) elementSchema); 114 setter.accept(schema); 115 } else if (elementSchema instanceof JsonString) { 116 final Schema.Type innerType = Schema.Type.valueOf(((JsonString) elementSchema).getString()); 117 setter.accept(this.factory.newSchemaBuilder(innerType).build()); 118 } 119 } 120 121 private void addEntries(final Schema.Builder schemaBuilder, final JsonArray entries) { 122 if (entries == null || entries.isEmpty()) { 123 return; 124 } 125 126 entries.stream() 127 .filter(JsonObject.class::isInstance) 128 .map(JsonObject.class::cast) 129 .map(this::jsonToEntry) 130 .forEach(schemaBuilder::withEntry); 131 } 132 133 private Schema.Entry jsonToEntry(final JsonObject jsonEntry) { 134 final Schema.Entry.Builder builder = factory.newEntryBuilder(); 135 final Schema.Type schemaType = Enum.valueOf(Schema.Type.class, jsonEntry.getString(TYPE)); 136 builder.withType(schemaType) 137 .withName(jsonEntry.getString("name")) 138 .withNullable(jsonEntry.getBoolean("nullable", true)) 139 .withMetadata(jsonEntry.getBoolean("metadata", false)); 140 final JsonString comment = jsonEntry.getJsonString("comment"); 141 if (comment != null) { 142 builder.withComment(comment.getString()); 143 } 144 final JsonString rawName = jsonEntry.getJsonString("rawName"); 145 if (rawName != null) { 146 builder.withRawName(rawName.getString()); 147 } 148 final JsonValue jsonValue = jsonEntry.get("defaultValue"); 149 if (jsonValue != null) { 150 this.setDefaultValue(builder, jsonValue); 151 } 152 153 if (schemaType == Schema.Type.RECORD || schemaType == Schema.Type.ARRAY) { 154 this.treatElementSchema(jsonEntry, builder::withElementSchema); 155 } 156 157 this.addProps(builder::withProp, jsonEntry); 158 return builder.build(); 159 } 160 161 private void addProps(final BiConsumer<String, String> setter, 162 final JsonObject object) { 163 if (object == null || object.get(PROPS) == null) { 164 return; 165 } 166 JsonObject props = object.getJsonObject(PROPS); 167 if (props.isEmpty()) { 168 return; 169 } 170 props.forEach( 171 (String name, JsonValue value) -> setter.accept(name, ((JsonString) value).getString())); 172 } 173 174 private void setDefaultValue(final Schema.Entry.Builder entry, 175 final JsonValue value) { 176 final JsonValue.ValueType valueType = value.getValueType(); 177 if (valueType == JsonValue.ValueType.NUMBER) { 178 entry.withDefaultValue(((JsonNumber) value).numberValue()); 179 } else if (valueType == JsonValue.ValueType.FALSE) { 180 entry.withDefaultValue(Boolean.FALSE); 181 } else if (valueType == JsonValue.ValueType.TRUE) { 182 entry.withDefaultValue(Boolean.TRUE); 183 } else if (valueType == JsonValue.ValueType.STRING) { 184 entry.withDefaultValue(((JsonString) value).getString()); 185 } 186 // doesn't treat JsonArray nor JsonObject for default value. 187 } 188 189 public JsonObject toJson(final Schema schema) { 190 if (schema == null) { 191 return null; 192 } 193 final JsonObjectBuilder builder = Json.createObjectBuilder(); 194 builder.add(TYPE, schema.getType().name()); 195 if (schema.getType() == Schema.Type.RECORD) { 196 this.addEntries(builder, ENTRIES, schema.getAllEntries()); 197 } else if (schema.getType() == Schema.Type.ARRAY) { 198 final JsonObject elementSchema = this.toJson(schema.getElementSchema()); 199 if (elementSchema != null) { 200 builder.add(ELEMENT_SCHEMA, elementSchema); 201 } 202 } 203 this.addProps(builder, schema.getProps()); 204 205 if (schema.getType() == Schema.Type.RECORD) { 206 final Schema.EntriesOrder order = schema.naturalOrder(); 207 if (order != null) { 208 String orderValue = order.getFieldsOrder().collect(Collectors.joining(",")); 209 builder.add("order", orderValue); 210 } 211 } 212 return builder.build(); 213 } 214 215 private void addEntries(final JsonObjectBuilder objectBuilder, 216 final String name, 217 final Stream<Schema.Entry> entries) { 218 if (entries == null) { 219 return; 220 } 221 final JsonArrayBuilder jsonEntries = Json.createArrayBuilder(); 222 objectBuilder.add(name, jsonEntries); 223 entries.map(this::entryToJson) 224 .forEach(jsonEntries::add); 225 } 226 227 private JsonObjectBuilder entryToJson(final Schema.Entry entry) { 228 final JsonObjectBuilder entryBuilder = Json.createObjectBuilder(); 229 entryBuilder.add("name", entry.getName()); 230 entryBuilder.add(TYPE, entry.getType().name()); 231 entryBuilder.add("nullable", entry.isNullable()); 232 if (entry.getComment() != null) { 233 entryBuilder.add("comment", entry.getComment()); 234 } 235 if (entry.getRawName() != null) { 236 entryBuilder.add("rawName", entry.getRawName()); 237 } 238 if (entry.isMetadata()) { 239 entryBuilder.add("metadata", true); 240 } 241 if (entry.getDefaultValue() != null) { 242 final JsonValue jsonValue = this.toValue(entry.getDefaultValue()); 243 if (jsonValue != null) { 244 entryBuilder.add("defaultValue", jsonValue); 245 } 246 } 247 if (entry.getType() == Schema.Type.RECORD 248 || entry.getType() == Schema.Type.ARRAY) { 249 250 final Schema innerSchema = entry.getElementSchema(); 251 if (innerSchema.getType() == Schema.Type.ARRAY 252 || innerSchema.getType() == Schema.Type.RECORD) { 253 final JsonObject elementSchema = this.toJson(innerSchema); 254 entryBuilder.add(ELEMENT_SCHEMA, elementSchema); 255 } else { 256 entryBuilder.add(ELEMENT_SCHEMA, innerSchema.getType().name()); 257 } 258 } 259 this.addProps(entryBuilder, entry.getProps()); 260 return entryBuilder; 261 } 262 263 private void addProps(final JsonObjectBuilder builder, 264 final Map<String, String> properties) { 265 if (properties == null || properties.isEmpty()) { 266 return; 267 } 268 final JsonObjectBuilder jsonProps = Json.createObjectBuilder(); 269 properties.entrySet() 270 .forEach( 271 (Map.Entry<String, String> e) -> jsonProps.add(e.getKey(), e.getValue())); 272 builder.add(PROPS, jsonProps); 273 } 274 275 private JsonValue toValue(final Object object) { 276 if (object == null) { 277 return JsonValue.NULL; 278 } 279 if (object instanceof Integer) { 280 return Json.createValue((Integer) object); 281 } 282 if (object instanceof Long) { 283 return Json.createValue((Long) object); 284 } 285 if (object instanceof Double || object instanceof Float) { 286 return Json.createValue((Double) object); 287 } 288 if (object instanceof BigInteger) { 289 return Json.createValue((BigInteger) object); 290 } 291 if (object instanceof Boolean) { 292 if (object == Boolean.TRUE) { 293 return JsonValue.TRUE; 294 } 295 return JsonValue.FALSE; 296 } 297 if (object instanceof BigDecimal) { 298 return Json.createValue((BigDecimal) object); 299 } 300 if (object instanceof String) { 301 return Json.createValue((String) object); 302 } 303 304 return null; 305 } 306 307}