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.Collections.emptyList; 019import static java.util.Optional.ofNullable; 020import static java.util.stream.Collectors.toList; 021import static java.util.stream.Collectors.toSet; 022import static java.util.stream.Stream.empty; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import java.util.Collection; 029import java.util.Date; 030import java.util.List; 031import java.util.Locale; 032import java.util.Properties; 033import java.util.function.Supplier; 034import java.util.stream.Stream; 035 036import javax.annotation.PostConstruct; 037import javax.annotation.PreDestroy; 038import javax.enterprise.context.ApplicationScoped; 039import javax.enterprise.context.Initialized; 040import javax.enterprise.event.Event; 041import javax.enterprise.event.Observes; 042import javax.enterprise.inject.Produces; 043import javax.inject.Inject; 044import javax.ws.rs.core.Context; 045import javax.ws.rs.core.UriInfo; 046 047import org.talend.sdk.component.container.Container; 048import org.talend.sdk.component.container.ContainerListener; 049import org.talend.sdk.component.dependencies.maven.Artifact; 050import org.talend.sdk.component.dependencies.maven.MvnCoordinateToFileConverter; 051import org.talend.sdk.component.design.extension.RepositoryModel; 052import org.talend.sdk.component.design.extension.repository.Config; 053import org.talend.sdk.component.path.PathFactory; 054import org.talend.sdk.component.runtime.manager.ComponentFamilyMeta; 055import org.talend.sdk.component.runtime.manager.ComponentManager; 056import org.talend.sdk.component.runtime.manager.ContainerComponentRegistry; 057import org.talend.sdk.component.server.configuration.ComponentServerConfiguration; 058import org.talend.sdk.component.server.dao.ComponentActionDao; 059import org.talend.sdk.component.server.dao.ComponentDao; 060import org.talend.sdk.component.server.dao.ComponentFamilyDao; 061import org.talend.sdk.component.server.dao.ConfigurationDao; 062import org.talend.sdk.component.server.service.event.DeployedComponent; 063 064import lombok.AllArgsConstructor; 065import lombok.Data; 066import lombok.extern.slf4j.Slf4j; 067 068@Slf4j 069@ApplicationScoped 070public class ComponentManagerService { 071 072 @Inject 073 private ComponentServerConfiguration configuration; 074 075 @Inject 076 private ComponentDao componentDao; 077 078 @Inject 079 private ComponentFamilyDao componentFamilyDao; 080 081 @Inject 082 private ComponentActionDao actionDao; 083 084 @Inject 085 private ConfigurationDao configurationDao; 086 087 @Inject 088 private VirtualDependenciesService virtualDependenciesService; 089 090 @Inject 091 private GlobService globService; 092 093 @Inject 094 private Event<DeployedComponent> deployedComponentEvent; 095 096 @Inject 097 @Context 098 private UriInfo uriInfo; 099 100 @Inject 101 private LocaleMapper localeMapper; 102 103 private ComponentManager instance; 104 105 private MvnCoordinateToFileConverter mvnCoordinateToFileConverter; 106 107 private DeploymentListener deploymentListener; 108 109 private volatile Date lastUpdated = new Date(); 110 111 private boolean started; 112 113 public void startupLoad(@Observes @Initialized(ApplicationScoped.class) final Object start) { 114 // no-op 115 } 116 117 @PostConstruct 118 private void init() { 119 if (log.isWarnEnabled()) { 120 final String filter = System.getProperty("jdk.serialFilter"); 121 if (filter == null) { 122 log.warn("No system property 'jdk.serialFilter', ensure it is intended"); 123 } 124 } 125 126 mvnCoordinateToFileConverter = new MvnCoordinateToFileConverter(); 127 final Path m2 = configuration 128 .getMavenRepository() 129 .map(PathFactory::get) 130 .filter(Files::exists) 131 .orElseGet(ComponentManager::findM2); 132 log.info("Using maven repository: '{}'", m2); 133 instance = new ComponentManager(m2) { 134 135 @Override 136 protected Supplier<Locale> getLocalSupplier() { 137 return ComponentManagerService.this::readCurrentLocale; 138 } 139 }; 140 deploymentListener = new DeploymentListener(componentDao, componentFamilyDao, actionDao, configurationDao, 141 virtualDependenciesService); 142 instance.getContainer().registerListener(deploymentListener); 143 144 // note: we don't want to download anything from the manager, if we need to download any artifact we need 145 // to ensure it is controlled (secured) and allowed so don't make it implicit but enforce a first phase 146 // where it is cached locally (provisioning solution) 147 final List<String> coords = configuration 148 .getComponentCoordinates() 149 .map(it -> Stream.of(it.split(",")).map(String::trim).filter(i -> !i.isEmpty()).collect(toList())) 150 .orElse(emptyList()); 151 coords.forEach(this::deploy); 152 configuration 153 .getComponentRegistry() 154 .map(Collection::stream) 155 .orElseGet(Stream::empty) 156 .flatMap(globService::toFiles) 157 .forEach(registry -> { 158 final Properties properties = new Properties(); 159 try (final InputStream is = Files.newInputStream(registry)) { 160 properties.load(is); 161 } catch (final IOException e) { 162 throw new IllegalArgumentException(e); 163 } 164 properties 165 .stringPropertyNames() 166 .stream() 167 .map(properties::getProperty) 168 .filter(gav -> !coords.contains(gav)) 169 .forEach(this::deploy); 170 }); 171 started = true; 172 } 173 174 private Locale readCurrentLocale() { 175 try { 176 return ofNullable(uriInfo.getQueryParameters().getFirst("lang")) 177 .map(localeMapper::mapLocale) 178 .orElseGet(Locale::getDefault); 179 } catch (final RuntimeException ex) { 180 log.debug("Can't get the locale from current request in thread '{}'", Thread.currentThread().getName(), ex); 181 return Locale.getDefault(); 182 } 183 } 184 185 @PreDestroy 186 private void destroy() { 187 started = false; 188 instance.getContainer().unregisterListener(deploymentListener); 189 instance.close(); 190 } 191 192 public String deploy(final String pluginGAV) { 193 final String pluginPath = ofNullable(pluginGAV) 194 .map(gav -> mvnCoordinateToFileConverter.toArtifact(gav)) 195 .map(Artifact::toPath) 196 .orElseThrow(() -> new IllegalArgumentException("Plugin GAV can't be empty")); 197 198 final Path m2 = instance.getContainer().getRootRepositoryLocationPath(); 199 final String plugin = 200 instance.addWithLocationPlugin(pluginGAV, m2.resolve(pluginPath).toAbsolutePath().toString()); 201 lastUpdated = new Date(); 202 if (started) { 203 deployedComponentEvent.fire(new DeployedComponent()); 204 } 205 return plugin; 206 } 207 208 public void undeploy(final String pluginGAV) { 209 if (pluginGAV == null || pluginGAV.isEmpty()) { 210 throw new IllegalArgumentException("plugin maven GAV are required to undeploy a plugin"); 211 } 212 213 String pluginID = instance 214 .find(c -> pluginGAV.equals(c.get(ComponentManager.OriginalId.class).getValue()) ? Stream.of(c.getId()) 215 : empty()) 216 .findFirst() 217 .orElseThrow(() -> new IllegalArgumentException("No plugin found using maven GAV: " + pluginGAV)); 218 219 instance.removePlugin(pluginID); 220 lastUpdated = new Date(); 221 } 222 223 public Date findLastUpdated() { 224 return lastUpdated; 225 } 226 227 @AllArgsConstructor 228 private static class DeploymentListener implements ContainerListener { 229 230 private final ComponentDao componentDao; 231 232 private final ComponentFamilyDao componentFamilyDao; 233 234 private final ComponentActionDao actionDao; 235 236 private final ConfigurationDao configurationDao; 237 238 private final VirtualDependenciesService virtualDependenciesService; 239 240 @Override 241 public void onCreate(final Container container) { 242 container.set(CleanupTask.class, new CleanupTask(postDeploy(container))); 243 } 244 245 @Override 246 public void onClose(final Container container) { 247 if (container.getState() == Container.State.ON_ERROR) { 248 // means it was not deployed so don't drop old state 249 return; 250 } 251 ofNullable(container.get(CleanupTask.class)).ifPresent(c -> c.getCleanup().run()); 252 } 253 254 private Runnable postDeploy(final Container plugin) { 255 final Collection<String> componentIds = plugin 256 .get(ContainerComponentRegistry.class) 257 .getComponents() 258 .values() 259 .stream() 260 .flatMap(c -> Stream 261 .concat(c.getPartitionMappers().values().stream(), c.getProcessors().values().stream())) 262 .peek(componentDao::createOrUpdate) 263 .map(ComponentFamilyMeta.BaseMeta::getId) 264 .collect(toSet()); 265 266 final Collection<ComponentActionDao.ActionKey> actions = plugin 267 .get(ContainerComponentRegistry.class) 268 .getServices() 269 .stream() 270 .flatMap(c -> c.getActions().stream()) 271 .map(actionDao::createOrUpdate) 272 .collect(toList()); 273 274 final Collection<String> families = plugin 275 .get(ContainerComponentRegistry.class) 276 .getComponents() 277 .values() 278 .stream() 279 .map(componentFamilyDao::createOrUpdate) 280 .collect(toList()); 281 282 final Collection<String> configs = ofNullable(plugin.get(RepositoryModel.class)) 283 .map(r -> r 284 .getFamilies() 285 .stream() 286 .flatMap(f -> configAsStream(f.getConfigs().get().stream())) 287 .collect(toList())) 288 .orElse(emptyList()) 289 .stream() 290 .map(configurationDao::createOrUpdate) 291 .collect(toList()); 292 293 return () -> { 294 virtualDependenciesService.onUnDeploy(plugin); 295 componentIds.forEach(componentDao::removeById); 296 actions.forEach(actionDao::removeById); 297 families.forEach(componentFamilyDao::removeById); 298 configs.forEach(configurationDao::removeById); 299 }; 300 } 301 302 private Stream<Config> configAsStream(final Stream<Config> stream) { 303 return stream.flatMap(s -> Stream.concat(Stream.of(s), s.getChildConfigs().stream())); 304 } 305 } 306 307 @Data 308 private static class CleanupTask { 309 310 private final Runnable cleanup; 311 } 312 313 @Produces 314 public ComponentManager manager() { 315 return instance; 316 } 317 318}