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}