001/**
002 * Copyright (C) 2006-2025 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}