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