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