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}