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; 017 018import static java.util.Arrays.asList; 019import static java.util.Collections.emptyList; 020import static java.util.Collections.emptyMap; 021import static java.util.Collections.list; 022import static java.util.Comparator.comparing; 023import static java.util.Locale.ROOT; 024import static java.util.Optional.of; 025import static java.util.Optional.ofNullable; 026import static java.util.function.Function.identity; 027import static java.util.stream.Collectors.joining; 028import static java.util.stream.Collectors.toList; 029import static java.util.stream.Collectors.toMap; 030import static java.util.stream.Collectors.toSet; 031import static org.apache.xbean.finder.archive.FileArchive.decode; 032import static org.talend.sdk.component.classloader.ConfigurableClassLoader.NESTED_MAVEN_REPOSITORY; 033import static org.talend.sdk.component.runtime.base.lang.exception.InvocationExceptionWrapper.toRuntimeException; 034import static org.talend.sdk.component.runtime.manager.ComponentManager.ComponentType.DRIVER_RUNNER; 035import static org.talend.sdk.component.runtime.manager.ComponentManager.ComponentType.MAPPER; 036import static org.talend.sdk.component.runtime.manager.ComponentManager.ComponentType.PROCESSOR; 037import static org.talend.sdk.component.runtime.manager.reflect.Constructors.findConstructor; 038import static org.talend.sdk.component.runtime.manager.util.Lazy.lazy; 039 040import java.io.File; 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.ObjectStreamException; 044import java.io.Serializable; 045import java.lang.annotation.Annotation; 046import java.lang.instrument.ClassFileTransformer; 047import java.lang.management.ManagementFactory; 048import java.lang.reflect.AnnotatedElement; 049import java.lang.reflect.Constructor; 050import java.lang.reflect.Executable; 051import java.lang.reflect.InvocationTargetException; 052import java.lang.reflect.Method; 053import java.lang.reflect.Modifier; 054import java.lang.reflect.Proxy; 055import java.net.MalformedURLException; 056import java.net.URL; 057import java.nio.file.Path; 058import java.util.ArrayList; 059import java.util.Collection; 060import java.util.Collections; 061import java.util.Enumeration; 062import java.util.HashMap; 063import java.util.Iterator; 064import java.util.List; 065import java.util.Locale; 066import java.util.Map; 067import java.util.Objects; 068import java.util.Optional; 069import java.util.Properties; 070import java.util.ServiceLoader; 071import java.util.Set; 072import java.util.Spliterator; 073import java.util.Spliterators; 074import java.util.concurrent.ConcurrentHashMap; 075import java.util.concurrent.atomic.AtomicBoolean; 076import java.util.concurrent.atomic.AtomicReference; 077import java.util.function.Consumer; 078import java.util.function.Function; 079import java.util.function.Predicate; 080import java.util.function.Supplier; 081import java.util.jar.JarInputStream; 082import java.util.logging.Level; 083import java.util.stream.Stream; 084import java.util.stream.StreamSupport; 085 086import javax.annotation.PostConstruct; 087import javax.annotation.PreDestroy; 088import javax.json.JsonArray; 089import javax.json.JsonBuilderFactory; 090import javax.json.JsonObject; 091import javax.json.JsonReaderFactory; 092import javax.json.JsonString; 093import javax.json.JsonValue; 094import javax.json.JsonWriterFactory; 095import javax.json.bind.Jsonb; 096import javax.json.bind.JsonbConfig; 097import javax.json.bind.config.BinaryDataStrategy; 098import javax.json.bind.spi.JsonbProvider; 099import javax.json.spi.JsonProvider; 100import javax.json.stream.JsonGeneratorFactory; 101import javax.json.stream.JsonParserFactory; 102 103import org.apache.xbean.asm9.Type; 104import org.apache.xbean.finder.AnnotationFinder; 105import org.apache.xbean.finder.ClassFinder; 106import org.apache.xbean.finder.archive.Archive; 107import org.apache.xbean.finder.archive.ClassesArchive; 108import org.apache.xbean.finder.archive.ClasspathArchive; 109import org.apache.xbean.finder.archive.CompositeArchive; 110import org.apache.xbean.finder.archive.FileArchive; 111import org.apache.xbean.finder.archive.FilteredArchive; 112import org.apache.xbean.finder.archive.JarArchive; 113import org.apache.xbean.finder.filter.ExcludeIncludeFilter; 114import org.apache.xbean.finder.filter.Filter; 115import org.apache.xbean.finder.filter.FilterList; 116import org.apache.xbean.finder.filter.Filters; 117import org.apache.xbean.finder.filter.IncludeExcludeFilter; 118import org.apache.xbean.finder.filter.PrefixFilter; 119import org.apache.xbean.finder.util.Files; 120import org.apache.xbean.propertyeditor.Converter; 121import org.talend.sdk.component.api.component.Components; 122import org.talend.sdk.component.api.component.Version; 123import org.talend.sdk.component.api.input.Emitter; 124import org.talend.sdk.component.api.input.PartitionMapper; 125import org.talend.sdk.component.api.internationalization.Internationalized; 126import org.talend.sdk.component.api.processor.AfterGroup; 127import org.talend.sdk.component.api.processor.Processor; 128import org.talend.sdk.component.api.service.ActionType; 129import org.talend.sdk.component.api.service.Service; 130import org.talend.sdk.component.api.service.configuration.LocalConfiguration; 131import org.talend.sdk.component.api.service.http.HttpClient; 132import org.talend.sdk.component.api.service.http.HttpClientFactory; 133import org.talend.sdk.component.api.service.http.Request; 134import org.talend.sdk.component.api.service.injector.Injector; 135import org.talend.sdk.component.api.service.record.RecordBuilderFactory; 136import org.talend.sdk.component.api.standalone.DriverRunner; 137import org.talend.sdk.component.classloader.ConfigurableClassLoader; 138import org.talend.sdk.component.classloader.ThreadHelper; 139import org.talend.sdk.component.container.Container; 140import org.talend.sdk.component.container.ContainerListener; 141import org.talend.sdk.component.container.ContainerManager; 142import org.talend.sdk.component.dependencies.EmptyResolver; 143import org.talend.sdk.component.dependencies.maven.Artifact; 144import org.talend.sdk.component.dependencies.maven.MvnDependencyListLocalRepositoryResolver; 145import org.talend.sdk.component.jmx.JmxManager; 146import org.talend.sdk.component.path.PathFactory; 147import org.talend.sdk.component.runtime.base.Delegated; 148import org.talend.sdk.component.runtime.base.Lifecycle; 149import org.talend.sdk.component.runtime.impl.Mode; 150import org.talend.sdk.component.runtime.input.CheckpointState; 151import org.talend.sdk.component.runtime.input.LocalPartitionMapper; 152import org.talend.sdk.component.runtime.input.Mapper; 153import org.talend.sdk.component.runtime.input.PartitionMapperImpl; 154import org.talend.sdk.component.runtime.internationalization.InternationalizationServiceFactory; 155import org.talend.sdk.component.runtime.manager.asm.ProxyGenerator; 156import org.talend.sdk.component.runtime.manager.builtinparams.MaxBatchSizeParamBuilder; 157import org.talend.sdk.component.runtime.manager.builtinparams.StreamingLongParamBuilder.StreamingMaxDurationMsParamBuilder; 158import org.talend.sdk.component.runtime.manager.builtinparams.StreamingLongParamBuilder.StreamingMaxRecordsParamBuilder; 159import org.talend.sdk.component.runtime.manager.extension.ComponentContextImpl; 160import org.talend.sdk.component.runtime.manager.extension.ComponentContexts; 161import org.talend.sdk.component.runtime.manager.json.TalendAccessMode; 162import org.talend.sdk.component.runtime.manager.proxy.JavaProxyEnricherFactory; 163import org.talend.sdk.component.runtime.manager.reflect.ComponentMetadataService; 164import org.talend.sdk.component.runtime.manager.reflect.IconFinder; 165import org.talend.sdk.component.runtime.manager.reflect.MigrationHandlerFactory; 166import org.talend.sdk.component.runtime.manager.reflect.ParameterModelService; 167import org.talend.sdk.component.runtime.manager.reflect.ReflectionService; 168import org.talend.sdk.component.runtime.manager.reflect.parameterenricher.BaseParameterEnricher; 169import org.talend.sdk.component.runtime.manager.service.DefaultServiceProvider; 170import org.talend.sdk.component.runtime.manager.service.MavenRepositoryDefaultResolver; 171import org.talend.sdk.component.runtime.manager.service.ServiceHelper; 172import org.talend.sdk.component.runtime.manager.service.api.ComponentInstantiator; 173import org.talend.sdk.component.runtime.manager.service.record.RecordBuilderFactoryProvider; 174import org.talend.sdk.component.runtime.manager.spi.ContainerListenerExtension; 175import org.talend.sdk.component.runtime.manager.util.Lazy; 176import org.talend.sdk.component.runtime.manager.util.LazyMap; 177import org.talend.sdk.component.runtime.manager.xbean.KnownClassesFilter; 178import org.talend.sdk.component.runtime.manager.xbean.NestedJarArchive; 179import org.talend.sdk.component.runtime.manager.xbean.registry.EnrichedPropertyEditorRegistry; 180import org.talend.sdk.component.runtime.output.ProcessorImpl; 181import org.talend.sdk.component.runtime.record.RecordBuilderFactoryImpl; 182import org.talend.sdk.component.runtime.serialization.LightContainer; 183import org.talend.sdk.component.runtime.standalone.DriverRunnerImpl; 184import org.talend.sdk.component.runtime.visitor.ModelListener; 185import org.talend.sdk.component.runtime.visitor.ModelVisitor; 186import org.talend.sdk.component.spi.component.ComponentExtension; 187import org.talend.sdk.component.spi.component.GenericComponentExtension; 188 189import lombok.AllArgsConstructor; 190import lombok.Data; 191import lombok.Getter; 192import lombok.RequiredArgsConstructor; 193import lombok.extern.slf4j.Slf4j; 194 195@Slf4j 196public class ComponentManager implements AutoCloseable { 197 198 private static class SingletonHolder { 199 200 protected static final AtomicReference<ComponentManager> CONTEXTUAL_INSTANCE = new AtomicReference<>(); 201 202 private static ComponentManager buildNewComponentManager() { 203 final Thread shutdownHook = SingletonHolder.buildShutDownHook(); 204 ComponentManager componentManager = new ComponentManager(findM2()) { 205 206 private final AtomicBoolean closed = new AtomicBoolean(false); 207 208 { 209 info("ComponentManager version: " + ComponentManagerVersion.VERSION); 210 info("Creating the contextual ComponentManager instance " + getIdentifiers()); 211 212 parallelIf(Boolean.getBoolean("talend.component.manager.plugins.parallel"), 213 container.getDefinedNestedPlugin().stream().filter(p -> !hasPlugin(p))) 214 .forEach(this::addPlugin); 215 info("Components: " + availablePlugins()); 216 } 217 218 @Override 219 public void close() { 220 log.debug("Closing ComponentManager."); 221 if (!closed.compareAndSet(false, true)) { 222 log.debug("ComponentManager already closed"); 223 return; 224 } 225 try { 226 synchronized (CONTEXTUAL_INSTANCE) { 227 if (CONTEXTUAL_INSTANCE.compareAndSet(this, null)) { 228 try { 229 log.debug("ComponentManager : remove shutdown hook"); 230 Runtime.getRuntime().removeShutdownHook(shutdownHook); 231 } catch (final IllegalStateException ise) { 232 // already shutting down 233 } 234 } 235 } 236 } finally { 237 CONTEXTUAL_INSTANCE.set(null); 238 super.close(); 239 info("Released the contextual ComponentManager instance " + getIdentifiers()); 240 } 241 } 242 243 Object readResolve() throws ObjectStreamException { 244 return new SerializationReplacer(); 245 } 246 }; 247 248 try { 249 Runtime.getRuntime().addShutdownHook(shutdownHook); 250 } catch (IllegalStateException e) { 251 log.warn("addShutdownHook: Shutdown already in progress."); 252 } 253 componentManager.info("Created the contextual ComponentManager instance " + getIdentifiers()); 254 if (!CONTEXTUAL_INSTANCE.compareAndSet(null, componentManager)) { // unlikely it fails in a synch block 255 componentManager = CONTEXTUAL_INSTANCE.get(); 256 } 257 return componentManager; 258 } 259 260 private static synchronized ComponentManager renew(final ComponentManager current) { 261 final ComponentManager manager; 262 if (current == null) { 263 log.info("rebuild new component manager"); 264 manager = SingletonHolder.buildNewComponentManager(); 265 } else { 266 manager = current; 267 } 268 return manager; 269 } 270 271 private static final Thread buildShutDownHook() { 272 return new Thread(ComponentManager.class.getName() + "-" + ComponentManager.class.hashCode()) { 273 274 @Override 275 public void run() { 276 ofNullable(CONTEXTUAL_INSTANCE.get()).ifPresent(ComponentManager::close); 277 } 278 }; 279 } 280 281 static { 282 ComponentManager manager = SingletonHolder.buildNewComponentManager(); 283 } 284 285 } 286 287 private static final Components DEFAULT_COMPONENT = new Components() { 288 289 @Override 290 public Class<? extends Annotation> annotationType() { 291 return Components.class; 292 } 293 294 @Override 295 public String family() { 296 return ""; 297 } 298 299 @Override 300 public String[] categories() { 301 return new String[] { "Misc" }; 302 } 303 }; 304 305 @Getter 306 protected final ContainerManager container; 307 308 // tcomp (org.talend + javax.annotation + jsonp) + logging (slf4j) are/can be provided service 309 // + tcomp "runtime" indeed (invisible from the components but required for the runtime 310 private final Filter classesFilter; 311 312 private final ParameterModelService parameterModelService; 313 314 private final InternationalizationServiceFactory internationalizationServiceFactory; 315 316 @Getter 317 private final JsonProvider jsonpProvider; 318 319 @Getter 320 private final JsonGeneratorFactory jsonpGeneratorFactory; 321 322 @Getter 323 private final JsonReaderFactory jsonpReaderFactory; 324 325 @Getter 326 private final JsonBuilderFactory jsonpBuilderFactory; 327 328 @Getter 329 private final JsonParserFactory jsonpParserFactory; 330 331 @Getter 332 private final JsonWriterFactory jsonpWriterFactory; 333 334 @Getter 335 private final JsonbProvider jsonbProvider; 336 337 private final ProxyGenerator proxyGenerator = new ProxyGenerator(); 338 339 private final JavaProxyEnricherFactory javaProxyEnricherFactory = new JavaProxyEnricherFactory(); 340 341 // kind of extracted to ensure we can switch it later if needed 342 // (like using a Spring/CDI context and something else than xbean) 343 private final ReflectionService reflections; 344 345 @Getter // for extensions 346 private final MigrationHandlerFactory migrationHandlerFactory; 347 348 private final Collection<ComponentExtension> extensions; 349 350 private final Collection<ClassFileTransformer> transformers; 351 352 private final Collection<LocalConfiguration> localConfigurations; 353 354 // org.slf4j.event but https://issues.apache.org/jira/browse/MNG-6360 355 private final Level logInfoLevelMapping; 356 357 @Getter // use with caution 358 private final List<Customizer> customizers; 359 360 @Getter 361 private final Function<String, RecordBuilderFactory> recordBuilderFactoryProvider; 362 363 @Getter 364 private final JsonbConfig jsonbConfig = new JsonbConfig() 365 .withBinaryDataStrategy(BinaryDataStrategy.BASE_64) 366 .setProperty("johnzon.cdi.activated", false) 367 .setProperty("johnzon.accessModeDelegate", new TalendAccessMode()); 368 369 private final EnrichedPropertyEditorRegistry propertyEditorRegistry; 370 371 private final List<ContainerClasspathContributor> classpathContributors; 372 373 private final IconFinder iconFinder = new IconFinder(); 374 375 private final DefaultServiceProvider defaultServiceProvider; 376 377 public ComponentManager(final File m2) { 378 this(m2.toPath()); 379 } 380 381 public ComponentManager(final File m2, final String dependenciesResource, final String jmxNamePattern) { 382 this(m2.toPath(), dependenciesResource, jmxNamePattern); 383 } 384 385 public ComponentManager(final Path m2) { 386 this(m2, "TALEND-INF/dependencies.txt", "org.talend.sdk.component:type=component,value=%s"); 387 } 388 389 /** 390 * @param m2 the maven repository location if on the file system. 391 * @param dependenciesResource the resource path containing dependencies. 392 * @param jmxNamePattern a pattern to register the plugins (containers) in JMX, null 393 * otherwise. 394 */ 395 public ComponentManager(final Path m2, final String dependenciesResource, final String jmxNamePattern) { 396 final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); 397 398 internationalizationServiceFactory = new InternationalizationServiceFactory(getLocalSupplier()); 399 customizers = toStream(loadServiceProviders(Customizer.class, tccl)).collect(toList()); // must stay first 400 if (!customizers.isEmpty()) { 401 customizers.forEach(c -> c.setCustomizers(customizers)); 402 } 403 if (!Boolean.getBoolean("talend.component.manager.classpathcontributor.skip")) { 404 classpathContributors = 405 toStream(loadServiceProviders(ContainerClasspathContributor.class, tccl)).collect(toList()); 406 } else { 407 classpathContributors = emptyList(); 408 } 409 classesFilter = new FilterList(Stream 410 .concat(Stream 411 .of("org.talend.sdk.component.api.", "org.talend.sdk.component.spi.", "javax.annotation.", 412 "javax.json.", "org.talend.sdk.component.classloader.", 413 "org.talend.sdk.component.runtime.", "org.slf4j.", "org.apache.johnzon."), 414 additionalContainerClasses()) 415 .distinct() 416 .map(PrefixFilter::new) 417 .toArray(Filter[]::new)); 418 419 jsonpProvider = loadJsonProvider(); 420 jsonbProvider = loadJsonbProvider(); 421 // these factories have memory caches so ensure we reuse them properly 422 jsonpGeneratorFactory = JsonGeneratorFactory.class 423 .cast(javaProxyEnricherFactory 424 .asSerializable(tccl, null, JsonGeneratorFactory.class.getName(), 425 jsonpProvider.createGeneratorFactory(emptyMap()))); 426 jsonpReaderFactory = JsonReaderFactory.class 427 .cast(javaProxyEnricherFactory 428 .asSerializable(tccl, null, JsonReaderFactory.class.getName(), 429 jsonpProvider.createReaderFactory(emptyMap()))); 430 jsonpBuilderFactory = JsonBuilderFactory.class 431 .cast(javaProxyEnricherFactory 432 .asSerializable(tccl, null, JsonBuilderFactory.class.getName(), 433 jsonpProvider.createBuilderFactory(emptyMap()))); 434 jsonpParserFactory = JsonParserFactory.class 435 .cast(javaProxyEnricherFactory 436 .asSerializable(tccl, null, JsonParserFactory.class.getName(), 437 jsonpProvider.createParserFactory(emptyMap()))); 438 jsonpWriterFactory = JsonWriterFactory.class 439 .cast(javaProxyEnricherFactory 440 .asSerializable(tccl, null, JsonWriterFactory.class.getName(), 441 jsonpProvider.createWriterFactory(emptyMap()))); 442 443 logInfoLevelMapping = findLogInfoLevel(); 444 445 propertyEditorRegistry = createPropertyEditorRegistry(); 446 localConfigurations = createRawLocalConfigurations(); 447 parameterModelService = new ParameterModelService(propertyEditorRegistry); 448 reflections = new ReflectionService(parameterModelService, propertyEditorRegistry); 449 migrationHandlerFactory = new MigrationHandlerFactory(reflections); 450 451 final Predicate<String> isContainerClass = name -> isContainerClass(classesFilter, name); 452 final ContainerManager.ClassLoaderConfiguration defaultClassLoaderConfiguration = 453 ContainerManager.ClassLoaderConfiguration 454 .builder() 455 .parent(tccl) 456 .parentClassesFilter(isContainerClass) 457 .classesFilter(isContainerClass.negate()) 458 .supportsResourceDependencies(true) 459 .create(); 460 this.container = new ContainerManager(ContainerManager.DependenciesResolutionConfiguration 461 .builder() 462 .resolver(customizers.stream().noneMatch(Customizer::ignoreDefaultDependenciesDescriptor) 463 ? new MvnDependencyListLocalRepositoryResolver(dependenciesResource, this::resolve) 464 : new EmptyResolver()) 465 .rootRepositoryLocation(m2) 466 .create(), defaultClassLoaderConfiguration, container -> { 467 }, logInfoLevelMapping) { 468 469 @Override 470 public Path resolve(final String path) { 471 return classpathContributors 472 .stream() 473 .filter(it -> it.canResolve(path)) 474 .map(it -> it.resolve(path)) 475 .filter(Objects::nonNull) 476 .findFirst() 477 .orElseGet(() -> super.resolve(path)); 478 } 479 }; 480 this.container.registerListener(new Updater(dependenciesResource)); 481 if (!Boolean.getBoolean("talend.component.manager.jmx.skip")) { 482 ofNullable(jmxNamePattern) 483 .map(String::trim) 484 .filter(n -> !n.isEmpty()) 485 .ifPresent(p -> this.container 486 .registerListener( 487 new JmxManager(container, p, ManagementFactory.getPlatformMBeanServer()))); 488 } 489 toStream(loadServiceProviders(ContainerListenerExtension.class, tccl)) 490 .peek(e -> e.setComponentManager(ComponentManager.this)) 491 .sorted(comparing(ContainerListenerExtension::order)) 492 .forEach(container::registerListener); 493 this.extensions = toStream(loadServiceProviders(ComponentExtension.class, tccl)) 494 .filter(ComponentExtension::isActive) 495 .sorted(comparing(ComponentExtension::priority)) 496 .collect(toList()); 497 this.transformers = extensions.stream().flatMap(e -> e.getTransformers().stream()).collect(toList()); 498 499 final Iterator<RecordBuilderFactoryProvider> recordBuilderFactoryIterator = 500 ServiceLoader.load(RecordBuilderFactoryProvider.class, tccl).iterator(); 501 if (recordBuilderFactoryIterator.hasNext()) { 502 final RecordBuilderFactoryProvider factory = recordBuilderFactoryIterator.next(); 503 recordBuilderFactoryProvider = factory::apply; 504 if (recordBuilderFactoryIterator.hasNext()) { 505 throw new IllegalArgumentException( 506 "Ambiguous recordBuilderFactory: " + factory + "/" + recordBuilderFactoryIterator.next()); 507 } 508 } else { 509 recordBuilderFactoryProvider = RecordBuilderFactoryImpl::new; 510 } 511 512 this.defaultServiceProvider = new DefaultServiceProvider(reflections, jsonpProvider, jsonpGeneratorFactory, 513 jsonpReaderFactory, jsonpBuilderFactory, jsonpParserFactory, jsonpWriterFactory, jsonbConfig, 514 jsonbProvider, proxyGenerator, javaProxyEnricherFactory, localConfigurations, 515 recordBuilderFactoryProvider, propertyEditorRegistry); 516 } 517 518 private JsonbProvider loadJsonbProvider() { 519 try { 520 return new org.apache.johnzon.jsonb.JohnzonProvider(); 521 } catch (final RuntimeException re) { 522 return JsonbProvider.provider(); 523 } 524 } 525 526 private JsonProvider loadJsonProvider() { 527 try { 528 return new org.apache.johnzon.core.JsonProviderImpl(); 529 } catch (final RuntimeException re) { 530 return JsonProvider.provider(); 531 } 532 } 533 534 protected Supplier<Locale> getLocalSupplier() { 535 return Locale::getDefault; 536 } 537 538 private Path resolve(final String artifact) { 539 return container.resolve(artifact); 540 } 541 542 private EnrichedPropertyEditorRegistry createPropertyEditorRegistry() { 543 return new EnrichedPropertyEditorRegistry(); 544 } 545 546 private Level findLogInfoLevel() { 547 if (Boolean.getBoolean("talend.component.manager.log.info")) { 548 return Level.INFO; 549 } 550 try { 551 ComponentManager.class.getClassLoader().loadClass("routines.TalendString"); 552 return Level.FINE; 553 } catch (final NoClassDefFoundError | ClassNotFoundException e) { 554 return Level.INFO; 555 } 556 } 557 558 /** 559 * Creates a default manager with default maven local repository, 560 * TALEND-INF/dependencies.txt file to find the dependencies of the plugins and 561 * a default JMX pattern for plugins. It also adds the caller as a plugin. 562 * 563 * @return the contextual manager instance. 564 */ 565 public static ComponentManager instance() { 566 return SingletonHolder.CONTEXTUAL_INSTANCE.updateAndGet(SingletonHolder::renew); 567 } 568 569 /** 570 * For test purpose only. 571 * 572 * @return the contextual instance 573 */ 574 protected static AtomicReference<ComponentManager> contextualInstance() { 575 return SingletonHolder.CONTEXTUAL_INSTANCE; 576 } 577 578 private static <T> Stream<T> parallelIf(final boolean condition, final Stream<T> stringStream) { 579 return condition ? stringStream.parallel() : stringStream; 580 } 581 582 protected void info(final String msg) { 583 switch (logInfoLevelMapping.intValue()) { 584 case 500: // FINE 585 log.debug(msg); 586 break; 587 case 800: // INFo 588 default: 589 log.info(msg); 590 } 591 } 592 593 private Stream<String> additionalContainerClasses() { 594 return Stream 595 .concat(customizers.stream().flatMap(Customizer::containerClassesAndPackages), 596 ofNullable( 597 System.getProperty("talend.component.manager.classloader.container.classesAndPackages")) 598 .map(s -> s.split(",")) 599 .map(Stream::of) 600 .orElseGet(Stream::empty)); 601 } 602 603 public static Path findM2() { 604 return new MavenRepositoryDefaultResolver().discover(); 605 } 606 607 private static String getIdentifiers() { 608 return "(classloader=" + ComponentManager.class.getClassLoader() + ", jvm=" 609 + ManagementFactory.getRuntimeMXBean().getName() + ")"; 610 } 611 612 private static <T> Stream<T> toStream(final Iterator<T> iterator) { 613 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.IMMUTABLE), false); 614 } 615 616 private static <T> Iterator<T> loadServiceProviders(final Class<T> service, final ClassLoader classLoader) { 617 return ServiceLoader.load(service, classLoader).iterator(); 618 } 619 620 private static Path toFile(final String classFileName, final URL url) { 621 String path = url.getFile(); 622 path = path.substring(0, path.length() - classFileName.length()); 623 return PathFactory.get(decode(path)); 624 } 625 626 public void addCallerAsPlugin() { 627 try { 628 final ClassLoader tmpLoader = ofNullable(Thread.currentThread().getContextClassLoader()) 629 .orElseGet(ClassLoader::getSystemClassLoader); 630 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 631 final Class<?> jarMarker = tmpLoader 632 .loadClass(Stream 633 .of(stackTrace) 634 .filter(c -> !c.getClassName().startsWith("org.talend.sdk.component.runtime.manager.") 635 && !c.getClassName().startsWith("org.talend.sdk.component.runtime.beam.dsl.") 636 && !c.getClassName().startsWith("org.talend.sdk.component.runtime.standalone.") 637 && !c.getClassName().startsWith("org.talend.sdk.component.runtime.avro.") 638 && !c.getClassName().startsWith("org.talend.daikon.") 639 && !c.getClassName().startsWith("org.talend.designer.") 640 && !c.getClassName().startsWith("org.eclipse.") 641 && !c.getClassName().startsWith("java.") && !c.getClassName().startsWith("javax.") 642 && !c.getClassName().startsWith("sun.") && !c.getClassName().startsWith("com.sun.") 643 && !c.getClassName().startsWith("com.oracle.")) 644 .findFirst() 645 .map(StackTraceElement::getClassName) 646 .orElse(ComponentManager.class.getName())); 647 if (jarMarker == ComponentManager.class) { 648 return; 649 } 650 651 addJarContaining(tmpLoader, jarMarker.getName().replace(".", "/") + ".class"); 652 } catch (final ClassNotFoundException e) { 653 // ignore, not important. if we can't do it then the plugins should be 654 // registered normally 655 } 656 } 657 658 protected List<String> addJarContaining(final ClassLoader loader, final String resource) { 659 final URL url = loader.getResource(resource); 660 if (url != null) { 661 Path plugin = null; 662 switch (url.getProtocol()) { 663 case "bundleresource": // studio on equinox, this is the definition part so we don't register it 664 break; 665 case "file": 666 plugin = toFile(resource, url); 667 break; 668 case "jar": 669 if (url.getPath() != null && url.getPath().startsWith("mvn:")) { // pax mvn 670 // studio temporary integration, to drop when studio integrates correctly tcomp 671 break; 672 } 673 final String spec = url.getFile(); 674 final int separator = spec.indexOf('!'); 675 if (separator > 0) { 676 try { 677 plugin = PathFactory.get(decode(new URL(spec.substring(0, separator)).getFile())); 678 } catch (final MalformedURLException e) { 679 // no-op 680 } 681 } 682 break; 683 default: 684 } 685 if (plugin == null) { 686 log.warn("Can't find " + url); 687 return null; 688 } 689 return Stream 690 .of(plugin) 691 // just a small workaround for maven/gradle 692 .flatMap(this::toPluginLocations) 693 .filter(path -> !container.find(path.getFileName().toString()).isPresent()) 694 .map(file -> { 695 final String id = addPlugin(file.toAbsolutePath().toString()); 696 if (container.find(id).get().get(ContainerComponentRegistry.class).getComponents().isEmpty()) { 697 removePlugin(id); 698 return null; 699 } 700 return id; 701 }) 702 .filter(Objects::nonNull) 703 .collect(toList()); 704 } 705 return emptyList(); 706 } 707 708 private Stream<Path> toPluginLocations(final Path src) { 709 final String filename = src.getFileName().toString(); 710 if ("test-classes".equals(filename) && src.getParent() != null) { // maven 711 return Stream.of(src.getParent().resolve("classes"), src); 712 } 713 714 // gradle (v3 & v4) 715 if ("classes".equals(filename) && src.getParent() != null 716 && "test".equals(src.getParent().getFileName().toString()) && src.getParent().getParent() != null) { 717 return Stream 718 .of(src.getParent().getParent().resolve("production/classes"), src) 719 .filter(java.nio.file.Files::exists); 720 } 721 if ("test".equals(filename) && src.getParent() != null 722 && "java".equals(src.getParent().getFileName().toString())) { 723 return Stream.of(src.getParent().resolve("main"), src).filter(java.nio.file.Files::exists); 724 } 725 726 return Stream.of(src); 727 } 728 729 public Map<String, String> mergeCheckpointConfiguration(final String plugin, final String name, 730 final ComponentType componentType, final Map<String, String> configuration) { 731 if (!MAPPER.equals(componentType)) { 732 return configuration; 733 } 734 if (!Boolean.parseBoolean(System.getProperty("talend.checkpoint.enabled", "false"))) { 735 return configuration; 736 } 737 final ParameterMeta checkpoint = findCheckpointParameterMeta(plugin, name); 738 if (checkpoint == null) { 739 return configuration; 740 } 741 return replaceKeys(configuration, CheckpointState.CHECKPOINT_KEY, checkpoint.getPath()); 742 } 743 744 public ParameterMeta findConfigurationType(final String plugin, final String name, final String configurationType) { 745 return container 746 .findAll() 747 .stream() 748 .map(c -> c.get(ContainerComponentRegistry.class)) 749 .map(registry -> registry.findComponentFamily(plugin)) 750 .filter(Objects::nonNull) 751 .map(family -> family.getPartitionMappers().get(name).getParameterMetas().get()) 752 .filter(Objects::nonNull) 753 .flatMap(List::stream) 754 .flatMap(np -> np.getNestedParameters().stream()) 755 .filter(m -> configurationType 756 .equals(m.getMetadata().getOrDefault("tcomp::configurationtype::type", ""))) 757 .findFirst() 758 .orElse(null); 759 } 760 761 public ParameterMeta findCheckpointParameterMeta(final String plugin, final String name) { 762 return findConfigurationType(plugin, name, "checkpoint"); 763 } 764 765 public ParameterMeta findDatasetParameterMeta(final String plugin, final String name) { 766 return findConfigurationType(plugin, name, "dataset"); 767 } 768 769 public ParameterMeta findDatastoreParameterMeta(final String plugin, final String name) { 770 return findConfigurationType(plugin, name, "datastore"); 771 } 772 773 /** 774 * Convert a json value to a configuration map. 775 * 776 * @param jsonValue json value to convert to a configuration map 777 * @param path optional path to add to keys as prefix 778 * @return a configuration map from a json value 779 */ 780 public static Map<String, String> jsonToMap(final JsonValue jsonValue, final String path) { 781 final Map<String, String> result = new HashMap<>(); 782 if (jsonValue instanceof JsonObject) { 783 JsonObject jsonObj = (JsonObject) jsonValue; 784 for (String key : jsonObj.keySet()) { 785 String newPath = path.isEmpty() ? key : path + "." + key; 786 result.putAll(jsonToMap(jsonObj.get(key), newPath)); 787 } 788 } else if (jsonValue instanceof JsonArray) { 789 JsonArray jsonArray = (JsonArray) jsonValue; 790 for (int i = 0; i < jsonArray.size(); i++) { 791 String newPath = path + "[" + i + "]"; 792 result.putAll(jsonToMap(jsonArray.get(i), newPath)); 793 } 794 } else { 795 String str; 796 if (jsonValue.getValueType() == JsonValue.ValueType.STRING) { 797 str = ((JsonString) (jsonValue)).getString(); 798 } else { 799 str = jsonValue.toString(); 800 } 801 result.put(path, str); 802 } 803 return result; 804 } 805 806 /** 807 * Convert a json value to a configuration map. 808 * 809 * @param jsonValue json value to convert to a configuration map 810 * @return a configuration map from a json value 811 */ 812 public static Map<String, String> jsonToMap(final JsonValue jsonValue) { 813 return jsonToMap(jsonValue, ""); 814 } 815 816 /** 817 * Replace some keys in the configuration map. 818 * 819 * @param configuration original configuration 820 * @param oldPrefix old prefix to replace 821 * @param newPrefix new prefix to replace with 822 * @return Map with keys replaced 823 */ 824 public static Map<String, String> replaceKeys(final Map<String, String> configuration, final String oldPrefix, 825 final String newPrefix) { 826 final Map<String, String> replaced = new HashMap<>(); 827 for (Map.Entry<String, String> entry : configuration.entrySet()) { 828 String key = entry.getKey(); 829 String value = entry.getValue(); 830 if (key.startsWith(oldPrefix)) { 831 String newKey = newPrefix + key.substring(oldPrefix.length()); 832 replaced.put(newKey, value); 833 } else { 834 replaced.put(key, value); 835 } 836 } 837 return replaced; 838 } 839 840 public <T> Stream<T> find(final Function<Container, Stream<T>> mapper) { 841 return container.findAll().stream().flatMap(mapper); 842 } 843 844 // really a DIY entry point for custom flow, it creates component instances but 845 // nothing more 846 public Optional<Object> createComponent(final String plugin, final String name, final ComponentType componentType, 847 final int version, final Map<String, String> configuration) { 848 return findComponentInternal(plugin, name, componentType, version, configuration) 849 // unwrap to access the actual instance which is the desired one 850 .map(i -> Delegated.class.isInstance(i) ? Delegated.class.cast(i).getDelegate() : i); 851 } 852 853 private Optional<Object> findComponentInternal(final String plugin, final String name, 854 final ComponentType componentType, final int version, final Map<String, String> configuration) { 855 if (container.findAll().isEmpty()) { 856 autoDiscoverPlugins(false, true); 857 } 858 final Map<String, String> conf = mergeCheckpointConfiguration(plugin, name, componentType, configuration); 859 return find(pluginContainer -> Stream 860 .of(findInstance(plugin, name, componentType, version, conf, pluginContainer))) 861 .filter(Objects::nonNull) 862 .findFirst(); 863 } 864 865 public void autoDiscoverPlugins(final boolean callers, final boolean classpath) { 866 if (callers && !Boolean.getBoolean("component.manager.callers.skip")) { 867 addCallerAsPlugin(); 868 } 869 870 // common for studio until job generation is updated to build a tcomp friendly bundle 871 if (classpath && !Boolean.getBoolean("component.manager.classpath.skip")) { 872 try { 873 final Enumeration<URL> componentMarkers = 874 Thread.currentThread().getContextClassLoader().getResources("TALEND-INF/dependencies.txt"); 875 while (componentMarkers.hasMoreElements()) { 876 File file = Files.toFile(componentMarkers.nextElement()); 877 if (file.getName().equals("dependencies.txt") && file.getParentFile() != null 878 && file.getParentFile().getName().equals("TALEND-INF")) { 879 file = file.getParentFile().getParentFile(); 880 } 881 if (!hasPlugin(container.buildAutoIdFromName(file.getName()))) { 882 addPlugin(file.getAbsolutePath()); 883 } 884 } 885 } catch (final IOException e) { 886 // no-op 887 } 888 } 889 } 890 891 private Object findInstance(final String plugin, final String name, final ComponentType componentType, 892 final int version, final Map<String, String> configuration, final Container pluginContainer) { 893 return findGenericInstance(plugin, name, componentType, version, configuration, pluginContainer) 894 .orElseGet( 895 () -> findDeployedInstance(plugin, name, componentType, version, configuration, pluginContainer) 896 .orElse(null)); 897 } 898 899 private Optional<Object> findDeployedInstance(final String plugin, final String name, 900 final ComponentType componentType, final int version, final Map<String, String> configuration, 901 final Container pluginContainer) { 902 903 final String pluginIdentifier = container.buildAutoIdFromName(plugin); 904 905 final ComponentInstantiator.Builder builder = new ComponentInstantiator.BuilderDefault( 906 () -> Stream.of(pluginContainer.get(ContainerComponentRegistry.class))); 907 return ofNullable( 908 builder.build(pluginIdentifier, ComponentInstantiator.MetaFinder.ofComponent(name), componentType)) 909 .map((ComponentInstantiator instantiator) -> instantiator.instantiate(configuration, version)); 910 } 911 912 private Optional<Object> findGenericInstance(final String plugin, final String name, 913 final ComponentType componentType, final int version, final Map<String, String> configuration, 914 final Container pluginContainer) { 915 return ofNullable(pluginContainer.get(GenericComponentExtension.class)) 916 .filter(ext -> ext.canHandle(componentType.runtimeType(), plugin, name)) 917 .map(ext -> Object.class 918 .cast(ext 919 .createInstance(componentType.runtimeType(), plugin, name, version, configuration, 920 ofNullable(pluginContainer.get(AllServices.class)) 921 .map(AllServices::getServices) 922 .orElseGet(Collections::emptyMap)))); 923 } 924 925 public Optional<Mapper> findMapper(final String plugin, final String name, final int version, 926 final Map<String, String> configuration) { 927 return findComponentInternal(plugin, name, MAPPER, version, configuration).map(Mapper.class::cast); 928 } 929 930 public Optional<org.talend.sdk.component.runtime.standalone.DriverRunner> findDriverRunner(final String plugin, 931 final String name, final int version, final Map<String, String> configuration) { 932 return findComponentInternal(plugin, name, DRIVER_RUNNER, version, configuration) 933 .map(org.talend.sdk.component.runtime.standalone.DriverRunner.class::cast); 934 } 935 936 public Optional<org.talend.sdk.component.runtime.output.Processor> findProcessor(final String plugin, 937 final String name, final int version, final Map<String, String> configuration) { 938 return findComponentInternal(plugin, name, PROCESSOR, version, configuration) 939 .map(org.talend.sdk.component.runtime.output.Processor.class::cast); 940 } 941 942 public boolean hasPlugin(final String plugin) { 943 return container.find(plugin).isPresent(); 944 } 945 946 public Optional<Container> findPlugin(final String plugin) { 947 return container.find(plugin); 948 } 949 950 public synchronized String addPlugin(final String pluginRootFile) { 951 final Optional<Container> pl = findPlugin(pluginRootFile); 952 if (pl.isPresent()) { 953 return pl.get().getId(); 954 } 955 final String id = this.container 956 .builder(pluginRootFile) 957 .withCustomizer(createContainerCustomizer(pluginRootFile)) 958 .withAdditionalClasspath(findAdditionalClasspathFor(container.buildAutoIdFromName(pluginRootFile))) 959 .create() 960 .getId(); 961 info("Adding plugin: " + pluginRootFile + ", as " + id); 962 return id; 963 } 964 965 public String addWithLocationPlugin(final String location, final String pluginRootFile) { 966 final String id = this.container 967 .builder(pluginRootFile) 968 .withCustomizer(createContainerCustomizer(location)) 969 .withAdditionalClasspath(findAdditionalClasspathFor(container.buildAutoIdFromName(location))) 970 .create() 971 .getId(); 972 info("Adding plugin: " + pluginRootFile + ", as " + id); 973 return id; 974 } 975 976 protected String addPlugin(final String forcedId, final String pluginRootFile) { 977 final String id = this.container 978 .builder(forcedId, pluginRootFile) 979 .withCustomizer(createContainerCustomizer(forcedId)) 980 .withAdditionalClasspath(findAdditionalClasspathFor(forcedId)) 981 .create() 982 .getId(); 983 info("Adding plugin: " + pluginRootFile + ", as " + id); 984 return id; 985 } 986 987 private Collection<Artifact> findAdditionalClasspathFor(final String pluginId) { 988 return classpathContributors 989 .stream() 990 .flatMap(it -> it.findContributions(pluginId).stream()) 991 .distinct() 992 .collect(toList())/* keep order */; 993 } 994 995 public void removePlugin(final String id) { 996 container.find(id).ifPresent(Container::close); 997 info("Removed plugin: " + id); 998 } 999 1000 protected boolean isContainerClass(final Filter filter, final String name) { 1001 return name != null && filter.accept(name); 1002 } 1003 1004 @Override 1005 public void close() { 1006 container.close(); 1007 propertyEditorRegistry.close(); 1008 } 1009 1010 private Consumer<Container> createContainerCustomizer(final String originalId) { 1011 return c -> { 1012 c.set(OriginalId.class, new OriginalId(originalId)); 1013 transformers.forEach(c::registerTransformer); 1014 }; 1015 } 1016 1017 private <T> T executeInContainer(final String plugin, final Supplier<T> supplier) { 1018 final Thread thread = Thread.currentThread(); 1019 final ClassLoader old = thread.getContextClassLoader(); 1020 thread 1021 .setContextClassLoader( 1022 container.find(plugin).map(Container::getLoader).map(ClassLoader.class::cast).orElse(old)); 1023 try { 1024 return supplier.get(); 1025 } finally { 1026 thread.setContextClassLoader(old); 1027 } 1028 } 1029 1030 public List<String> availablePlugins() { 1031 return container.findAll().stream().map(Container::getId).collect(toList()); 1032 } 1033 1034 protected void containerServices(final Container container, final Map<Class<?>, Object> services) { 1035 // no-op 1036 } 1037 1038 protected static Collection<LocalConfiguration> createRawLocalConfigurations() { 1039 final List<LocalConfiguration> configurations = new ArrayList<>(2); 1040 if (!Boolean.getBoolean("talend.component.manager.localconfiguration.skip")) { 1041 configurations 1042 .addAll(toStream( 1043 loadServiceProviders(LocalConfiguration.class, LocalConfiguration.class.getClassLoader())) 1044 .collect(toList())); 1045 } 1046 configurations.addAll(asList(new LocalConfiguration() { 1047 1048 @Override 1049 public String get(final String key) { 1050 return System.getProperty(key); 1051 } 1052 1053 @Override 1054 public Set<String> keys() { 1055 return System.getProperties().stringPropertyNames(); 1056 } 1057 }, new LocalConfiguration() { 1058 1059 @Override 1060 public String get(final String key) { 1061 String val = System.getenv(key); 1062 if (val != null) { 1063 return val; 1064 } 1065 String k = key.replaceAll("[^A-Za-z0-9]", "_"); 1066 val = System.getenv(k); 1067 if (val != null) { 1068 return val; 1069 } 1070 val = System.getenv(k.toUpperCase(ROOT)); 1071 if (val != null) { 1072 return val; 1073 } 1074 return null; 1075 } 1076 1077 @Override 1078 public Set<String> keys() { 1079 return System.getenv().keySet(); 1080 } 1081 })); 1082 return configurations; 1083 } 1084 1085 private <T extends Annotation> T findComponentsConfig(final Map<String, AnnotatedElement> componentDefaults, 1086 final Class<?> type, final ConfigurableClassLoader loader, final Class<T> annotation, 1087 final T defaultValue) { 1088 1089 final AnnotatedElement annotatedElement = 1090 componentDefaults.computeIfAbsent(getAnnotatedElementCacheKey(type), p -> { 1091 if (p != null) { 1092 String currentPackage = p; 1093 do { 1094 try { 1095 final Class<?> pckInfo = loader.loadClass(currentPackage + ".package-info"); 1096 if (pckInfo.isAnnotationPresent(annotation)) { 1097 return pckInfo; 1098 } 1099 } catch (final ClassNotFoundException e) { 1100 // no-op 1101 } 1102 1103 final int endPreviousPackage = currentPackage.lastIndexOf('.'); 1104 if (endPreviousPackage < 0) { // we don't accept default package since it is not specific 1105 // enough 1106 break; 1107 } 1108 1109 currentPackage = currentPackage.substring(0, endPreviousPackage); 1110 } while (true); 1111 } 1112 1113 return new AnnotatedElement() { 1114 1115 @Override 1116 public <T extends Annotation> T getAnnotation(final Class<T> annotationClass) { 1117 return annotationClass == annotation ? annotationClass.cast(defaultValue) : null; 1118 } 1119 1120 @Override 1121 public Annotation[] getAnnotations() { 1122 return new Annotation[] { defaultValue }; 1123 } 1124 1125 @Override 1126 public Annotation[] getDeclaredAnnotations() { 1127 return getAnnotations(); 1128 } 1129 }; 1130 }); 1131 return annotatedElement.getAnnotation(annotation); 1132 } 1133 1134 private String getAnnotatedElementCacheKey(final Class<?> type) { 1135 return ofNullable(type.getPackage().getName()).orElse(""); 1136 } 1137 1138 private Function<Map<String, String>, Object[]> createParametersFactory(final String plugin, 1139 final Executable method, final Map<Class<?>, Object> services, final Supplier<List<ParameterMeta>> metas) { 1140 // it is "slow" for cold boots so let's delay it 1141 return config -> executeInContainer(plugin, 1142 lazy(() -> reflections.parameterFactory(method, services, metas == null ? null : metas.get()))) 1143 .apply(config); 1144 } 1145 1146 public enum ComponentType { 1147 1148 MAPPER { 1149 1150 @Override 1151 public Map<String, ? extends ComponentFamilyMeta.BaseMeta> findMeta(final ComponentFamilyMeta family) { 1152 return family.getPartitionMappers(); 1153 } 1154 1155 @Override 1156 Class<? extends Lifecycle> runtimeType() { 1157 return Mapper.class; 1158 } 1159 }, 1160 PROCESSOR { 1161 1162 @Override 1163 public Map<String, ? extends ComponentFamilyMeta.BaseMeta> findMeta(final ComponentFamilyMeta family) { 1164 return family.getProcessors(); 1165 } 1166 1167 @Override 1168 Class<? extends Lifecycle> runtimeType() { 1169 return org.talend.sdk.component.runtime.output.Processor.class; 1170 } 1171 }, 1172 DRIVER_RUNNER { 1173 1174 @Override 1175 public Map<String, ? extends ComponentFamilyMeta.BaseMeta> findMeta(final ComponentFamilyMeta family) { 1176 return family.getDriverRunners(); 1177 } 1178 1179 @Override 1180 Class<? extends Lifecycle> runtimeType() { 1181 return org.talend.sdk.component.runtime.standalone.DriverRunner.class; 1182 } 1183 }; 1184 1185 abstract public Map<String, ? extends ComponentFamilyMeta.BaseMeta> findMeta(ComponentFamilyMeta family); 1186 1187 abstract Class<? extends Lifecycle> runtimeType(); 1188 } 1189 1190 @AllArgsConstructor 1191 private static class SerializationReplacer implements Serializable { 1192 1193 Object readResolve() throws ObjectStreamException { 1194 return instance(); 1195 } 1196 } 1197 1198 @Data 1199 @AllArgsConstructor 1200 public static class AllServices { 1201 1202 private final Map<Class<?>, Object> services; 1203 } 1204 1205 @Data 1206 public static class OriginalId { 1207 1208 private final String value; 1209 } 1210 1211 @RequiredArgsConstructor 1212 private class Updater implements ContainerListener { 1213 1214 private final String dependenciesResource; 1215 1216 private final ModelVisitor visitor = new ModelVisitor(); 1217 1218 private final Collection<String> supportedAnnotations = Stream 1219 .of(Internationalized.class, Service.class, Request.class, PartitionMapper.class, Processor.class, 1220 Emitter.class, DriverRunner.class) 1221 .map(Type::getDescriptor) 1222 .collect(toSet()); 1223 1224 @Override 1225 public void onCreate(final Container container) { 1226 final ConfigurableClassLoader loader = container.getLoader(); 1227 final OriginalId originalId = OriginalId.class.cast(container.get(OriginalId.class)); 1228 final Map<java.lang.reflect.Type, Optional<Converter>> xbeanConverterCache = new ConcurrentHashMap<>(); 1229 1230 final AnnotationFinder finder; 1231 Archive archive = null; 1232 try { 1233 String alreadyScannedClasses = null; 1234 Filter filter = KnownClassesFilter.INSTANCE; 1235 try (final InputStream containerFilterConfig = 1236 container.getLoader().getResourceAsStream("TALEND-INF/scanning.properties")) { 1237 if (containerFilterConfig != null) { 1238 final Properties config = new Properties(); 1239 config.load(containerFilterConfig); 1240 filter = createScanningFilter(config); 1241 alreadyScannedClasses = config.getProperty("classes.list"); 1242 } 1243 } catch (final IOException e) { 1244 log.debug(e.getMessage(), e); 1245 } 1246 1247 AnnotationFinder optimizedFinder = null; 1248 if (alreadyScannedClasses != null 1249 && !(alreadyScannedClasses = alreadyScannedClasses.trim()).isEmpty()) { 1250 final List<? extends Class<?>> classes = 1251 Stream.of(alreadyScannedClasses.split(",")).map(String::trim).map(it -> { 1252 try { 1253 return loader.loadClass(it); 1254 } catch (final ClassNotFoundException e) { 1255 throw new IllegalArgumentException(e); 1256 } 1257 }).collect(toList()); 1258 if (KnownClassesFilter.INSTANCE == filter) { 1259 archive = new ClassesArchive(/* empty */); 1260 optimizedFinder = new AnnotationFinder(archive) { 1261 1262 @Override 1263 public List<Class<?>> findAnnotatedClasses(final Class<? extends Annotation> marker) { 1264 return classes.stream().filter(c -> c.isAnnotationPresent(marker)).collect(toList()); 1265 } 1266 1267 @Override 1268 public List<Method> findAnnotatedMethods(final Class<? extends Annotation> annotation) { 1269 if (Request.class == annotation) { // optimized 1270 return classes 1271 .stream() 1272 .filter(HttpClient.class::isAssignableFrom) 1273 .flatMap(client -> Stream 1274 .of(client.getMethods()) 1275 .filter(m -> m.isAnnotationPresent(annotation))) 1276 .collect(toList()); 1277 } 1278 return super.findAnnotatedMethods(annotation); 1279 } 1280 1281 // finder.findAnnotatedMethods(Request.class) 1282 }; 1283 } 1284 } else { 1285 /* 1286 * container.findExistingClasspathFiles() - we just scan the root module for 1287 * now, no need to scan all the world 1288 */ 1289 archive = toArchive(container.getRootModule(), originalId, loader); 1290 } 1291 finder = optimizedFinder == null ? new AnnotationFinder(new FilteredArchive(archive, filter)) { 1292 1293 @Override 1294 protected boolean cleanOnNaked() { 1295 return true; 1296 } 1297 1298 @Override 1299 protected boolean isTracked(final String annotationType) { 1300 return supportedAnnotations.contains(annotationType); 1301 } 1302 } : optimizedFinder; 1303 } finally { 1304 if (AutoCloseable.class.isInstance(archive)) { 1305 try { 1306 AutoCloseable.class.cast(archive).close(); 1307 } catch (final Exception e) { 1308 log.warn(e.getMessage()); 1309 } 1310 } 1311 } 1312 final ContainerComponentRegistry registry = new ContainerComponentRegistry(); 1313 container.set(ContainerComponentRegistry.class, registry); 1314 1315 final boolean isGeneric; 1316 final Iterator<GenericComponentExtension> genericExtension = 1317 ServiceLoader.load(GenericComponentExtension.class, container.getLoader()).iterator(); 1318 if (genericExtension.hasNext()) { 1319 final GenericComponentExtension first = genericExtension.next(); 1320 container.set(GenericComponentExtension.class, first); 1321 isGeneric = true; 1322 if (genericExtension.hasNext()) { 1323 throw new IllegalArgumentException("A component can't have two generic component extensions: " 1324 + finder + ", " + genericExtension.next()); 1325 } 1326 } else { 1327 isGeneric = false; 1328 } 1329 1330 final AtomicReference<Map<Class<?>, Object>> seviceLookupRef = new AtomicReference<>(); 1331 1332 final ContainerManager containerManager = ComponentManager.this.getContainer(); 1333 final Supplier<Stream<ContainerComponentRegistry>> registriesSupplier = () -> containerManager 1334 .findAll() 1335 .stream() 1336 .map((Container c) -> c.get(ContainerComponentRegistry.class)); 1337 final ComponentInstantiator.Builder builder = new ComponentInstantiator.BuilderDefault(registriesSupplier); 1338 1339 final Map<Class<?>, Object> services = new LazyMap<>(24, 1340 type -> defaultServiceProvider 1341 .lookup(container.getId(), container.getLoader(), 1342 () -> container 1343 .getLoader() 1344 .findContainedResources("TALEND-INF/local-configuration.properties"), 1345 container.getLocalDependencyRelativeResolver(), type, seviceLookupRef, builder)); 1346 seviceLookupRef.set(services); 1347 1348 final AllServices allServices = new AllServices(services); 1349 container.set(AllServices.class, allServices); 1350 // container services 1351 containerServices(container, services); 1352 1353 container.set(LightContainer.class, new LightContainer() { 1354 1355 @Override 1356 public ClassLoader classloader() { 1357 return container.getLoader(); 1358 } 1359 1360 @Override 1361 public <T> T findService(final Class<T> key) { 1362 return key.cast(services.get(key)); 1363 } 1364 }); 1365 1366 final Map<String, AnnotatedElement> componentDefaults = new HashMap<>(); 1367 1368 finder.findAnnotatedClasses(Internationalized.class).forEach((Class proxy) -> { 1369 final Object service = internationalizationServiceFactory.create(proxy, container.getLoader()); 1370 final Object instance = javaProxyEnricherFactory 1371 .asSerializable(container.getLoader(), container.getId(), proxy.getName(), 1372 service, true); 1373 services.put(proxy, instance); 1374 registry.getServices().add(new ServiceMeta(instance, emptyList())); 1375 }); 1376 finder 1377 .findAnnotatedMethods(Request.class) 1378 .stream() 1379 .map(Method::getDeclaringClass) 1380 .distinct() 1381 .filter(HttpClient.class::isAssignableFrom) // others are created manually 1382 .forEach(proxy -> { 1383 final Object instance = 1384 HttpClientFactory.class.cast(services.get(HttpClientFactory.class)).create(proxy, null); 1385 services.put(proxy, instance); 1386 registry.getServices().add(new ServiceMeta(instance, emptyList())); 1387 }); 1388 final ServiceHelper serviceHelper = new ServiceHelper(ComponentManager.this.proxyGenerator, services); 1389 final Map<Class<?>, Object> userServices = finder 1390 .findAnnotatedClasses(Service.class) 1391 .stream() 1392 .filter(s -> !services.containsKey(s)) 1393 .collect(toMap(identity(), (Class<?> service) -> ThreadHelper 1394 .runWithClassLoader( 1395 () -> serviceHelper 1396 .createServiceInstance(container.getLoader(), container.getId(), service), 1397 container.getLoader()))); 1398 // now we created all instances we can inject *then* postconstruct 1399 final Injector injector = Injector.class.cast(services.get(Injector.class)); 1400 services.putAll(userServices); 1401 userServices.forEach((service, instance) -> { 1402 injector.inject(instance); 1403 doInvoke(container.getId(), instance, PostConstruct.class); 1404 services.put(service, instance); 1405 registry 1406 .getServices() 1407 .add(new ServiceMeta(instance, Stream 1408 .of(service.getMethods()) 1409 .filter(m -> Stream 1410 .of(m.getAnnotations()) 1411 .anyMatch(a -> a.annotationType().isAnnotationPresent(ActionType.class))) 1412 .map(serviceMethod -> createServiceMeta(container, services, componentDefaults, service, 1413 instance, serviceMethod, service)) 1414 .collect(toList()))); 1415 info("Added @Service " + service + " for container-id=" + container.getId()); 1416 }); 1417 1418 final ComponentContexts componentContexts = new ComponentContexts(); 1419 container.set(ComponentContexts.class, componentContexts); 1420 if (!isGeneric) { 1421 Stream 1422 .of(PartitionMapper.class, Processor.class, Emitter.class, DriverRunner.class) 1423 .flatMap(a -> finder.findAnnotatedClasses(a).stream()) 1424 .filter(t -> Modifier.isPublic(t.getModifiers())) 1425 .forEach(type -> onComponent(container, registry, services, allServices, componentDefaults, 1426 componentContexts, type, xbeanConverterCache)); 1427 } 1428 } 1429 1430 private Filter createScanningFilter(final Properties config) { 1431 final String includes = config.getProperty("classloader.includes"); 1432 final String excludes = config.getProperty("classloader.excludes"); 1433 if (includes == null && excludes == null) { 1434 return KnownClassesFilter.INSTANCE; 1435 } 1436 final Filter accept = ofNullable(includes) 1437 .map(String::trim) 1438 .filter(v -> !v.isEmpty()) 1439 .map(s -> s.split(",")) 1440 .map(Filters::patterns) 1441 .orElseGet(() -> name -> true); 1442 final Filter reject = ofNullable(excludes) 1443 .map(String::trim) 1444 .filter(v -> !v.isEmpty()) 1445 .map(s -> s.split(",")) 1446 .map(Filters::patterns) 1447 .orElseGet(() -> name -> false); 1448 if ("include-exclude".equals(config.getProperty("classloader.filter.strategy"))) { 1449 return new IncludeExcludeFilter(accept, reject); 1450 } 1451 return new ExcludeIncludeFilter(accept, reject); 1452 } 1453 1454 private void onComponent(final Container container, final ContainerComponentRegistry registry, 1455 final Map<Class<?>, Object> services, final AllServices allServices, 1456 final Map<String, AnnotatedElement> componentDefaults, final ComponentContexts componentContexts, 1457 final Class<?> type, final Map<java.lang.reflect.Type, Optional<Converter>> xbeanConverterCache) { 1458 final Components components = findComponentsConfig(componentDefaults, type, container.getLoader(), 1459 Components.class, DEFAULT_COMPONENT); 1460 1461 final ComponentContextImpl context = new ComponentContextImpl(type); 1462 componentContexts.getContexts().put(type, context); 1463 extensions.forEach(e -> { 1464 context.setCurrentExtension(e); 1465 try { 1466 e.onComponent(context); 1467 } finally { 1468 context.setCurrentExtension(null); 1469 } 1470 if (context.getOwningExtension() == e) { 1471 ofNullable(e.getExtensionServices(container.getId())).ifPresent(services::putAll); 1472 } 1473 }); 1474 1475 final ComponentMetaBuilder builder = new ComponentMetaBuilder(container.getId(), allServices, components, 1476 componentDefaults.get(getAnnotatedElementCacheKey(type)), context, migrationHandlerFactory, 1477 iconFinder, xbeanConverterCache); 1478 1479 final Thread thread = Thread.currentThread(); 1480 final ClassLoader old = thread.getContextClassLoader(); 1481 thread.setContextClassLoader(container.getLoader()); 1482 try { 1483 visitor.visit(type, builder, Mode.mode != Mode.UNSAFE && !context.isNoValidation()); 1484 } finally { 1485 thread.setContextClassLoader(old); 1486 } 1487 1488 ofNullable(builder.component).ifPresent(c -> { 1489 // for now we assume one family per module, we can remove this constraint if 1490 // really needed 1491 // but kind of enforce a natural modularity 1492 1493 final ComponentFamilyMeta componentFamilyMeta = 1494 registry.getComponents().computeIfAbsent(c.getName(), n -> c); 1495 if (componentFamilyMeta != c) { 1496 if (componentFamilyMeta 1497 .getProcessors() 1498 .keySet() 1499 .stream() 1500 .anyMatch(k -> c.getProcessors().containsKey(k))) { 1501 throw new IllegalArgumentException("Conflicting processors in " + c); 1502 } 1503 if (componentFamilyMeta 1504 .getPartitionMappers() 1505 .keySet() 1506 .stream() 1507 .anyMatch(k -> c.getPartitionMappers().containsKey(k))) { 1508 throw new IllegalArgumentException("Conflicting mappers in " + c); 1509 } 1510 if (componentFamilyMeta 1511 .getDriverRunners() 1512 .keySet() 1513 .stream() 1514 .anyMatch(k -> c.getDriverRunners().containsKey(k))) { 1515 throw new IllegalArgumentException("Conflicting driver runners in " + c); 1516 } 1517 1518 // if we passed validations then merge 1519 componentFamilyMeta.getProcessors().putAll(c.getProcessors()); 1520 componentFamilyMeta.getPartitionMappers().putAll(c.getPartitionMappers()); 1521 componentFamilyMeta.getDriverRunners().putAll(c.getDriverRunners()); 1522 } 1523 }); 1524 1525 info("Parsed component " + type + " for container-id=" + container.getId()); 1526 } 1527 1528 private ServiceMeta.ActionMeta createServiceMeta(final Container container, 1529 final Map<Class<?>, Object> services, final Map<String, AnnotatedElement> componentDefaults, 1530 final Class<?> service, final Object instance, final Method serviceMethod, 1531 final Class<?> declaringClass) { 1532 final Components components = findComponentsConfig(componentDefaults, declaringClass, container.getLoader(), 1533 Components.class, DEFAULT_COMPONENT); 1534 1535 final Annotation marker = Stream 1536 .of(serviceMethod.getAnnotations()) 1537 .filter(a -> a.annotationType().isAnnotationPresent(ActionType.class)) 1538 .findFirst() 1539 .orElseThrow(() -> new IllegalStateException("Something went wrong with " + serviceMethod)); 1540 final ActionType actionType = marker.annotationType().getAnnotation(ActionType.class); 1541 1542 if (actionType.expectedReturnedType() != Object.class 1543 && !actionType.expectedReturnedType().isAssignableFrom(serviceMethod.getReturnType())) { 1544 throw new IllegalArgumentException( 1545 "Can't use " + marker + " on " + serviceMethod + ", expected returned type: " 1546 + actionType.expectedReturnedType() + ", actual one: " + serviceMethod.getReturnType()); 1547 } 1548 1549 String component = ""; 1550 try { 1551 component = ofNullable(String.class.cast(marker.annotationType().getMethod("family").invoke(marker))) 1552 .filter(c -> !c.isEmpty()) 1553 .orElseGet(components::family); 1554 } catch (final NoSuchMethodException e) { 1555 component = components.family(); 1556 } catch (final IllegalAccessException e) { 1557 throw new IllegalStateException(e); 1558 } catch (final InvocationTargetException e) { 1559 throw toRuntimeException(e); 1560 } 1561 if (component.isEmpty()) { 1562 throw new IllegalArgumentException("No component for " + serviceMethod 1563 + ", maybe add a @Components on your package " + service.getPackage()); 1564 } 1565 1566 final String name = Stream.of("name", "value").map(mName -> { 1567 try { 1568 return String.class.cast(marker.annotationType().getMethod(mName).invoke(marker)); 1569 } catch (final IllegalAccessException e) { 1570 throw new IllegalStateException(e); 1571 } catch (final InvocationTargetException e) { 1572 throw toRuntimeException(e); 1573 } catch (final NoSuchMethodException e) { 1574 return null; 1575 } 1576 }).filter(Objects::nonNull).findFirst().orElse("default"); 1577 1578 final Function<Map<String, String>, Object[]> parameterFactory = 1579 /* 1580 * think user flow in a form, we can't validate these actions. Maybe we need to add an API for that 1581 * later 1582 */ 1583 createParametersFactory(container.getId(), serviceMethod, services, null); 1584 final Object actionInstance = Modifier.isStatic(serviceMethod.getModifiers()) ? null : instance; 1585 final Function<Map<String, String>, Object> invoker = arg -> executeInContainer(container.getId(), () -> { 1586 try { 1587 final Object[] args = parameterFactory.apply(arg); 1588 return serviceMethod.invoke(actionInstance, args); 1589 } catch (final IllegalAccessException e) { 1590 throw new IllegalStateException(e); 1591 } catch (final InvocationTargetException e) { 1592 throw toRuntimeException(e); 1593 } 1594 }); 1595 1596 return new ServiceMeta.ActionMeta(component, actionType.value(), name, 1597 serviceMethod.getGenericParameterTypes(), 1598 () -> executeInContainer(container.getId(), 1599 () -> parameterModelService 1600 .buildServiceParameterMetas(serviceMethod, 1601 ofNullable(serviceMethod.getDeclaringClass().getPackage()) 1602 .map(Package::getName) 1603 .orElse(""), 1604 new BaseParameterEnricher.Context(LocalConfiguration.class 1605 .cast(container 1606 .get(AllServices.class) 1607 .getServices() 1608 .get(LocalConfiguration.class))))), 1609 invoker); 1610 } 1611 1612 private Archive toArchive(final String module, final OriginalId originalId, 1613 final ConfigurableClassLoader loader) { 1614 final Collection<Archive> archives = new ArrayList<>(); 1615 final Archive mainArchive = 1616 toArchive(module, ofNullable(originalId).map(OriginalId::getValue).orElse(module), loader); 1617 archives.add(mainArchive); 1618 final URL mainUrl = archiveToUrl(mainArchive); 1619 try { 1620 archives.addAll(list(loader.getResources(dependenciesResource)).stream().map(url -> { // strip resource 1621 final String rawUrl = url.toExternalForm(); 1622 try { 1623 if (rawUrl.startsWith("nested")) { 1624 return loader 1625 .getParent() 1626 .getResource(rawUrl.substring("nested:".length(), rawUrl.indexOf("!/"))); 1627 } 1628 return new URL(rawUrl.substring(0, rawUrl.length() - dependenciesResource.length())); 1629 } catch (final MalformedURLException e) { 1630 throw new IllegalArgumentException(e); 1631 } 1632 }).filter(Objects::nonNull).filter(url -> { 1633 if (Objects.equals(mainUrl, url)) { 1634 return false; 1635 } 1636 if (mainUrl == null) { 1637 return true; 1638 } 1639 1640 // if not the same url, ensure it is not the same jar coming from file and nested repo 1641 // this is very unlikely but happens on dirty systems/setups 1642 final String mainPath = mainUrl.getPath(); 1643 final String path = url.getPath(); 1644 final String marker = "!/" + NESTED_MAVEN_REPOSITORY; 1645 if (path != null && path.contains(marker) && !mainPath.contains(marker)) { 1646 final String mvnPath = path.substring(path.lastIndexOf(marker) + marker.length()); 1647 final Path asFile = ofNullable(container.getRootRepositoryLocationPath()) 1648 .orElseGet(() -> PathFactory.get(".")) 1649 .resolve(mvnPath); 1650 return !Objects.equals(Files.toFile(mainUrl).toPath(), asFile); 1651 } 1652 return true; 1653 }).map(nested -> { 1654 if ("nested".equals(nested.getProtocol()) 1655 || (nested.getPath() != null && nested.getPath().contains("!/MAVEN-INF/repository/"))) { 1656 JarInputStream jarStream = null; 1657 try { 1658 jarStream = new JarInputStream(nested.openStream()); 1659 log.debug("Found a nested resource for " + nested); 1660 return new NestedJarArchive(nested, jarStream, loader); 1661 } catch (final IOException e) { 1662 if (jarStream != null) { 1663 try { // normally not needed 1664 jarStream.close(); 1665 } catch (final IOException e1) { 1666 // no-op 1667 } 1668 } 1669 throw new IllegalStateException(e); 1670 } 1671 } 1672 try { 1673 return ClasspathArchive.archive(loader, Files.toFile(nested).toURI().toURL()); 1674 } catch (final MalformedURLException e) { 1675 throw new IllegalStateException(e); 1676 } 1677 }).collect(toList())); 1678 } catch (final IOException e) { 1679 throw new IllegalArgumentException("Error scanning " + module, e); 1680 } 1681 return new CompositeArchive(archives); 1682 } 1683 1684 private URL archiveToUrl(final Archive mainArchive) { 1685 if (JarArchive.class.isInstance(mainArchive)) { 1686 return JarArchive.class.cast(mainArchive).getUrl(); 1687 } else if (FileArchive.class.isInstance(mainArchive)) { 1688 try { 1689 return FileArchive.class.cast(mainArchive).getDir().toURI().toURL(); 1690 } catch (final MalformedURLException e) { 1691 throw new IllegalStateException(e); 1692 } 1693 } else if (NestedJarArchive.class.isInstance(mainArchive)) { 1694 return NestedJarArchive.class.cast(mainArchive).getRootMarker(); 1695 } 1696 return null; 1697 } 1698 1699 private Archive toArchive(final String module, final String moduleId, final ConfigurableClassLoader loader) { 1700 final Path file = of(PathFactory.get(module)) 1701 .filter(java.nio.file.Files::exists) 1702 .orElseGet(() -> container.resolve(module)); 1703 if (java.nio.file.Files.exists(file)) { 1704 try { 1705 return ClasspathArchive.archive(loader, file.toUri().toURL()); 1706 } catch (final MalformedURLException e) { 1707 throw new IllegalArgumentException(e); 1708 } 1709 } 1710 info(module + " (" + moduleId + ") is not a file, will try to look it up from a nested maven repository"); 1711 final URL nestedJar = 1712 loader.getParent().getResource(ConfigurableClassLoader.NESTED_MAVEN_REPOSITORY + module); 1713 if (nestedJar != null) { 1714 InputStream nestedStream = null; 1715 final JarInputStream jarStream; 1716 try { 1717 nestedStream = nestedJar.openStream(); 1718 jarStream = new JarInputStream(nestedStream); 1719 log.debug("Found a nested resource for " + module); 1720 return new NestedJarArchive(nestedJar, jarStream, loader); 1721 } catch (final IOException e) { 1722 if (nestedStream != null) { 1723 try { // normally not needed 1724 nestedStream.close(); 1725 } catch (final IOException e1) { 1726 // no-op 1727 } 1728 } 1729 } 1730 } 1731 throw new IllegalArgumentException( 1732 "Module error: check that the module exist and is a jar or a directory. " + moduleId); 1733 } 1734 1735 @Override 1736 public void onClose(final Container container) { 1737 // ensure we don't keep any data/ref after the classloader of the container is 1738 // released 1739 ofNullable(container.get(ContainerComponentRegistry.class)).ifPresent(r -> { 1740 final ContainerComponentRegistry registry = container.remove(ContainerComponentRegistry.class); 1741 registry.getComponents().clear(); 1742 registry 1743 .getServices() 1744 .stream() 1745 .filter(i -> !Proxy.isProxyClass(i.getInstance().getClass())) 1746 .forEach(s -> doInvoke(container.getId(), s.getInstance(), PreDestroy.class)); 1747 registry.getServices().clear(); 1748 }); 1749 ofNullable(container.get(AllServices.class)) 1750 .map(s -> s.getServices().get(Jsonb.class)) 1751 .map(Jsonb.class::cast) 1752 .ifPresent(jsonb -> { 1753 try { 1754 jsonb.close(); 1755 } catch (final Exception e) { 1756 log.warn(e.getMessage(), e); 1757 } 1758 }); 1759 } 1760 1761 private void doInvoke(final String container, final Object instance, final Class<? extends Annotation> marker) { 1762 executeInContainer(container, () -> { 1763 final Class<?> instanceClass = instance.getClass(); 1764 new ClassFinder(instanceClass.getName().contains("$$") ? instanceClass.getSuperclass() : instanceClass) 1765 .findAnnotatedMethods(marker) 1766 .stream() // we don't limit to one for now 1767 .filter(m -> Modifier.isPublic(m.getModifiers())) 1768 .forEach(m -> { 1769 try { 1770 m.invoke(instance); 1771 } catch (final IllegalAccessException e) { 1772 throw new IllegalStateException(e); 1773 } catch (final InvocationTargetException e) { 1774 throw toRuntimeException(e); 1775 } 1776 }); 1777 return null; 1778 }); 1779 } 1780 } 1781 1782 @RequiredArgsConstructor 1783 private class ComponentMetaBuilder implements ModelListener { 1784 1785 private final String plugin; 1786 1787 private final AllServices services; 1788 1789 private final Components components; 1790 1791 private final AnnotatedElement familyAnnotationElement; 1792 1793 private final ComponentContextImpl context; 1794 1795 private final MigrationHandlerFactory migrationHandlerFactory; 1796 1797 private final IconFinder iconFinder; 1798 1799 private final Map<java.lang.reflect.Type, Optional<Converter>> xbeanConverterCache; 1800 1801 private ComponentMetadataService metadataService = new ComponentMetadataService(); 1802 1803 private ComponentFamilyMeta component; 1804 1805 @Override 1806 public void onPartitionMapper(final Class<?> type, final PartitionMapper partitionMapper) { 1807 final Constructor<?> constructor = findConstructor(type); 1808 final boolean infinite = partitionMapper.infinite(); 1809 final Supplier<List<ParameterMeta>> parameterMetas = lazy(() -> executeInContainer(plugin, 1810 () -> { 1811 final List<ParameterMeta> params = parameterModelService 1812 .buildParameterMetas(constructor, getPackage(type), 1813 new BaseParameterEnricher.Context(LocalConfiguration.class 1814 .cast(services.getServices().get(LocalConfiguration.class)))); 1815 if (infinite) { 1816 if (partitionMapper.stoppable()) { 1817 addInfiniteMapperBuiltInParameters(type, params); 1818 } 1819 } 1820 return params; 1821 })); 1822 final Function<Map<String, String>, Object[]> parameterFactory = 1823 createParametersFactory(plugin, constructor, services.getServices(), parameterMetas); 1824 final String name = of(partitionMapper.name()).filter(n -> !n.isEmpty()).orElseGet(type::getName); 1825 final ComponentFamilyMeta component = getOrCreateComponent(partitionMapper.family()); 1826 1827 final Function<Map<String, String>, Mapper> instantiator = 1828 context.getOwningExtension() != null && context.getOwningExtension().supports(Mapper.class) 1829 ? config -> executeInContainer(plugin, 1830 () -> context 1831 .getOwningExtension() 1832 .convert(new ComponentInstanceImpl( 1833 doInvoke(constructor, parameterFactory.apply(config)), plugin, 1834 component.getName(), name), Mapper.class)) 1835 : config -> new PartitionMapperImpl(component.getName(), name, null, plugin, infinite, 1836 ofNullable(config) 1837 .map(it -> it 1838 .entrySet() 1839 .stream() 1840 .filter(e -> e.getKey().startsWith("$") 1841 || e.getKey().contains(".$")) 1842 .collect(toMap(java.util.Map.Entry::getKey, 1843 java.util.Map.Entry::getValue))) 1844 .orElseGet(Collections::emptyMap), 1845 doInvoke(constructor, parameterFactory.apply(config))); 1846 final Map<String, String> metadata = metadataService.getMetadata(type); 1847 1848 component 1849 .getPartitionMappers() 1850 .put(name, 1851 new ComponentFamilyMeta.PartitionMapperMeta(component, name, iconFinder.findIcon(type), 1852 findVersion(type), type, parameterMetas, 1853 args -> propertyEditorRegistry 1854 .withCache(xbeanConverterCache, () -> instantiator.apply(args)), 1855 Lazy 1856 .lazy(() -> migrationHandlerFactory 1857 .findMigrationHandler(parameterMetas, type, services)), 1858 !context.isNoValidation(), metadata)); 1859 } 1860 1861 @Override 1862 public void onEmitter(final Class<?> type, final Emitter emitter) { 1863 final Constructor<?> constructor = findConstructor(type); 1864 final Supplier<List<ParameterMeta>> parameterMetas = lazy(() -> executeInContainer(plugin, 1865 () -> parameterModelService 1866 .buildParameterMetas(constructor, getPackage(type), 1867 new BaseParameterEnricher.Context(LocalConfiguration.class 1868 .cast(services.getServices().get(LocalConfiguration.class)))))); 1869 final Function<Map<String, String>, Object[]> parameterFactory = 1870 createParametersFactory(plugin, constructor, services.getServices(), parameterMetas); 1871 final String name = of(emitter.name()).filter(n -> !n.isEmpty()).orElseGet(type::getName); 1872 final ComponentFamilyMeta component = getOrCreateComponent(emitter.family()); 1873 final Function<Map<String, String>, Mapper> instantiator = 1874 context.getOwningExtension() != null && context.getOwningExtension().supports(Mapper.class) 1875 ? config -> executeInContainer(plugin, 1876 () -> context 1877 .getOwningExtension() 1878 .convert(new ComponentInstanceImpl( 1879 doInvoke(constructor, parameterFactory.apply(config)), plugin, 1880 component.getName(), name), Mapper.class)) 1881 : config -> new LocalPartitionMapper(component.getName(), name, plugin, 1882 doInvoke(constructor, parameterFactory.apply(config))); 1883 1884 final Map<String, String> metadata = metadataService.getMetadata(type); 1885 1886 component 1887 .getPartitionMappers() 1888 .put(name, 1889 new ComponentFamilyMeta.PartitionMapperMeta(component, name, iconFinder.findIcon(type), 1890 findVersion(type), type, parameterMetas, 1891 args -> propertyEditorRegistry 1892 .withCache(xbeanConverterCache, () -> instantiator.apply(args)), 1893 Lazy 1894 .lazy(() -> migrationHandlerFactory 1895 .findMigrationHandler(parameterMetas, type, services)), 1896 !context.isNoValidation(), metadata)); 1897 } 1898 1899 @Override 1900 public void onProcessor(final Class<?> type, final Processor processor) { 1901 final Constructor<?> constructor = findConstructor(type); 1902 final Supplier<List<ParameterMeta>> parameterMetas = lazy(() -> executeInContainer(plugin, () -> { 1903 final List<ParameterMeta> params = parameterModelService 1904 .buildParameterMetas(constructor, getPackage(type), new BaseParameterEnricher.Context( 1905 LocalConfiguration.class.cast(services.getServices().get(LocalConfiguration.class)))); 1906 addProcessorsBuiltInParameters(type, params); 1907 return params; 1908 })); 1909 final Function<Map<String, String>, Object[]> parameterFactory = 1910 createParametersFactory(plugin, constructor, services.getServices(), parameterMetas); 1911 final String name = of(processor.name()).filter(n -> !n.isEmpty()).orElseGet(type::getName); 1912 final ComponentFamilyMeta component = getOrCreateComponent(processor.family()); 1913 final Function<Map<String, String>, org.talend.sdk.component.runtime.output.Processor> instantiator = 1914 context.getOwningExtension() != null && context 1915 .getOwningExtension() 1916 .supports(org.talend.sdk.component.runtime.output.Processor.class) 1917 ? config -> executeInContainer( 1918 plugin, 1919 () -> context 1920 .getOwningExtension() 1921 .convert( 1922 new ComponentInstanceImpl(doInvoke(constructor, 1923 parameterFactory.apply(config)), plugin, 1924 component.getName(), name), 1925 org.talend.sdk.component.runtime.output.Processor.class)) 1926 : config -> new ProcessorImpl(this.component.getName(), name, plugin, 1927 ofNullable(config) 1928 .map(it -> it 1929 .entrySet() 1930 .stream() 1931 .filter(e -> e.getKey().startsWith("$") 1932 || e.getKey().contains(".$")) 1933 .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))) 1934 .orElseGet(Collections::emptyMap), 1935 doInvoke(constructor, parameterFactory.apply(config))); 1936 1937 final Map<String, String> metadata = metadataService.getMetadata(type); 1938 1939 component 1940 .getProcessors() 1941 .put(name, 1942 new ComponentFamilyMeta.ProcessorMeta(component, name, iconFinder.findIcon(type), 1943 findVersion(type), type, parameterMetas, 1944 args -> propertyEditorRegistry 1945 .withCache(xbeanConverterCache, () -> instantiator.apply(args)), 1946 Lazy 1947 .lazy(() -> migrationHandlerFactory 1948 .findMigrationHandler(parameterMetas, type, services)), 1949 !context.isNoValidation(), metadata)); 1950 } 1951 1952 private void addInfiniteMapperBuiltInParameters(final Class<?> type, final List<ParameterMeta> parameterMetas) { 1953 final ParameterMeta root = 1954 parameterMetas.stream().filter(p -> p.getName().equals(p.getPath())).findFirst().orElseGet(() -> { 1955 final ParameterMeta umbrella = new ParameterMeta(new ParameterMeta.Source() { 1956 1957 @Override 1958 public String name() { 1959 return "$configuration"; 1960 } 1961 1962 @Override 1963 public Class<?> declaringClass() { 1964 return Object.class; 1965 } 1966 }, Object.class, ParameterMeta.Type.OBJECT, "$configuration", "$configuration", new String[0], 1967 new ArrayList<>(), new ArrayList<>(), new HashMap<>(), true); 1968 parameterMetas.add(umbrella); 1969 return umbrella; 1970 }); 1971 1972 final StreamingMaxRecordsParamBuilder paramBuilder = new StreamingMaxRecordsParamBuilder(root, 1973 type.getSimpleName(), 1974 LocalConfiguration.class.cast(services.services.get(LocalConfiguration.class))); 1975 final ParameterMeta maxRecords = paramBuilder.newBulkParameter(); 1976 final ParameterMeta maxDuration = new StreamingMaxDurationMsParamBuilder(root, type.getSimpleName(), 1977 LocalConfiguration.class.cast(services.services.get(LocalConfiguration.class))).newBulkParameter(); 1978 final String layoutOptions = maxRecords.getName() + "|" + maxDuration.getName(); 1979 final String layoutType = paramBuilder.getLayoutType(); 1980 if (layoutType == null) { 1981 root.getMetadata().put("tcomp::ui::gridlayout::Advanced::value", layoutOptions); 1982 root.getMetadata() 1983 .put("tcomp::ui::gridlayout::Main::value", root.getNestedParameters() 1984 .stream() 1985 .map(ParameterMeta::getName) 1986 .collect(joining("|"))); 1987 } else if (!root.getMetadata().containsKey(layoutType)) { 1988 root.getMetadata().put(layoutType, layoutType.contains("gridlayout") ? layoutOptions : "true"); 1989 } else if (layoutType.contains("gridlayout")) { 1990 final String oldLayout = root.getMetadata().get(layoutType); 1991 root.getMetadata().put(layoutType, layoutOptions + "|" + oldLayout); 1992 } 1993 root.getNestedParameters().add(maxRecords); 1994 root.getNestedParameters().add(maxDuration); 1995 } 1996 1997 private void addProcessorsBuiltInParameters(final Class<?> type, final List<ParameterMeta> parameterMetas) { 1998 final ParameterMeta root = 1999 parameterMetas.stream().filter(p -> p.getName().equals(p.getPath())).findFirst().orElseGet(() -> { 2000 final ParameterMeta umbrella = new ParameterMeta(new ParameterMeta.Source() { 2001 2002 @Override 2003 public String name() { 2004 return "$configuration"; 2005 } 2006 2007 @Override 2008 public Class<?> declaringClass() { 2009 return Object.class; 2010 } 2011 }, Object.class, ParameterMeta.Type.OBJECT, "$configuration", "$configuration", new String[0], 2012 new ArrayList<>(), new ArrayList<>(), new HashMap<>(), true); 2013 parameterMetas.add(umbrella); 2014 return umbrella; 2015 }); 2016 2017 if (Stream.of(type.getMethods()).anyMatch(p -> p.isAnnotationPresent(AfterGroup.class))) { 2018 final MaxBatchSizeParamBuilder paramBuilder = new MaxBatchSizeParamBuilder(root, type.getSimpleName(), 2019 LocalConfiguration.class.cast(services.services.get(LocalConfiguration.class))); 2020 final ParameterMeta maxBatchSize = paramBuilder.newBulkParameter(); 2021 if (maxBatchSize != null) { 2022 final String layoutType = paramBuilder.getLayoutType(); 2023 if (layoutType == null) { 2024 root.getMetadata().put("tcomp::ui::gridlayout::Advanced::value", maxBatchSize.getName()); 2025 root 2026 .getMetadata() 2027 .put("tcomp::ui::gridlayout::Main::value", 2028 root 2029 .getNestedParameters() 2030 .stream() 2031 .map(ParameterMeta::getName) 2032 .collect(joining("|"))); 2033 } else if (!root.getMetadata().containsKey(layoutType)) { 2034 root 2035 .getMetadata() 2036 .put(layoutType, layoutType.contains("gridlayout") ? maxBatchSize.getName() : "true"); 2037 } else if (layoutType.contains("gridlayout")) { 2038 final String oldLayout = root.getMetadata().get(layoutType); 2039 root.getMetadata().put(layoutType, maxBatchSize.getName() + "|" + oldLayout); 2040 } 2041 root.getNestedParameters().add(maxBatchSize); 2042 } 2043 } 2044 } 2045 2046 @Override 2047 public void onDriverRunner(final Class<?> type, final DriverRunner processor) { 2048 final Constructor<?> constructor = findConstructor(type); 2049 final Supplier<List<ParameterMeta>> parameterMetas = lazy(() -> executeInContainer(plugin, 2050 () -> parameterModelService 2051 .buildParameterMetas(constructor, getPackage(type), 2052 new BaseParameterEnricher.Context(LocalConfiguration.class 2053 .cast(services.getServices().get(LocalConfiguration.class)))))); 2054 final Function<Map<String, String>, Object[]> parameterFactory = 2055 createParametersFactory(plugin, constructor, services.getServices(), parameterMetas); 2056 final String name = of(processor.name()).filter(n -> !n.isEmpty()).orElseGet(type::getName); 2057 final ComponentFamilyMeta component = getOrCreateComponent(processor.family()); 2058 final Function<Map<String, String>, org.talend.sdk.component.runtime.standalone.DriverRunner> instantiator = 2059 context.getOwningExtension() != null && context 2060 .getOwningExtension() 2061 .supports(org.talend.sdk.component.runtime.standalone.DriverRunner.class) 2062 ? config -> executeInContainer(plugin, 2063 () -> context 2064 .getOwningExtension() 2065 .convert(new ComponentInstanceImpl(doInvoke( 2066 constructor, parameterFactory.apply(config)), plugin, 2067 component.getName(), name), 2068 org.talend.sdk.component.runtime.standalone.DriverRunner.class)) 2069 : config -> new DriverRunnerImpl(this.component.getName(), name, plugin, 2070 doInvoke(constructor, parameterFactory.apply(config))); 2071 final Map<String, String> metadata = metadataService.getMetadata(type); 2072 2073 component 2074 .getDriverRunners() 2075 .put(name, 2076 new ComponentFamilyMeta.DriverRunnerMeta(component, name, iconFinder.findIcon(type), 2077 findVersion(type), type, parameterMetas, 2078 args -> propertyEditorRegistry 2079 .withCache(xbeanConverterCache, () -> instantiator.apply(args)), 2080 Lazy 2081 .lazy(() -> migrationHandlerFactory 2082 .findMigrationHandler(parameterMetas, type, services)), 2083 !context.isNoValidation(), metadata)); 2084 } 2085 2086 private String getPackage(final Class<?> type) { 2087 return ofNullable(type.getPackage()).map(Package::getName).orElse(""); 2088 } 2089 2090 private int findVersion(final Class<?> type) { 2091 return ofNullable(type.getAnnotation(Version.class)).map(Version::value).orElse(1); 2092 } 2093 2094 // we keep the component (family) value since it is more user fiendly than 2095 // having a generated id and it makes the 2096 // component 2097 // logical "id" more accurate 2098 private ComponentFamilyMeta getOrCreateComponent(final String component) { 2099 final String comp = ofNullable(component).filter(s -> !s.isEmpty()).orElseGet(components::family); 2100 if (comp.isEmpty()) { 2101 throw new IllegalArgumentException("Missing component"); 2102 } 2103 return this.component == null || !component.equals(this.component.getName()) 2104 ? (this.component = new ComponentFamilyMeta(plugin, asList(components.categories()), 2105 iconFinder.findIcon(familyAnnotationElement), comp, 2106 Class.class.isInstance(familyAnnotationElement) 2107 ? getPackage(Class.class.cast(familyAnnotationElement)) 2108 : "")) 2109 : this.component; 2110 } 2111 2112 private Serializable doInvoke(final Constructor<?> constructor, final Object[] args) { 2113 return executeInContainer(plugin, () -> { 2114 try { 2115 return Serializable.class.cast(constructor.newInstance(args)); 2116 } catch (final IllegalAccessException e) { 2117 throw new IllegalStateException(e); 2118 } catch (final ClassCastException e) { 2119 throw new IllegalArgumentException(constructor + " should return a Serializable", e); 2120 } catch (final InvocationTargetException e) { 2121 throw toRuntimeException(e); 2122 } catch (final InstantiationException e) { 2123 throw new IllegalArgumentException(e); 2124 } 2125 }); 2126 } 2127 } 2128 2129 /** 2130 * WARNING: use with caution and only if you fully understand the consequences. 2131 */ 2132 public interface Customizer { 2133 2134 /** 2135 * @return a stream of string representing classes or packages. They will be considered 2136 * as loaded from the "container" (ComponentManager loader) and not the components classloaders. 2137 */ 2138 Stream<String> containerClassesAndPackages(); 2139 2140 /** 2141 * @return advanced toggle to ignore built-in beam exclusions and let this customizer override them. 2142 */ 2143 default boolean ignoreBeamClassLoaderExclusions() { 2144 return false; 2145 } 2146 2147 /** 2148 * Disable default built-in component classpath building mecanism. This is useful when relying on 2149 * a custom {@link ContainerClasspathContributor} handling it. 2150 * 2151 * @return true if the default dependencies descriptor (TALEND-INF/dependencies.txt) must be ignored. 2152 */ 2153 default boolean ignoreDefaultDependenciesDescriptor() { 2154 return false; 2155 } 2156 2157 /** 2158 * Enables a customizer to know other configuration. 2159 * 2160 * @param customizers all customizers. 2161 * 2162 * @deprecated Mainly here for backward compatibility for beam customizer. 2163 */ 2164 @Deprecated 2165 default void setCustomizers(final Collection<Customizer> customizers) { 2166 // no-op 2167 } 2168 } 2169 2170 /** 2171 * WARNING: internal extension point, use it only if you really understand what it implies. 2172 */ 2173 public interface ContainerClasspathContributor { 2174 2175 Collection<Artifact> findContributions(String pluginId); 2176 2177 boolean canResolve(String path); 2178 2179 Path resolve(String path); 2180 } 2181}