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.reflect.visibility; 017 018import static java.util.Collections.emptyMap; 019import static java.util.Comparator.comparing; 020import static java.util.Optional.ofNullable; 021import static javax.json.stream.JsonCollectors.toJsonArray; 022 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Map; 026import java.util.stream.Stream; 027 028import javax.json.JsonArray; 029import javax.json.JsonBuilderFactory; 030import javax.json.JsonObject; 031import javax.json.JsonObjectBuilder; 032import javax.json.JsonValue; 033import javax.json.spi.JsonProvider; 034 035import org.talend.sdk.component.runtime.manager.ParameterMeta; 036 037import lombok.RequiredArgsConstructor; 038 039@RequiredArgsConstructor 040public class PayloadMapper { 041 042 // we don't need the runtime one here 043 private final JsonProvider jsonp = JsonProvider.provider(); 044 045 private static final VisibilityService VISIBILITY_SERVICE = new VisibilityService(JsonProvider.provider()); 046 047 private final JsonBuilderFactory factory = jsonp.createBuilderFactory(emptyMap()); 048 049 private final OnParameter parameterVisitor; 050 051 private JsonObject globalPayload = null; 052 053 public JsonObject visitAndMap(final Collection<ParameterMeta> parameters, final Map<String, String> payload) { 054 return unflatten("", ofNullable(parameters).orElseGet(Collections::emptyList), 055 payload == null ? emptyMap() : payload); 056 } 057 058 public void setGlobalPayload(final JsonObject payload) { 059 globalPayload = payload; 060 } 061 062 private JsonObject unflatten(final String contextualPrefix, final Collection<ParameterMeta> definitions, 063 final Map<String, String> config) { 064 final JsonObjectBuilder json = factory.createObjectBuilder(); 065 ofNullable(definitions) 066 .map(Collection::stream) 067 .orElseGet(Stream::empty) 068 .sorted(comparing(ParameterMeta::getName)) 069 .forEach(meta -> onProperty(contextualPrefix, meta.getNestedParameters(), config, json, meta)); 070 return json.build(); 071 } 072 073 private void onProperty(final String contextualPrefix, final Collection<ParameterMeta> definitions, 074 final Map<String, String> config, final JsonObjectBuilder json, final ParameterMeta definition) { 075 final String name = definition.getName(); 076 final String newPath = contextualPrefix + (contextualPrefix.isEmpty() ? "" : ".") + name; 077 switch (definition.getType()) { 078 case OBJECT: { 079 onObject(definitions, definition, config, json, name, newPath); 080 break; 081 } 082 case ARRAY: { 083 onArray(definition.getNestedParameters(), definition, config, newPath, json, name); 084 break; 085 } 086 case BOOLEAN: 087 final String boolValue = config.get(newPath); 088 if (boolValue == null || boolValue.isEmpty()) { 089 parameterVisitor.onParameter(definition, JsonValue.NULL); 090 } else { 091 final boolean value = Boolean.parseBoolean(boolValue.trim()); 092 parameterVisitor.onParameter(definition, value ? JsonValue.TRUE : JsonValue.FALSE); 093 json.add(name, value); 094 } 095 ofNullable(boolValue) 096 .map(String::trim) 097 .filter(v -> !v.isEmpty()) 098 .ifPresent(v -> json.add(name, Boolean.parseBoolean(v))); 099 break; 100 case NUMBER: 101 final String numberValue = config.get(newPath); 102 if (numberValue == null || numberValue.isEmpty()) { 103 parameterVisitor.onParameter(definition, JsonValue.NULL); 104 } else { 105 final Double value = Double.valueOf(numberValue.trim()); 106 parameterVisitor.onParameter(definition, jsonp.createValue(value)); 107 final long asLong = value.longValue(); 108 if (value == asLong) { 109 json.add(name, asLong); 110 } else { 111 json.add(name, value); 112 } 113 } 114 break; 115 case ENUM: 116 case STRING: { 117 final String value = config.get(newPath); 118 parameterVisitor.onParameter(definition, value == null ? JsonValue.NULL : jsonp.createValue(value)); 119 ofNullable(value).ifPresent(v -> json.add(name, v)); 120 break; 121 } 122 default: 123 } 124 } 125 126 private void onObject(final Collection<ParameterMeta> definitions, final ParameterMeta meta, 127 final Map<String, String> config, final JsonObjectBuilder json, final String name, 128 final String currentPath) { 129 if (!isVisible(meta)) { 130 return; 131 } 132 final JsonObject unflatten = unflatten(currentPath, definitions, config); 133 if (!unflatten.isEmpty()) { 134 json.add(name, unflatten); 135 parameterVisitor.onParameter(meta, unflatten); 136 } else { 137 parameterVisitor.onParameter(meta, JsonValue.NULL); 138 } 139 } 140 141 private boolean isVisible(final ParameterMeta meta) { 142 return globalPayload == null ? true : VISIBILITY_SERVICE.build(meta).isVisible(globalPayload); 143 } 144 145 private void onArray(final Collection<ParameterMeta> definitions, final ParameterMeta definition, 146 final Map<String, String> config, final String currentPrefix, final JsonObjectBuilder json, 147 final String name) { 148 final JsonArray array; 149 if (definitions.size() == 1 && definitions.iterator().next().getPath().endsWith("[${index}]")) { // primitive 150 final ParameterMeta primitiveMeta = definitions.stream().iterator().next(); 151 array = config 152 .entrySet() 153 .stream() 154 .filter(it -> it.getKey().startsWith(currentPrefix + '[')) 155 .map(e -> new ArrayEntry(e, currentPrefix)) 156 .distinct() 157 // sort by index 158 .sorted(comparing(it -> it.index)) 159 .map(entry -> onArrayPrimitive(primitiveMeta, entry)) 160 .collect(toJsonArray()); 161 } else { 162 array = config 163 .entrySet() 164 .stream() 165 .filter(it -> it.getKey().startsWith(currentPrefix + '[')) 166 .map(e -> new ArrayEntry(e, currentPrefix).index) 167 .distinct() 168 // sort by index 169 .sorted(comparing(it -> it)) 170 .map(index -> unflatten(currentPrefix + '[' + index + ']', definitions, config)) 171 .collect(toJsonArray()); 172 } 173 if (!array.isEmpty()) { 174 json.add(name, array); 175 parameterVisitor.onParameter(definition, array); 176 } else { 177 parameterVisitor.onParameter(definition, JsonValue.NULL); 178 } 179 } 180 181 private JsonValue onArrayPrimitive(final ParameterMeta itemDef, final ArrayEntry e) { 182 final String value = e.entry.getValue(); 183 switch (itemDef.getType()) { 184 case BOOLEAN: 185 return Boolean.parseBoolean(value.trim()) ? JsonValue.TRUE : JsonValue.FALSE; 186 case NUMBER: 187 final Double number = Double.valueOf(value.trim()); 188 return number == number.longValue() ? jsonp.createValue(number.longValue()) : jsonp.createValue(number); 189 case ENUM: 190 case STRING: 191 return jsonp.createValue(value); 192 default: 193 throw new IllegalArgumentException("Unsupported structure in " + "array: " + itemDef.getType()); 194 } 195 } 196 197 private static class ArrayEntry { 198 199 private final Map.Entry<String, String> entry; 200 201 private final int index; 202 203 private ArrayEntry(final Map.Entry<String, String> entry, final String name) { 204 this.entry = entry; 205 206 final String indexStr = 207 entry.getKey().substring(name.length() + 1, entry.getKey().indexOf(']', name.length())); 208 this.index = Integer.parseInt(indexStr); 209 } 210 } 211 212 public interface OnParameter { 213 214 void onParameter(ParameterMeta meta, JsonValue value); 215 216 } 217}