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.front; 017 018import static java.util.Collections.singletonList; 019import static java.util.Optional.ofNullable; 020import static java.util.function.Function.identity; 021import static java.util.stream.Collectors.toList; 022import static java.util.stream.Collectors.toMap; 023import static java.util.stream.Collectors.toSet; 024 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.Iterator; 029import java.util.Locale; 030import java.util.Map; 031import java.util.concurrent.ConcurrentHashMap; 032import java.util.concurrent.ConcurrentMap; 033import java.util.function.Function; 034import java.util.function.Predicate; 035import java.util.stream.Stream; 036 037import javax.annotation.PostConstruct; 038import javax.enterprise.context.ApplicationScoped; 039import javax.enterprise.event.Observes; 040import javax.inject.Inject; 041import javax.ws.rs.WebApplicationException; 042import javax.ws.rs.core.Response; 043 044import org.talend.sdk.component.container.Container; 045import org.talend.sdk.component.design.extension.RepositoryModel; 046import org.talend.sdk.component.design.extension.repository.Config; 047import org.talend.sdk.component.runtime.internationalization.FamilyBundle; 048import org.talend.sdk.component.runtime.manager.ComponentManager; 049import org.talend.sdk.component.server.api.ConfigurationTypeResource; 050import org.talend.sdk.component.server.configuration.ComponentServerConfiguration; 051import org.talend.sdk.component.server.dao.ConfigurationDao; 052import org.talend.sdk.component.server.front.base.internal.RequestKey; 053import org.talend.sdk.component.server.front.model.ConfigTypeNode; 054import org.talend.sdk.component.server.front.model.ConfigTypeNodes; 055import org.talend.sdk.component.server.front.model.ErrorDictionary; 056import org.talend.sdk.component.server.front.model.SimplePropertyDefinition; 057import org.talend.sdk.component.server.front.model.error.ErrorPayload; 058import org.talend.sdk.component.server.lang.MapCache; 059import org.talend.sdk.component.server.service.ActionsService; 060import org.talend.sdk.component.server.service.ExtensionComponentMetadataManager; 061import org.talend.sdk.component.server.service.LocaleMapper; 062import org.talend.sdk.component.server.service.PropertiesService; 063import org.talend.sdk.component.server.service.SimpleQueryLanguageCompiler; 064import org.talend.sdk.component.server.service.event.DeployedComponent; 065 066import lombok.extern.slf4j.Slf4j; 067 068@Slf4j 069@ApplicationScoped 070public class ConfigurationTypeResourceImpl implements ConfigurationTypeResource { 071 072 private final ConcurrentMap<RequestKey, ConfigTypeNodes> indicesPerRequest = new ConcurrentHashMap<>(); 073 074 @Inject 075 private ComponentManager manager; 076 077 @Inject 078 private PropertiesService propertiesService; 079 080 @Inject 081 private ActionsService actionsService; 082 083 @Inject 084 private LocaleMapper localeMapper; 085 086 @Inject 087 private ConfigurationDao configurations; 088 089 @Inject 090 private ExtensionComponentMetadataManager virtualComponents; 091 092 @Inject 093 private MapCache caches; 094 095 @Inject 096 private ComponentServerConfiguration configuration; 097 098 @Inject 099 private SimpleQueryLanguageCompiler queryLanguageCompiler; 100 101 private Map<String, Function<ConfigTypeNode, Object>> configNodeEvaluators = new HashMap<>(); 102 103 @PostConstruct 104 private void init() { 105 configNodeEvaluators.put("id", ConfigTypeNode::getId); 106 configNodeEvaluators.put("type", ConfigTypeNode::getConfigurationType); 107 configNodeEvaluators.put("name", ConfigTypeNode::getName); 108 configNodeEvaluators.put("metadata", node -> { 109 final Iterator<SimplePropertyDefinition> iterator = node.getProperties().stream().iterator(); 110 if (iterator.hasNext()) { 111 return iterator.next().getMetadata(); 112 } 113 return Collections.emptyMap(); 114 }); 115 } 116 117 public void clearCache(@Observes final DeployedComponent deployedComponent) { 118 indicesPerRequest.clear(); 119 } 120 121 @Override 122 public ConfigTypeNodes getRepositoryModel(final String language, final boolean lightPayload, final String query) { 123 final Locale locale = localeMapper.mapLocale(language); 124 caches.evictIfNeeded(indicesPerRequest, configuration.getMaxCacheSize() - 1); 125 return indicesPerRequest 126 .computeIfAbsent(new RequestKey(locale, !lightPayload, query), key -> toNodes(locale, lightPayload, 127 it -> true, queryLanguageCompiler.compile(query, configNodeEvaluators))); 128 } 129 130 @Override 131 public ConfigTypeNodes getDetail(final String language, final String[] ids) { 132 final Predicate<String> filter = ids == null ? s -> false : new Predicate<String>() { 133 134 private final Collection<String> values = Stream.of(ids).collect(toSet()); 135 136 @Override 137 public boolean test(final String s) { 138 return values.contains(s); 139 } 140 }; 141 final Locale locale = localeMapper.mapLocale(language); 142 return toNodes(locale, false, filter, it -> true); 143 } 144 145 @Override 146 public Map<String, String> migrate(final String id, final int version, final Map<String, String> config) { 147 if (virtualComponents.isExtensionEntity(id)) { 148 return config; 149 } 150 final Config configuration = ofNullable(configurations.findById(id)) 151 .orElseThrow(() -> new WebApplicationException(Response 152 .status(Response.Status.NOT_FOUND) 153 .entity(new ErrorPayload(ErrorDictionary.CONFIGURATION_MISSING, 154 "Didn't find configuration " + id)) 155 .build())); 156 final Map<String, String> configToMigrate = new HashMap<>(config); 157 final String versionKey = configuration.getMeta().getPath() + ".__version"; 158 final boolean addedVersion = configToMigrate.putIfAbsent(versionKey, Integer.toString(version)) == null; 159 final Map<String, String> migrated = configuration.getMigrationHandler().migrate(version, configToMigrate); 160 if (addedVersion) { 161 migrated.remove(versionKey); 162 } 163 return migrated; 164 } 165 166 private Stream<ConfigTypeNode> createNode(final String parentId, final String family, final Stream<Config> configs, 167 final FamilyBundle resourcesBundle, final Container container, final Locale locale, 168 final Predicate<String> idFilter, final boolean lightPayload) { 169 final ClassLoader loader = container.getLoader(); 170 if (configs == null) { 171 return Stream.empty(); 172 } 173 return configs.flatMap(c -> { 174 final Stream<ConfigTypeNode> configNode; 175 if (idFilter.test(c.getId())) { 176 final ConfigTypeNode node = new ConfigTypeNode(); 177 node.setId(c.getId()); 178 node.setVersion(c.getVersion()); 179 node.setConfigurationType(c.getKey().getConfigType()); 180 node.setName(c.getKey().getConfigName()); 181 node.setParentId(parentId); 182 node 183 .setDisplayName(resourcesBundle 184 .configurationDisplayName(c.getKey().getConfigType(), c.getKey().getConfigName()) 185 .orElse(c.getKey().getConfigName())); 186 if (!lightPayload) { 187 node.setActions(actionsService.findActions(family, container, locale, c, resourcesBundle)); 188 189 // force configuration as root prefix 190 final int prefixLen = c.getMeta().getPath().length(); 191 final String forcedPrefix = c.getMeta().getName(); 192 node 193 .setProperties(propertiesService 194 .buildProperties(singletonList(c.getMeta()), loader, locale, null) 195 .map(p -> new SimplePropertyDefinition( 196 forcedPrefix + p.getPath().substring(prefixLen), p.getName(), 197 p.getDisplayName(), p.getType(), p.getDefaultValue(), p.getValidation(), 198 p.getMetadata(), p.getPlaceholder(), p.getProposalDisplayNames())) 199 .collect(toList())); 200 } 201 202 node.setEdges(c.getChildConfigs().stream().map(Config::getId).collect(toSet())); 203 204 configNode = Stream.of(node); 205 } else { 206 configNode = Stream.empty(); 207 } 208 209 return Stream 210 .concat(configNode, createNode(c.getId(), family, c.getChildConfigs().stream(), resourcesBundle, 211 container, locale, idFilter, lightPayload)); 212 }); 213 } 214 215 private ConfigTypeNodes toNodes(final Locale locale, final boolean lightPayload, final Predicate<String> filter, 216 final Predicate<ConfigTypeNode> nodeFilter) { 217 return new ConfigTypeNodes(Stream 218 .concat(getDeployedConfigurations(filter, nodeFilter, lightPayload, locale), 219 virtualComponents 220 .getConfigurations() 221 .stream() 222 .filter(it -> filter.test(it.getId())) 223 .filter(nodeFilter) 224 .map(it -> lightPayload ? copyLight(it) : it)) 225 .collect(toMap(ConfigTypeNode::getId, identity()))); 226 } 227 228 private ConfigTypeNode copyLight(final ConfigTypeNode it) { 229 return new ConfigTypeNode(it.getId(), it.getVersion(), it.getParentId(), it.getConfigurationType(), 230 it.getName(), it.getDisplayName(), it.getEdges(), null, null); 231 } 232 233 private Stream<ConfigTypeNode> getDeployedConfigurations(final Predicate<String> filter, 234 final Predicate<ConfigTypeNode> nodeFilter, final boolean lightPayload, final Locale locale) { 235 return manager 236 .find(Stream::of) 237 .filter(c -> c.get(RepositoryModel.class) != null) 238 .flatMap(c -> c 239 .get(RepositoryModel.class) 240 .getFamilies() 241 .stream() 242 .filter(f -> !f.getConfigs().get().isEmpty()) 243 .flatMap(family -> { 244 final FamilyBundle resourcesBundle = family.getMeta().findBundle(c.getLoader(), locale); 245 246 final Stream<ConfigTypeNode> familyNode; 247 if (filter.test(family.getId())) { 248 final ConfigTypeNode node = new ConfigTypeNode(); 249 node.setId(family.getId()); 250 node.setName(family.getMeta().getName()); 251 252 node.setDisplayName(resourcesBundle.displayName().orElse(family.getMeta().getName())); 253 254 node.setEdges(family.getConfigs().get().stream().map(Config::getId).collect(toSet())); 255 familyNode = Stream.of(node); 256 } else { 257 familyNode = Stream.empty(); 258 } 259 return Stream 260 .concat(familyNode, 261 createNode(family.getId(), family.getMeta().getName(), 262 family.getConfigs().get().stream(), resourcesBundle, c, locale, 263 filter, lightPayload)); 264 })) 265 .filter(nodeFilter); 266 } 267}