001/**
002 * Copyright (C) 2006-2020 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.server.service;
017
018import static java.util.Optional.ofNullable;
019import static java.util.function.Function.identity;
020import static java.util.stream.Collectors.toSet;
021import static org.talend.sdk.component.server.lang.CustomCollectors.toLinkedMap;
022
023import java.util.AbstractMap;
024import java.util.Collection;
025import java.util.Comparator;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Locale;
029import java.util.Map;
030import java.util.function.Predicate;
031import java.util.stream.Collectors;
032import java.util.stream.Stream;
033
034import javax.enterprise.context.ApplicationScoped;
035import javax.inject.Inject;
036import javax.json.bind.Jsonb;
037
038import org.talend.sdk.component.runtime.internationalization.ParameterBundle;
039import org.talend.sdk.component.runtime.manager.ParameterMeta;
040import org.talend.sdk.component.runtime.manager.reflect.parameterenricher.ValidationParameterEnricher;
041import org.talend.sdk.component.runtime.manager.util.DefaultValueInspector;
042import org.talend.sdk.component.server.configuration.ComponentServerConfiguration;
043import org.talend.sdk.component.server.front.model.PropertyValidation;
044import org.talend.sdk.component.server.front.model.SimplePropertyDefinition;
045import org.talend.sdk.component.server.service.qualifier.ComponentServer;
046
047import lombok.extern.slf4j.Slf4j;
048
049@Slf4j
050@ApplicationScoped
051public class PropertiesService {
052
053    private final DefaultValueInspector defaultValueInspector = new DefaultValueInspector();
054
055    @Inject
056    private PropertyValidationService propertyValidationService;
057
058    @Inject
059    private ComponentServerConfiguration componentServerConfiguration;
060
061    @Inject
062    @ComponentServer
063    private Jsonb defaultMapper;
064
065    public Stream<SimplePropertyDefinition> buildProperties(final List<ParameterMeta> meta, final ClassLoader loader,
066            final Locale locale, final DefaultValueInspector.Instance rootInstance) {
067        return buildProperties(meta, loader, locale, rootInstance, null);
068    }
069
070    private Stream<SimplePropertyDefinition> buildProperties(final List<ParameterMeta> meta, final ClassLoader loader,
071            final Locale locale, final DefaultValueInspector.Instance rootInstance, final ParameterMeta parent) {
072        return meta.stream().flatMap(p -> {
073            final String path = sanitizePropertyName(p.getPath());
074            final String name = sanitizePropertyName(p.getName());
075            final String type = p.getType().name();
076            final boolean isEnum = p.getType() == ParameterMeta.Type.ENUM;
077            PropertyValidation validation = propertyValidationService.map(p.getMetadata());
078            if (isEnum) {
079                if (validation == null) {
080                    validation = new PropertyValidation();
081                }
082                validation.setEnumValues(p.getProposals());
083            }
084            final Map<String, String> sanitizedMetadata = ofNullable(p.getMetadata())
085                    .map(m -> m
086                            .entrySet()
087                            .stream()
088                            .filter(e -> !e.getKey().startsWith(ValidationParameterEnricher.META_PREFIX))
089                            .collect(toLinkedMap(e -> e.getKey().replace("tcomp::", ""), Map.Entry::getValue)))
090                    .orElse(null);
091            final Map<String, String> metadata;
092            if (parent != null) {
093                metadata = sanitizedMetadata;
094            } else {
095                metadata = ofNullable(sanitizedMetadata).orElseGet(HashMap::new);
096                metadata.put("definition::parameter::index", String.valueOf(meta.indexOf(p)));
097            }
098            final DefaultValueInspector.Instance instance = defaultValueInspector
099                    .createDemoInstance(
100                            ofNullable(rootInstance).map(DefaultValueInspector.Instance::getValue).orElse(null), p);
101            final ParameterBundle bundle = p.findBundle(loader, locale);
102            final ParameterBundle parentBundle = parent == null ? null : parent.findBundle(loader, locale);
103            return Stream
104                    .concat(Stream
105                            .of(new SimplePropertyDefinition(path, name,
106                                    bundle.displayName(parentBundle).orElse(p.getName()), type, toDefault(instance, p),
107                                    validation, rewriteMetadataForLocale(metadata, parentBundle, bundle),
108                                    bundle.placeholder(parentBundle).orElse(p.getName()),
109                                    !isEnum ? null
110                                            : p
111                                                    .getProposals()
112                                                    .stream()
113                                                    .collect(toLinkedMap(identity(),
114                                                            key -> bundle
115                                                                    .enumDisplayName(parentBundle, key)
116                                                                    .orElse(key))))),
117                            buildProperties(p.getNestedParameters(), loader, locale, instance, p));
118        }).sorted(Comparator.comparing(SimplePropertyDefinition::getPath)); // important cause it is the way you want to
119        // see it
120    }
121
122    private Map<String, String> rewriteMetadataForLocale(final Map<String, String> metadata,
123            final ParameterBundle parentBundle, final ParameterBundle bundle) {
124        return rewriteLayoutMetadata(rewriteDocMetadata(metadata, parentBundle, bundle), parentBundle, bundle);
125    }
126
127    private Map<String, String> rewriteDocMetadata(final Map<String, String> metadata,
128            final ParameterBundle parentBundle, final ParameterBundle bundle) {
129        final String defaultDoc = metadata.get("documentation::value");
130        final String bundleDoc = bundle.documentation(parentBundle).orElse(null);
131        if (bundleDoc == null || bundleDoc.equals(defaultDoc)) {
132            return metadata;
133        }
134        final Map<String, String> copy = new HashMap<>(metadata);
135        copy.put("documentation::value", bundleDoc);
136        return copy;
137    }
138
139    private Map<String, String> rewriteLayoutMetadata(final Map<String, String> metadata,
140            final ParameterBundle parentBundle, final ParameterBundle bundle) {
141        if (!componentServerConfiguration.getTranslateGridLayoutTabNames()) {
142            return metadata;
143        }
144
145        final Collection<String> keysToRewrite = metadata
146                .keySet()
147                .stream()
148                .filter(it -> it.startsWith("ui::gridlayout::") && it.endsWith("::value"))
149                .collect(toSet());
150        if (keysToRewrite.isEmpty()) {
151            return metadata;
152        }
153        final Predicate<Map.Entry<String, ?>> shouldBeRewritten = k -> keysToRewrite.contains(k.getKey());
154        return Stream
155                .concat(metadata.entrySet().stream().filter(shouldBeRewritten.negate()),
156                        metadata
157                                .entrySet()
158                                .stream()
159                                .filter(shouldBeRewritten)
160                                .map(it -> new AbstractMap.SimpleEntry<>(bundle
161                                        .gridLayoutName(parentBundle,
162                                                it
163                                                        .getKey()
164                                                        .substring("ui::gridlayout::".length(),
165                                                                it.getKey().length() - "::value".length()))
166                                        .map(t -> "ui::gridlayout::" + t + "::value")
167                                        .orElse(it.getKey()), it.getValue())))
168                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
169    }
170
171    private String toDefault(final DefaultValueInspector.Instance instance, final ParameterMeta p) {
172        if (instance.isCreated()) {
173            return null;
174        }
175        if (Collection.class.isInstance(instance.getValue()) || Map.class.isInstance(instance.getValue())) {
176            // @Experimental("not primitives are a challenge, for now use that but can change if not adapted")
177            return defaultMapper.toJson(instance.getValue());
178        }
179        return defaultValueInspector.findDefault(instance.getValue(), p);
180    }
181
182    private String sanitizePropertyName(final String path) {
183        return path.replace("${index}", "");
184    }
185}