From 4a6cc2ce4e950842c813c087362c3fd896e73960 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Tue, 16 Dec 2025 12:24:04 +0100
Subject: [PATCH 01/37] feat(QTDI-2134): initial commit
---
... => DynamicDependenciesConfiguration.java} | 6 +--
.../runtime/manager/ComponentManager.java | 13 +++---
.../runtime/manager/ComponentManagerTest.java | 5 ++-
.../classloader/ConfigurableClassLoader.java | 12 +++++
.../sdk/component/container/Container.java | 4 ++
.../component/container/ContainerManager.java | 44 ++++++++++++++++---
.../config/DynamicDependenciesConf.java | 4 +-
7 files changed, 69 insertions(+), 19 deletions(-)
rename component-api/src/main/java/org/talend/sdk/component/api/configuration/type/{DynamicDependenciesServiceConfiguration.java => DynamicDependenciesConfiguration.java} (85%)
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/configuration/type/DynamicDependenciesServiceConfiguration.java b/component-api/src/main/java/org/talend/sdk/component/api/configuration/type/DynamicDependenciesConfiguration.java
similarity index 85%
rename from component-api/src/main/java/org/talend/sdk/component/api/configuration/type/DynamicDependenciesServiceConfiguration.java
rename to component-api/src/main/java/org/talend/sdk/component/api/configuration/type/DynamicDependenciesConfiguration.java
index d367dce515f68..f7f01ec74d5b3 100644
--- a/component-api/src/main/java/org/talend/sdk/component/api/configuration/type/DynamicDependenciesServiceConfiguration.java
+++ b/component-api/src/main/java/org/talend/sdk/component/api/configuration/type/DynamicDependenciesConfiguration.java
@@ -26,9 +26,9 @@
@Target(TYPE)
@Retention(RUNTIME)
-@ConfigurationType("dynamicDependenciesServiceConfiguration")
-@Documentation("Mark a model (complex object) as being the configuration used in services annotated with @DynamicDependencies.")
-public @interface DynamicDependenciesServiceConfiguration {
+@ConfigurationType("dynamicDependenciesConfiguration")
+@Documentation("Mark a model (complex object) as being the configuration expected to compute dynamic dependencies.")
+public @interface DynamicDependenciesConfiguration {
String value() default "default";
}
\ No newline at end of file
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 56957e149196f..446b262fd3ebd 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -211,11 +211,14 @@ private static ComponentManager buildNewComponentManager() {
{
info("ComponentManager version: " + ComponentManagerVersion.VERSION);
info("Creating the contextual ComponentManager instance " + getIdentifiers());
-
- parallelIf(Boolean.getBoolean("talend.component.manager.plugins.parallel"),
- container.getDefinedNestedPlugin().stream().filter(p -> !hasPlugin(p)))
- .forEach(this::addPlugin);
- info("Components: " + availablePlugins());
+ try {
+ parallelIf(Boolean.getBoolean("talend.component.manager.plugins.parallel"),
+ container.getDefinedNestedPlugin().stream().filter(p -> !hasPlugin(p)))
+ .forEach(this::addPlugin);
+ info("Components: " + availablePlugins());
+ } catch (Exception e) {
+ info("Failed to load plugins from plugins.properties: " + e.getMessage());
+ }
}
@Override
diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/ComponentManagerTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/ComponentManagerTest.java
index 1441c9e7088e9..e9934f20b71f0 100644
--- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/ComponentManagerTest.java
+++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/ComponentManagerTest.java
@@ -441,8 +441,9 @@ void extendFamilyInNestedRepo(@TempDir final File temporaryFolder) throws Except
.map(File::getName)
.sorted()
.toArray(String[]::new);
- assertEquals(1, dependencies.length); // ignored transitive deps, enables the new root to control it
- assertEquals("main.jar", dependencies[0]); // transitive-1.0.0.jar is nested
+ assertEquals(2, dependencies.length); // ignored transitive deps, enables the new root to control it
+ assertEquals("fatjar.jar", dependencies[0]);
+ assertEquals("main.jar", dependencies[1]); // transitive-1.0.0.jar is nested
} finally {
if (!transitive.delete()) {
transitive.deleteOnExit();
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index 3ded8250b3ccd..5e75d9ebdf5fa 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -111,6 +111,8 @@ public class ConfigurableClassLoader extends URLClassLoader {
@Getter
private final List cacheableClasses;
+ private List nestedURLs = new ArrayList<>();
+
public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
final String[] nestedDependencies, final String[] jvmPrefixes) {
@@ -155,6 +157,7 @@ private void loadNestedDependencies(final ClassLoader parent, final String[] nes
if (url == null) {
throw new IllegalArgumentException("Didn't find " + resource + " in " + asList(nestedDependencies));
}
+ nestedURLs.add(url);
final Map resources = new HashMap<>();
final URLConnection urlConnection;
final Manifest manifest;
@@ -458,6 +461,15 @@ public Enumeration findResources(final String name) throws IOException {
return enumeration(aggregated);
}
+ @Override
+ public URL[] getURLs() {
+ final List urls = new ArrayList<>(Arrays.asList(super.getURLs()));
+ if (!nestedURLs.isEmpty()) {
+ urls.addAll(nestedURLs);
+ }
+ return urls.toArray(new URL[0]);
+ }
+
private boolean isNestedDependencyResource(final String name) {
return name != null && name.startsWith(NESTED_MAVEN_REPOSITORY);
}
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
index 67e33c498479a..287e580ae72ad 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
@@ -307,6 +307,10 @@ public Date getCreated() {
return created.get();
}
+ public boolean hasNestedRepository() {
+ return hasNestedRepository;
+ }
+
public void registerTransformer(final ClassFileTransformer transformer) {
transformers.add(transformer);
}
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index ac3ed4fbdfa37..b5579f3ecfa67 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -29,6 +29,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -111,8 +114,11 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
this.containerInitializer = containerInitializer;
this.resolver = dependenciesResolutionConfiguration.getResolver();
this.rootRepositoryLocation = dependenciesResolutionConfiguration.getRootRepositoryLocation();
-
info("Using root repository: " + this.rootRepositoryLocation.toAbsolutePath());
+ if (log.isDebugEnabled()) {
+ getSystemInformations();
+ }
+
final String nestedPluginMappingResource = ofNullable(classLoaderConfiguration.getNestedPluginMappingResource())
.orElse("TALEND-INF/plugins.properties");
this.classLoaderConfiguration = new ClassLoaderConfiguration(
@@ -124,6 +130,12 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
try (final InputStream mappingStream =
classLoaderConfiguration.getParent().getResourceAsStream(nestedPluginMappingResource)) {
if (mappingStream != null) {
+ if (log.isDebugEnabled()) {
+ final URL plug = classLoaderConfiguration.getParent().getResource(nestedPluginMappingResource);
+ if (plug != null) {
+ log.debug("[sysinfo] plugins mapping " + plug.toString());
+ }
+ }
final Properties properties = new Properties() {
{
@@ -150,10 +162,13 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
this.jvmMarkers = Stream
.concat(Stream.concat(Stream.of(getJre()), getComponentModules()), getCustomJvmMarkers())
.toArray(String[]::new);
- this.hasNestedRepository =
- this.classLoaderConfiguration.isSupportsResourceDependencies() && this.classLoaderConfiguration
- .getParent()
- .getResource(ConfigurableClassLoader.NESTED_MAVEN_REPOSITORY) != null;
+ final URL nestedMvn = this.classLoaderConfiguration
+ .getParent()
+ .getResource(ConfigurableClassLoader.NESTED_MAVEN_REPOSITORY);
+ this.hasNestedRepository = this.classLoaderConfiguration.isSupportsResourceDependencies() && nestedMvn != null;
+ if (log.isDebugEnabled() && hasNestedRepository) {
+ log.debug("[sysinfo] nested maven repository: " + nestedMvn);
+ }
}
public File getRootRepositoryLocation() {
@@ -389,6 +404,21 @@ private String getJre() {
.orElseThrow(IllegalArgumentException::new);
}
+ private void getSystemInformations() {
+ try {
+ final RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
+ log.debug("[sysinfo] JVM arguments: " + rt.getInputArguments());
+ try {
+ log.debug("[sysinfo] Boot classpath: " + rt.getBootClassPath());
+ } catch (Exception e) {
+ }
+ log.debug("[sysinfo] Runtime classpath: " + rt.getClassPath());
+ log.debug("[sysinfo] Runtime arguments: " + System.getProperty("sun.java.command"));
+ } catch (Exception e) {
+ log.debug("Unable to get JVM information: " + e.getMessage(), e);
+ }
+ }
+
@Override
public void close() {
lifecycle.closeIfNeeded(() -> {
@@ -472,8 +502,8 @@ public Container create() {
? nestedContainerMapping.getOrDefault(module, module)
: module;
final Path resolved = resolve(moduleLocation);
- info("Creating module " + moduleLocation + " (from " + module
- + (Files.exists(resolved) ? ", location=" + resolved.toAbsolutePath().toString() : "") + ")");
+ info(String.format("Creating module %s (from %s, location=%s)", moduleLocation, module,
+ resolved.toAbsolutePath()));
final Stream classpath = Stream
.concat(getBuiltInClasspath(moduleLocation),
additionalClasspath == null ? Stream.empty() : additionalClasspath.stream());
diff --git a/sample-parent/sample-connector/src/main/java/org/talend/sdk/component/test/connectors/config/DynamicDependenciesConf.java b/sample-parent/sample-connector/src/main/java/org/talend/sdk/component/test/connectors/config/DynamicDependenciesConf.java
index 22258c28bebca..4d182f743ac96 100644
--- a/sample-parent/sample-connector/src/main/java/org/talend/sdk/component/test/connectors/config/DynamicDependenciesConf.java
+++ b/sample-parent/sample-connector/src/main/java/org/talend/sdk/component/test/connectors/config/DynamicDependenciesConf.java
@@ -18,14 +18,14 @@
import java.io.Serializable;
import org.talend.sdk.component.api.configuration.Option;
-import org.talend.sdk.component.api.configuration.type.DynamicDependenciesServiceConfiguration;
+import org.talend.sdk.component.api.configuration.type.DynamicDependenciesConfiguration;
import org.talend.sdk.component.api.configuration.ui.layout.GridLayout;
import org.talend.sdk.component.api.meta.Documentation;
import lombok.Data;
@Data
-@DynamicDependenciesServiceConfiguration
+@DynamicDependenciesConfiguration
@GridLayout({
@GridLayout.Row({ "group" }),
@GridLayout.Row({ "artifact" })
From 83505e1d25c7a3e92ceb693431cf9434bac07c42 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 22 Dec 2025 19:00:57 +0100
Subject: [PATCH 02/37] feat(QTDI-2134): parent resources filtering
---
.../runtime/manager/ComponentManager.java | 33 +++++++++++++++++++
.../classloader/ConfigurableClassLoader.java | 26 +++++++++++++--
.../sdk/component/container/Container.java | 3 +-
.../component/container/ContainerManager.java | 3 ++
...DependencyListLocalRepositoryResolver.java | 26 +++++++++++++--
5 files changed, 84 insertions(+), 7 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 446b262fd3ebd..14f14b4a4febb 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -113,6 +113,7 @@
import org.apache.xbean.finder.archive.FileArchive;
import org.apache.xbean.finder.archive.FilteredArchive;
import org.apache.xbean.finder.archive.JarArchive;
+import org.apache.xbean.finder.filter.ContainsFilter;
import org.apache.xbean.finder.filter.ExcludeIncludeFilter;
import org.apache.xbean.finder.filter.Filter;
import org.apache.xbean.finder.filter.FilterList;
@@ -315,6 +316,8 @@ public String[] categories() {
// + tcomp "runtime" indeed (invisible from the components but required for the runtime
private final Filter classesFilter;
+ private final Filter resourcesFilter;
+
private final ParameterModelService parameterModelService;
private final InternationalizationServiceFactory internationalizationServiceFactory;
@@ -430,6 +433,13 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.map(PrefixFilter::new)
.toArray(Filter[]::new));
+ resourcesFilter = new FilterList(Stream.concat(
+ Stream.of("META-INF/services/"),
+ additionalParentResources())
+ .distinct()
+ .map(ContainsFilter::new)
+ .toArray(Filter[]::new));
+
jsonpProvider = loadJsonProvider();
jsonbProvider = loadJsonbProvider();
// these factories have memory caches so ensure we reuse them properly
@@ -463,6 +473,7 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
migrationHandlerFactory = new MigrationHandlerFactory(reflections);
final Predicate isContainerClass = name -> isContainerClass(classesFilter, name);
+ final Predicate isParentResource = name -> isContainerResource(resourcesFilter, name);
final ContainerManager.ClassLoaderConfiguration defaultClassLoaderConfiguration =
ContainerManager.ClassLoaderConfiguration
.builder()
@@ -470,6 +481,7 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.parentClassesFilter(isContainerClass)
.classesFilter(isContainerClass.negate())
.supportsResourceDependencies(true)
+ .parentResourcesFilter(isParentResource)
.create();
this.container = new ContainerManager(ContainerManager.DependenciesResolutionConfiguration
.builder()
@@ -614,6 +626,16 @@ private Stream additionalContainerClasses() {
.orElseGet(Stream::empty));
}
+ private Stream additionalParentResources() {
+ return Stream
+ .concat(customizers.stream().flatMap(Customizer::parentResources),
+ ofNullable(
+ System.getProperty("talend.component.manager.classloader.container.parentResources"))
+ .map(s -> s.split(","))
+ .map(Stream::of)
+ .orElseGet(Stream::empty));
+ }
+
public static Path findM2() {
return new MavenRepositoryDefaultResolver().discover();
}
@@ -1048,6 +1070,10 @@ protected boolean isContainerClass(final Filter filter, final String name) {
return name != null && filter.accept(name);
}
+ protected boolean isContainerResource(final Filter filter, final String name) {
+ return name != null && filter.accept(name);
+ }
+
@Override
public void close() {
container.close();
@@ -2210,6 +2236,13 @@ public interface Customizer {
*/
Stream containerClassesAndPackages();
+ /**
+ * @return
+ */
+ default Stream parentResources() {
+ return Stream.empty();
+ }
+
/**
* @return advanced toggle to ignore built-in beam exclusions and let this customizer override them.
*/
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index 5e75d9ebdf5fa..e62328e557796 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -92,6 +92,9 @@ public class ConfigurableClassLoader extends URLClassLoader {
@Getter
private final Predicate childFirstFilter;
+ @Getter
+ private final Predicate resourcesFilter;
+
private final Map> resources = new HashMap<>();
private final Collection transformers = new ArrayList<>();
@@ -116,7 +119,16 @@ public class ConfigurableClassLoader extends URLClassLoader {
public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
final String[] nestedDependencies, final String[] jvmPrefixes) {
- this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes);
+ this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes, (name) -> false);
+ if (nestedDependencies != null) {
+ loadNestedDependencies(parent, nestedDependencies);
+ }
+ }
+
+ public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
+ final Predicate parentFilter, final Predicate childFirstFilter,
+ final String[] nestedDependencies, final String[] jvmPrefixes, final Predicate resourcesFilter) {
+ this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes, resourcesFilter);
if (nestedDependencies != null) {
loadNestedDependencies(parent, nestedDependencies);
}
@@ -124,12 +136,14 @@ public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoa
private ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
- final Map> resources, final String[] jvmPrefixes) {
+ final Map> resources, final String[] jvmPrefixes,
+ final Predicate resourcesFilter) {
super(urls, parent);
this.id = id;
this.creationUrls = urls;
this.parentFilter = parentFilter;
this.childFirstFilter = childFirstFilter;
+ this.resourcesFilter = resourcesFilter;
this.resources.putAll(resources);
this.fullPathJvmPrefixes =
@@ -232,7 +246,7 @@ public void registerTransformer(final ClassFileTransformer transformer) {
public synchronized URLClassLoader createTemporaryCopy() {
final ConfigurableClassLoader self = this;
return temporaryCopy == null ? temporaryCopy = new ConfigurableClassLoader(id, creationUrls, getParent(),
- parentFilter, childFirstFilter, resources, fullPathJvmPrefixes) {
+ parentFilter, childFirstFilter, resources, fullPathJvmPrefixes, resourcesFilter) {
@Override
public synchronized void close() throws IOException {
@@ -475,6 +489,12 @@ private boolean isNestedDependencyResource(final String name) {
}
private boolean isInJvm(final URL resource) {
+ // Services and parent allowed resources that should always be found by top level classloader.
+ // By default, META-INF/services/ is always allowed otherwise SPI won't work properly in nested environments.
+ // Warning: selection shouldn't be too generic! Use very specific paths only like jndi.properties.
+ if (resourcesFilter.test(resource.getFile())) {
+ return true;
+ }
final Path path = toPath(resource);
if (path == null) {
return false;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
index 287e580ae72ad..8990bfbe45030 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
@@ -145,7 +145,8 @@ public Container(final String id, final String rootModule, final Artifact[] depe
: null;
final ConfigurableClassLoader loader = new ConfigurableClassLoader(id, urls,
overrideClassLoaderConfig.getParent(), overrideClassLoaderConfig.getParentClassesFilter(),
- overrideClassLoaderConfig.getClassesFilter(), rawNestedDependencies, jvmMarkers);
+ overrideClassLoaderConfig.getClassesFilter(), rawNestedDependencies, jvmMarkers,
+ overrideClassLoaderConfig.getParentResourcesFilter());
transformers.forEach(loader::registerTransformer);
activeSpecificTransformers(loader);
return loader;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index b5579f3ecfa67..9995b12da2932 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -125,6 +125,7 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
ofNullable(classLoaderConfiguration.getParent()).orElseGet(ContainerManager.class::getClassLoader),
ofNullable(classLoaderConfiguration.getClassesFilter()).orElseGet(() -> name -> true),
ofNullable(classLoaderConfiguration.getParentClassesFilter()).orElseGet(() -> name -> true),
+ ofNullable(classLoaderConfiguration.getParentResourcesFilter()).orElseGet(() -> name -> true),
classLoaderConfiguration.isSupportsResourceDependencies(), nestedPluginMappingResource);
if (classLoaderConfiguration.isSupportsResourceDependencies()) {
try (final InputStream mappingStream =
@@ -451,6 +452,8 @@ public static class ClassLoaderConfiguration {
private final Predicate parentClassesFilter;
+ private final Predicate parentResourcesFilter;
+
// is nested jar in jar supported (1 level only)
private final boolean supportsResourceDependencies;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
index c061f2d95caec..70d0fe75f0416 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Properties;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarFile;
@@ -61,8 +62,8 @@ public class MvnDependencyListLocalRepositoryResolver implements Resolver {
@Override
public Stream resolve(final ClassLoader rootLoader, final String artifact) {
- return Stream
- .of(readDependencies(ofNullable(getJar(artifact))
+ return Stream.of(
+ readDependencies(ofNullable(getJar(artifact))
.filter(Files::exists)
.map(this::findDependenciesFile)
.orElseGet(() -> {
@@ -98,7 +99,26 @@ public Stream resolve(final ClassLoader rootLoader, final String artif
}
return "";
- })));
+ })), readDynamicDependencies(rootLoader, artifact));
+ }
+
+ private String readDynamicDependencies(ClassLoader rootLoader, String artifact) {
+ try (final InputStream stream = rootLoader.getResourceAsStream("TALEND-INF/dynamic-dependencies.properties")) {
+ if (stream == null) {
+ return "";
+ }
+ final Properties properties = new Properties() {
+
+ {
+ load(stream);
+ }
+ };
+ final String dyndeps = properties.getProperty(artifact, "");
+ return dyndeps.replaceAll(",", System.lineSeparator());
+ } catch (final IOException e) {
+ log.debug(e.getMessage(), e);
+ return "";
+ }
}
private Path getJar(final String artifact) {
From 921e7fec4fe592ff0918f50f61cba8a9af4ac56f Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 22 Dec 2025 19:04:00 +0100
Subject: [PATCH 03/37] Revert "feat(QTDI-2134): parent resources filtering"
This reverts commit 83505e1d25c7a3e92ceb693431cf9434bac07c42.
---
.../runtime/manager/ComponentManager.java | 33 -------------------
.../classloader/ConfigurableClassLoader.java | 26 ++-------------
.../sdk/component/container/Container.java | 3 +-
.../component/container/ContainerManager.java | 3 --
...DependencyListLocalRepositoryResolver.java | 26 ++-------------
5 files changed, 7 insertions(+), 84 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 14f14b4a4febb..446b262fd3ebd 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -113,7 +113,6 @@
import org.apache.xbean.finder.archive.FileArchive;
import org.apache.xbean.finder.archive.FilteredArchive;
import org.apache.xbean.finder.archive.JarArchive;
-import org.apache.xbean.finder.filter.ContainsFilter;
import org.apache.xbean.finder.filter.ExcludeIncludeFilter;
import org.apache.xbean.finder.filter.Filter;
import org.apache.xbean.finder.filter.FilterList;
@@ -316,8 +315,6 @@ public String[] categories() {
// + tcomp "runtime" indeed (invisible from the components but required for the runtime
private final Filter classesFilter;
- private final Filter resourcesFilter;
-
private final ParameterModelService parameterModelService;
private final InternationalizationServiceFactory internationalizationServiceFactory;
@@ -433,13 +430,6 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.map(PrefixFilter::new)
.toArray(Filter[]::new));
- resourcesFilter = new FilterList(Stream.concat(
- Stream.of("META-INF/services/"),
- additionalParentResources())
- .distinct()
- .map(ContainsFilter::new)
- .toArray(Filter[]::new));
-
jsonpProvider = loadJsonProvider();
jsonbProvider = loadJsonbProvider();
// these factories have memory caches so ensure we reuse them properly
@@ -473,7 +463,6 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
migrationHandlerFactory = new MigrationHandlerFactory(reflections);
final Predicate isContainerClass = name -> isContainerClass(classesFilter, name);
- final Predicate isParentResource = name -> isContainerResource(resourcesFilter, name);
final ContainerManager.ClassLoaderConfiguration defaultClassLoaderConfiguration =
ContainerManager.ClassLoaderConfiguration
.builder()
@@ -481,7 +470,6 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.parentClassesFilter(isContainerClass)
.classesFilter(isContainerClass.negate())
.supportsResourceDependencies(true)
- .parentResourcesFilter(isParentResource)
.create();
this.container = new ContainerManager(ContainerManager.DependenciesResolutionConfiguration
.builder()
@@ -626,16 +614,6 @@ private Stream additionalContainerClasses() {
.orElseGet(Stream::empty));
}
- private Stream additionalParentResources() {
- return Stream
- .concat(customizers.stream().flatMap(Customizer::parentResources),
- ofNullable(
- System.getProperty("talend.component.manager.classloader.container.parentResources"))
- .map(s -> s.split(","))
- .map(Stream::of)
- .orElseGet(Stream::empty));
- }
-
public static Path findM2() {
return new MavenRepositoryDefaultResolver().discover();
}
@@ -1070,10 +1048,6 @@ protected boolean isContainerClass(final Filter filter, final String name) {
return name != null && filter.accept(name);
}
- protected boolean isContainerResource(final Filter filter, final String name) {
- return name != null && filter.accept(name);
- }
-
@Override
public void close() {
container.close();
@@ -2236,13 +2210,6 @@ public interface Customizer {
*/
Stream containerClassesAndPackages();
- /**
- * @return
- */
- default Stream parentResources() {
- return Stream.empty();
- }
-
/**
* @return advanced toggle to ignore built-in beam exclusions and let this customizer override them.
*/
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index e62328e557796..5e75d9ebdf5fa 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -92,9 +92,6 @@ public class ConfigurableClassLoader extends URLClassLoader {
@Getter
private final Predicate childFirstFilter;
- @Getter
- private final Predicate resourcesFilter;
-
private final Map> resources = new HashMap<>();
private final Collection transformers = new ArrayList<>();
@@ -119,16 +116,7 @@ public class ConfigurableClassLoader extends URLClassLoader {
public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
final String[] nestedDependencies, final String[] jvmPrefixes) {
- this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes, (name) -> false);
- if (nestedDependencies != null) {
- loadNestedDependencies(parent, nestedDependencies);
- }
- }
-
- public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
- final Predicate parentFilter, final Predicate childFirstFilter,
- final String[] nestedDependencies, final String[] jvmPrefixes, final Predicate resourcesFilter) {
- this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes, resourcesFilter);
+ this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes);
if (nestedDependencies != null) {
loadNestedDependencies(parent, nestedDependencies);
}
@@ -136,14 +124,12 @@ public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoa
private ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
- final Map> resources, final String[] jvmPrefixes,
- final Predicate resourcesFilter) {
+ final Map> resources, final String[] jvmPrefixes) {
super(urls, parent);
this.id = id;
this.creationUrls = urls;
this.parentFilter = parentFilter;
this.childFirstFilter = childFirstFilter;
- this.resourcesFilter = resourcesFilter;
this.resources.putAll(resources);
this.fullPathJvmPrefixes =
@@ -246,7 +232,7 @@ public void registerTransformer(final ClassFileTransformer transformer) {
public synchronized URLClassLoader createTemporaryCopy() {
final ConfigurableClassLoader self = this;
return temporaryCopy == null ? temporaryCopy = new ConfigurableClassLoader(id, creationUrls, getParent(),
- parentFilter, childFirstFilter, resources, fullPathJvmPrefixes, resourcesFilter) {
+ parentFilter, childFirstFilter, resources, fullPathJvmPrefixes) {
@Override
public synchronized void close() throws IOException {
@@ -489,12 +475,6 @@ private boolean isNestedDependencyResource(final String name) {
}
private boolean isInJvm(final URL resource) {
- // Services and parent allowed resources that should always be found by top level classloader.
- // By default, META-INF/services/ is always allowed otherwise SPI won't work properly in nested environments.
- // Warning: selection shouldn't be too generic! Use very specific paths only like jndi.properties.
- if (resourcesFilter.test(resource.getFile())) {
- return true;
- }
final Path path = toPath(resource);
if (path == null) {
return false;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
index 8990bfbe45030..287e580ae72ad 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
@@ -145,8 +145,7 @@ public Container(final String id, final String rootModule, final Artifact[] depe
: null;
final ConfigurableClassLoader loader = new ConfigurableClassLoader(id, urls,
overrideClassLoaderConfig.getParent(), overrideClassLoaderConfig.getParentClassesFilter(),
- overrideClassLoaderConfig.getClassesFilter(), rawNestedDependencies, jvmMarkers,
- overrideClassLoaderConfig.getParentResourcesFilter());
+ overrideClassLoaderConfig.getClassesFilter(), rawNestedDependencies, jvmMarkers);
transformers.forEach(loader::registerTransformer);
activeSpecificTransformers(loader);
return loader;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 9995b12da2932..b5579f3ecfa67 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -125,7 +125,6 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
ofNullable(classLoaderConfiguration.getParent()).orElseGet(ContainerManager.class::getClassLoader),
ofNullable(classLoaderConfiguration.getClassesFilter()).orElseGet(() -> name -> true),
ofNullable(classLoaderConfiguration.getParentClassesFilter()).orElseGet(() -> name -> true),
- ofNullable(classLoaderConfiguration.getParentResourcesFilter()).orElseGet(() -> name -> true),
classLoaderConfiguration.isSupportsResourceDependencies(), nestedPluginMappingResource);
if (classLoaderConfiguration.isSupportsResourceDependencies()) {
try (final InputStream mappingStream =
@@ -452,8 +451,6 @@ public static class ClassLoaderConfiguration {
private final Predicate parentClassesFilter;
- private final Predicate parentResourcesFilter;
-
// is nested jar in jar supported (1 level only)
private final boolean supportsResourceDependencies;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
index 70d0fe75f0416..c061f2d95caec 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
@@ -32,7 +32,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
-import java.util.Properties;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarFile;
@@ -62,8 +61,8 @@ public class MvnDependencyListLocalRepositoryResolver implements Resolver {
@Override
public Stream resolve(final ClassLoader rootLoader, final String artifact) {
- return Stream.of(
- readDependencies(ofNullable(getJar(artifact))
+ return Stream
+ .of(readDependencies(ofNullable(getJar(artifact))
.filter(Files::exists)
.map(this::findDependenciesFile)
.orElseGet(() -> {
@@ -99,26 +98,7 @@ public Stream resolve(final ClassLoader rootLoader, final String artif
}
return "";
- })), readDynamicDependencies(rootLoader, artifact));
- }
-
- private String readDynamicDependencies(ClassLoader rootLoader, String artifact) {
- try (final InputStream stream = rootLoader.getResourceAsStream("TALEND-INF/dynamic-dependencies.properties")) {
- if (stream == null) {
- return "";
- }
- final Properties properties = new Properties() {
-
- {
- load(stream);
- }
- };
- final String dyndeps = properties.getProperty(artifact, "");
- return dyndeps.replaceAll(",", System.lineSeparator());
- } catch (final IOException e) {
- log.debug(e.getMessage(), e);
- return "";
- }
+ })));
}
private Path getJar(final String artifact) {
From f92f29dde20031c34abcefab87e349e622d969ca Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 22 Dec 2025 19:04:57 +0100
Subject: [PATCH 04/37] feat(QTDI-2134): parent resources filtering
---
.../classloader/ConfigurableClassLoader.java | 26 ++++++++++++++++---
.../sdk/component/container/Container.java | 3 ++-
.../component/container/ContainerManager.java | 3 +++
3 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index 5e75d9ebdf5fa..e62328e557796 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -92,6 +92,9 @@ public class ConfigurableClassLoader extends URLClassLoader {
@Getter
private final Predicate childFirstFilter;
+ @Getter
+ private final Predicate resourcesFilter;
+
private final Map> resources = new HashMap<>();
private final Collection transformers = new ArrayList<>();
@@ -116,7 +119,16 @@ public class ConfigurableClassLoader extends URLClassLoader {
public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
final String[] nestedDependencies, final String[] jvmPrefixes) {
- this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes);
+ this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes, (name) -> false);
+ if (nestedDependencies != null) {
+ loadNestedDependencies(parent, nestedDependencies);
+ }
+ }
+
+ public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
+ final Predicate parentFilter, final Predicate childFirstFilter,
+ final String[] nestedDependencies, final String[] jvmPrefixes, final Predicate resourcesFilter) {
+ this(id, urls, parent, parentFilter, childFirstFilter, emptyMap(), jvmPrefixes, resourcesFilter);
if (nestedDependencies != null) {
loadNestedDependencies(parent, nestedDependencies);
}
@@ -124,12 +136,14 @@ public ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoa
private ConfigurableClassLoader(final String id, final URL[] urls, final ClassLoader parent,
final Predicate parentFilter, final Predicate childFirstFilter,
- final Map> resources, final String[] jvmPrefixes) {
+ final Map> resources, final String[] jvmPrefixes,
+ final Predicate resourcesFilter) {
super(urls, parent);
this.id = id;
this.creationUrls = urls;
this.parentFilter = parentFilter;
this.childFirstFilter = childFirstFilter;
+ this.resourcesFilter = resourcesFilter;
this.resources.putAll(resources);
this.fullPathJvmPrefixes =
@@ -232,7 +246,7 @@ public void registerTransformer(final ClassFileTransformer transformer) {
public synchronized URLClassLoader createTemporaryCopy() {
final ConfigurableClassLoader self = this;
return temporaryCopy == null ? temporaryCopy = new ConfigurableClassLoader(id, creationUrls, getParent(),
- parentFilter, childFirstFilter, resources, fullPathJvmPrefixes) {
+ parentFilter, childFirstFilter, resources, fullPathJvmPrefixes, resourcesFilter) {
@Override
public synchronized void close() throws IOException {
@@ -475,6 +489,12 @@ private boolean isNestedDependencyResource(final String name) {
}
private boolean isInJvm(final URL resource) {
+ // Services and parent allowed resources that should always be found by top level classloader.
+ // By default, META-INF/services/ is always allowed otherwise SPI won't work properly in nested environments.
+ // Warning: selection shouldn't be too generic! Use very specific paths only like jndi.properties.
+ if (resourcesFilter.test(resource.getFile())) {
+ return true;
+ }
final Path path = toPath(resource);
if (path == null) {
return false;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
index 287e580ae72ad..8990bfbe45030 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/Container.java
@@ -145,7 +145,8 @@ public Container(final String id, final String rootModule, final Artifact[] depe
: null;
final ConfigurableClassLoader loader = new ConfigurableClassLoader(id, urls,
overrideClassLoaderConfig.getParent(), overrideClassLoaderConfig.getParentClassesFilter(),
- overrideClassLoaderConfig.getClassesFilter(), rawNestedDependencies, jvmMarkers);
+ overrideClassLoaderConfig.getClassesFilter(), rawNestedDependencies, jvmMarkers,
+ overrideClassLoaderConfig.getParentResourcesFilter());
transformers.forEach(loader::registerTransformer);
activeSpecificTransformers(loader);
return loader;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index b5579f3ecfa67..9995b12da2932 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -125,6 +125,7 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
ofNullable(classLoaderConfiguration.getParent()).orElseGet(ContainerManager.class::getClassLoader),
ofNullable(classLoaderConfiguration.getClassesFilter()).orElseGet(() -> name -> true),
ofNullable(classLoaderConfiguration.getParentClassesFilter()).orElseGet(() -> name -> true),
+ ofNullable(classLoaderConfiguration.getParentResourcesFilter()).orElseGet(() -> name -> true),
classLoaderConfiguration.isSupportsResourceDependencies(), nestedPluginMappingResource);
if (classLoaderConfiguration.isSupportsResourceDependencies()) {
try (final InputStream mappingStream =
@@ -451,6 +452,8 @@ public static class ClassLoaderConfiguration {
private final Predicate parentClassesFilter;
+ private final Predicate parentResourcesFilter;
+
// is nested jar in jar supported (1 level only)
private final boolean supportsResourceDependencies;
From 26a5079a456672d581339ea430f3b69cc762f3d0 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 5 Jan 2026 16:32:31 +0100
Subject: [PATCH 05/37] feat(QTDI-2134): reintroduce MAVEN-INF resolve
---
...DependencyListLocalRepositoryResolver.java | 93 ++++++++++++-------
1 file changed, 59 insertions(+), 34 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
index c061f2d95caec..34687b5e82505 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Properties;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarFile;
@@ -61,44 +62,68 @@ public class MvnDependencyListLocalRepositoryResolver implements Resolver {
@Override
public Stream resolve(final ClassLoader rootLoader, final String artifact) {
- return Stream
- .of(readDependencies(ofNullable(getJar(artifact))
- .filter(Files::exists)
- .map(this::findDependenciesFile)
- .orElseGet(() -> {
- final boolean isNested;
- try (final InputStream stream = rootLoader
- .getResourceAsStream(ConfigurableClassLoader.NESTED_MAVEN_REPOSITORY + artifact)) {
- isNested = stream != null;
- } catch (final IOException e) {
- log.debug(e.getMessage(), e);
- return "";
- }
+ final String standardDeps = ofNullable(getJar(artifact))
+ .filter(Files::exists)
+ .map(this::findDependenciesFile)
+ .orElseGet(() -> {
+ final boolean isNested;
+ try (final InputStream stream = rootLoader
+ .getResourceAsStream(ConfigurableClassLoader.NESTED_MAVEN_REPOSITORY + artifact)) {
+ isNested = stream != null;
+ } catch (final IOException e) {
+ log.debug(e.getMessage(), e);
+ return "";
+ }
- if (isNested) { // we reuse ConfigurableClassLoader just to not
- // rewrite the logic but it is NOT a plugin!
- try (final ConfigurableClassLoader configurableClassLoader =
- new ConfigurableClassLoader("", new URL[0], rootLoader, name -> true,
- name -> true, new String[] { artifact }, new String[0])) {
- try (final InputStream deps =
- configurableClassLoader.getResourceAsStream(dependenciesListFile)) {
- return ofNullable(deps).map(s -> {
- try {
- return slurp(s);
- } catch (final IOException e) {
- log.debug(e.getMessage(), e);
- return "";
- }
- }).orElse("");
+ if (isNested) { // we reuse ConfigurableClassLoader just to not
+ // rewrite the logic but it is NOT a plugin!
+ try (final ConfigurableClassLoader configurableClassLoader =
+ new ConfigurableClassLoader("", new URL[0], rootLoader, name -> true,
+ name -> true, new String[] { artifact }, new String[0])) {
+ try (final InputStream deps =
+ configurableClassLoader.getResourceAsStream(dependenciesListFile)) {
+ return ofNullable(deps).map(s -> {
+ try {
+ return slurp(s);
+ } catch (final IOException e) {
+ log.debug(e.getMessage(), e);
+ return "";
}
- } catch (final IOException e) {
- log.debug(e.getMessage(), e);
- return "";
- }
+ }).orElse("");
}
-
+ } catch (final IOException e) {
+ log.debug(e.getMessage(), e);
return "";
- })));
+ }
+ }
+
+ return "";
+ });
+ final String dynamicDeps = findDynamicDependencies(rootLoader, artifact);
+ final String allDeps = Stream.of(standardDeps, dynamicDeps)
+ .filter(s -> !s.isEmpty())
+ .collect(joining(System.lineSeparator()));
+
+ return Stream.of(readDependencies(allDeps));
+ }
+
+ private String findDynamicDependencies(final ClassLoader rootLoader, final String artifact) {
+ try (final InputStream stream = rootLoader.getResourceAsStream("TALEND-INF/dynamic-dependencies.properties")) {
+ if (stream == null) {
+ return "";
+ }
+ final Properties properties = new Properties() {
+
+ {
+ load(stream);
+ }
+ };
+ final String dyndeps = properties.getProperty(artifact, "");
+ return dyndeps.replaceAll(",", System.lineSeparator());
+ } catch (final IOException e) {
+ log.debug(e.getMessage(), e);
+ return "";
+ }
}
private Path getJar(final String artifact) {
From 9589b3c29911252466220b0030054f17f6353d36 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Tue, 6 Jan 2026 18:52:06 +0100
Subject: [PATCH 06/37] feat(QTDI-2134): parentFilterResource configuration
---
.../runtime/manager/ComponentManager.java | 33 ++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 446b262fd3ebd..7f6843def8670 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -113,6 +113,7 @@
import org.apache.xbean.finder.archive.FileArchive;
import org.apache.xbean.finder.archive.FilteredArchive;
import org.apache.xbean.finder.archive.JarArchive;
+import org.apache.xbean.finder.filter.ContainsFilter;
import org.apache.xbean.finder.filter.ExcludeIncludeFilter;
import org.apache.xbean.finder.filter.Filter;
import org.apache.xbean.finder.filter.FilterList;
@@ -315,6 +316,8 @@ public String[] categories() {
// + tcomp "runtime" indeed (invisible from the components but required for the runtime
private final Filter classesFilter;
+ private final Filter resourcesFilter;
+
private final ParameterModelService parameterModelService;
private final InternationalizationServiceFactory internationalizationServiceFactory;
@@ -429,7 +432,12 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.distinct()
.map(PrefixFilter::new)
.toArray(Filter[]::new));
-
+ resourcesFilter = new FilterList(Stream.concat(
+ Stream.of("META-INF/services/"),
+ additionalParentResources())
+ .distinct()
+ .map(ContainsFilter::new)
+ .toArray(Filter[]::new));
jsonpProvider = loadJsonProvider();
jsonbProvider = loadJsonbProvider();
// these factories have memory caches so ensure we reuse them properly
@@ -463,6 +471,7 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
migrationHandlerFactory = new MigrationHandlerFactory(reflections);
final Predicate isContainerClass = name -> isContainerClass(classesFilter, name);
+ final Predicate isParentResource = name -> isParentResource(resourcesFilter, name);
final ContainerManager.ClassLoaderConfiguration defaultClassLoaderConfiguration =
ContainerManager.ClassLoaderConfiguration
.builder()
@@ -470,6 +479,7 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.parentClassesFilter(isContainerClass)
.classesFilter(isContainerClass.negate())
.supportsResourceDependencies(true)
+ .parentResourcesFilter(isParentResource)
.create();
this.container = new ContainerManager(ContainerManager.DependenciesResolutionConfiguration
.builder()
@@ -614,6 +624,16 @@ private Stream additionalContainerClasses() {
.orElseGet(Stream::empty));
}
+ private Stream additionalParentResources() {
+ return Stream
+ .concat(customizers.stream().flatMap(Customizer::parentResources),
+ ofNullable(
+ System.getProperty("talend.component.manager.classloader.container.parentResources"))
+ .map(s -> s.split(","))
+ .map(Stream::of)
+ .orElseGet(Stream::empty));
+ }
+
public static Path findM2() {
return new MavenRepositoryDefaultResolver().discover();
}
@@ -1048,6 +1068,10 @@ protected boolean isContainerClass(final Filter filter, final String name) {
return name != null && filter.accept(name);
}
+ protected boolean isParentResource(final Filter filter, final String name) {
+ return name != null && filter.accept(name);
+ }
+
@Override
public void close() {
container.close();
@@ -2210,6 +2234,13 @@ public interface Customizer {
*/
Stream containerClassesAndPackages();
+ /**
+ * @return
+ */
+ default Stream parentResources() {
+ return Stream.empty();
+ }
+
/**
* @return advanced toggle to ignore built-in beam exclusions and let this customizer override them.
*/
From 82d0275a72abe6070b3804d4e28974a667a0a599 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 8 Jan 2026 11:01:16 +0100
Subject: [PATCH 07/37] feat(QTDI-2134): doc for configuration type
---
.../pages/_partials/generated_configuration-types.adoc | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/documentation/src/main/antora/modules/ROOT/pages/_partials/generated_configuration-types.adoc b/documentation/src/main/antora/modules/ROOT/pages/_partials/generated_configuration-types.adoc
index 3fa442503bb76..00241ce1bc0c3 100644
--- a/documentation/src/main/antora/modules/ROOT/pages/_partials/generated_configuration-types.adoc
+++ b/documentation/src/main/antora/modules/ROOT/pages/_partials/generated_configuration-types.adoc
@@ -47,18 +47,18 @@ Mark a model (complex object) as being a dataset discovery configuration.
----
-= DynamicDependenciesServiceConfiguration
+= DynamicDependenciesConfiguration
-Mark a model (complex object) as being the configuration used in services annotated with @DynamicDependencies.
+Mark a model (complex object) as being the configuration expected to compute dynamic dependencies.
-- API: @org.talend.sdk.component.api.configuration.type.DynamicDependenciesServiceConfiguration
+- API: @org.talend.sdk.component.api.configuration.type.DynamicDependenciesConfiguration
- Sample:
[source,js]
----
{
"tcomp::configurationtype::name":"test",
- "tcomp::configurationtype::type":"dynamicDependenciesServiceConfiguration"
+ "tcomp::configurationtype::type":"dynamicDependenciesConfiguration"
}
----
From 7abd065f806060aaaf8dd00042fefc77a96f2165 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 12 Jan 2026 11:28:19 +0100
Subject: [PATCH 08/37] feat(QTDI-2134): fix module lookup in
dynamic-dependencies.properties
---
.../sdk/component/container/ContainerManager.java | 10 +++-------
.../MvnDependencyListLocalRepositoryResolver.java | 4 +++-
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 9995b12da2932..96ab203d34d22 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -103,10 +103,6 @@ public class ContainerManager implements Lifecycle {
private final boolean hasNestedRepository;
- private final Pattern versionWithJiraIssue = Pattern.compile("-[A-Z]{2,}-\\d+$");
-
- private final Pattern versionWithMilestone = Pattern.compile("M\\d+$");
-
public ContainerManager(final DependenciesResolutionConfiguration dependenciesResolutionConfiguration,
final ClassLoaderConfiguration classLoaderConfiguration, final Consumer containerInitializer,
final Level logInfoLevelMapping) {
@@ -265,7 +261,7 @@ public ContainerBuilder builder(final String module) {
return builder(buildAutoIdFromName(module), module);
}
- public String buildAutoIdFromName(final String module) {
+ public static String buildAutoIdFromName(final String module) {
final String[] segments = module.split(":");
if (segments.length > 2) { // == 2 can be a windows path so enforce > 2 but then
// assume it is mvn GAV
@@ -281,11 +277,11 @@ public String buildAutoIdFromName(final String module) {
if (autoId.endsWith("-SNAPSHOT")) {
autoId = autoId.substring(0, autoId.length() - "-SNAPSHOT".length());
}
- final Matcher jiraTicket = versionWithJiraIssue.matcher(autoId);
+ final Matcher jiraTicket = Pattern.compile("-[A-Z]{2,}-\\d+$").matcher(autoId);
if (jiraTicket.find()) {
autoId = autoId.substring(0, jiraTicket.start());
}
- final Matcher milestone = versionWithMilestone.matcher(autoId);
+ final Matcher milestone = Pattern.compile("M\\d+$").matcher(autoId);
if (milestone.find()) {
autoId = autoId.substring(0, milestone.start());
}
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
index 34687b5e82505..901e4caa69001 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
@@ -40,6 +40,7 @@
import java.util.zip.ZipEntry;
import org.talend.sdk.component.classloader.ConfigurableClassLoader;
+import org.talend.sdk.component.container.ContainerManager;
import org.talend.sdk.component.dependencies.Resolver;
import org.talend.sdk.component.path.PathFactory;
@@ -118,7 +119,8 @@ private String findDynamicDependencies(final ClassLoader rootLoader, final Strin
load(stream);
}
};
- final String dyndeps = properties.getProperty(artifact, "");
+ final String module = ContainerManager.buildAutoIdFromName(artifact);
+ final String dyndeps = properties.getProperty(module, "");
return dyndeps.replaceAll(",", System.lineSeparator());
} catch (final IOException e) {
log.debug(e.getMessage(), e);
From 0c0de56eebd51174fb9c55625d0b445ddc5bd3b9 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 12 Jan 2026 16:40:39 +0100
Subject: [PATCH 09/37] feat(QTDI-2134): fix NestedJarArchive in OSGI env
---
.../runtime/manager/ComponentManager.java | 29 ++++++++++++++-----
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 7f6843def8670..4df946448951e 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -925,13 +925,26 @@ private void autoDiscoverPlugins0(final boolean callers, final boolean classpath
final Enumeration componentMarkers =
Thread.currentThread().getContextClassLoader().getResources("TALEND-INF/dependencies.txt");
while (componentMarkers.hasMoreElements()) {
- File file = Files.toFile(componentMarkers.nextElement());
- if (file.getName().equals("dependencies.txt") && file.getParentFile() != null
- && file.getParentFile().getName().equals("TALEND-INF")) {
- file = file.getParentFile().getParentFile();
- }
- if (!hasPlugin(container.buildAutoIdFromName(file.getName()))) {
- addPlugin(file.getAbsolutePath());
+ final URL marker = componentMarkers.nextElement();
+ File file = Files.toFile(marker);
+ if (file != null) {
+ if (file.getName().equals("dependencies.txt") && file.getParentFile() != null
+ && file.getParentFile().getName().equals("TALEND-INF")) {
+ file = file.getParentFile().getParentFile();
+ }
+ if (!hasPlugin(container.buildAutoIdFromName(file.getName()))) {
+ addPlugin(file.getAbsolutePath());
+ }
+ } else {
+ // lookup nested jar
+ if (marker != null && "jar".equals(marker.getProtocol())) {
+ final String urlFile = marker.getFile();
+ final String jarPath = urlFile.substring(0, urlFile.lastIndexOf("!"));
+ final String jarFilePath = jarPath.substring(jarPath.lastIndexOf("/") + 1);
+ if (!hasPlugin(container.buildAutoIdFromName(jarFilePath))) {
+ addPlugin(jarPath);
+ }
+ }
}
}
} catch (final IOException e) {
@@ -1749,7 +1762,7 @@ private Archive toArchive(final String module, final OriginalId originalId,
return true;
}).map(nested -> {
if ("nested".equals(nested.getProtocol())
- || (nested.getPath() != null && nested.getPath().contains("!/MAVEN-INF/repository/"))) {
+ || (nested.getPath() != null && nested.getPath().contains(NESTED_MAVEN_REPOSITORY))) {
JarInputStream jarStream = null;
try {
jarStream = new JarInputStream(nested.openStream());
From d68b742d17157cad536d3d7c3d8d059541b9361f Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Tue, 3 Feb 2026 15:57:02 +0100
Subject: [PATCH 10/37] feat(QTDI-2134): remove "META-INF/services" from
default parent resources
---
.../sdk/component/runtime/manager/ComponentManager.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 4df946448951e..4c3cbe0aa0cfa 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -432,9 +432,7 @@ public ComponentManager(final Path m2, final String dependenciesResource, final
.distinct()
.map(PrefixFilter::new)
.toArray(Filter[]::new));
- resourcesFilter = new FilterList(Stream.concat(
- Stream.of("META-INF/services/"),
- additionalParentResources())
+ resourcesFilter = new FilterList(additionalParentResources()
.distinct()
.map(ContainsFilter::new)
.toArray(Filter[]::new));
From a27725a6c22cdb57f19b944215a497321f7669af Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 9 Feb 2026 11:42:08 +0100
Subject: [PATCH 11/37] feat(QTDI-2134): analyse classpath and use it for
resolve
---
.../component/container/ContainerManager.java | 67 ++++++++++++++++++-
1 file changed, 65 insertions(+), 2 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 19d920a3e0107..979dc6d22c68e 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -103,6 +103,8 @@ public class ContainerManager implements Lifecycle {
private final boolean hasNestedRepository;
+ private final List runtimeClasspath = new ArrayList<>();
+
public ContainerManager(final DependenciesResolutionConfiguration dependenciesResolutionConfiguration,
final ClassLoaderConfiguration classLoaderConfiguration, final Consumer containerInitializer,
final Level logInfoLevelMapping) {
@@ -114,7 +116,7 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
if (log.isDebugEnabled()) {
getSystemInformations();
}
-
+ readRuntimeClasspath();
final String nestedPluginMappingResource = ofNullable(classLoaderConfiguration.getNestedPluginMappingResource())
.orElse("TALEND-INF/plugins.properties");
this.classLoaderConfiguration = new ClassLoaderConfiguration(
@@ -168,6 +170,55 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
}
}
+ /**
+ * Catch the runtime classpath to be able to resolve plugins from it if needed.
+ * This may be useful in case of running in an environment like jobServer where
+ * the classpath is not linked to a single directory like `../lib` but scattered
+ * through cache dirs.
+ */
+ private void readRuntimeClasspath() {
+ try {
+ final RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
+ final String classpath = rt.getClassPath();
+ if ("classpath.jar".equals(classpath)) {
+ // read MANIFEST.MF in "classpath.jar" to get classpath jars' list
+ final Path cpJar = PathFactory.get("classpath.jar");
+ if (Files.exists(cpJar)) {
+ try (InputStream is = Files.newInputStream(cpJar)) {
+ final java.util.jar.JarInputStream jarStream = new java.util.jar.JarInputStream(is);
+ final java.util.jar.Manifest manifest = jarStream.getManifest();
+ if (manifest != null) {
+ final String cp = manifest.getMainAttributes().getValue("Class-Path");
+ if (cp != null) {
+ Stream.of(cp.split(" "))
+ .filter(c -> !c.equals(".") || !c.trim().isEmpty())
+ .map(PathFactory::get)
+ .filter(Files::exists)
+ .forEach(p -> {
+ runtimeClasspath.add(p.toAbsolutePath().toString());
+ log.debug("[sysinfo] Runtime classpath entry (manifest): "
+ + p.toAbsolutePath());
+ });
+ }
+ }
+ } catch (IOException e) {
+ info("Unable to read classpath.jar manifest: " + e.getMessage());
+ }
+ }
+ } else {
+ Stream.of(classpath.split(File.pathSeparator))
+ .map(PathFactory::get)
+ .filter(Files::exists)
+ .forEach(p -> {
+ runtimeClasspath.add(p.toAbsolutePath().toString());
+ log.debug("[sysinfo] Runtime classpath entry: " + p.toAbsolutePath());
+ });
+ }
+ } catch (Exception e) {
+ info("Unable to get runtime classpath: " + e.getMessage());
+ }
+ }
+
public File getRootRepositoryLocation() {
return rootRepositoryLocation.toFile();
}
@@ -247,12 +298,24 @@ public Path resolve(final String path) {
return file;
}
+ final String artifactName = path.substring(path.lastIndexOf('/') + 1);
// from job lib folder
- final Path libFile = rootRepositoryLocation.resolve(path.substring(path.lastIndexOf('/') + 1));
+ final Path libFile = rootRepositoryLocation.resolve(artifactName);
if (Files.exists(libFile)) {
return libFile;
}
+ // try to find it in the runtime classpath
+ final Optional rtcpFile = runtimeClasspath.stream()
+ .filter(p -> p.endsWith(artifactName) && p.contains(File.separator + artifactName))
+ .findFirst();
+ if (rtcpFile.isPresent()) {
+ final Path cpFile = PathFactory.get(rtcpFile.get());
+ if (Files.exists(cpFile)) {
+ return cpFile;
+ }
+ }
+
// will be filtered later
return file;
}
From 238294cee300bf5fa745e4b69c317198c07654d6 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Tue, 10 Feb 2026 14:16:47 +0100
Subject: [PATCH 12/37] feat(QTDI-2134): improve classpath feed for resolve
---
.../component/container/ContainerManager.java | 115 ++++++++++--------
1 file changed, 66 insertions(+), 49 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 979dc6d22c68e..c77125fc768f9 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -15,6 +15,7 @@
*/
package org.talend.sdk.component.container;
+import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.list;
import static java.util.Optional.of;
@@ -103,6 +104,7 @@ public class ContainerManager implements Lifecycle {
private final boolean hasNestedRepository;
+ @Getter
private final List runtimeClasspath = new ArrayList<>();
public ContainerManager(final DependenciesResolutionConfiguration dependenciesResolutionConfiguration,
@@ -170,55 +172,6 @@ public ContainerManager(final DependenciesResolutionConfiguration dependenciesRe
}
}
- /**
- * Catch the runtime classpath to be able to resolve plugins from it if needed.
- * This may be useful in case of running in an environment like jobServer where
- * the classpath is not linked to a single directory like `../lib` but scattered
- * through cache dirs.
- */
- private void readRuntimeClasspath() {
- try {
- final RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
- final String classpath = rt.getClassPath();
- if ("classpath.jar".equals(classpath)) {
- // read MANIFEST.MF in "classpath.jar" to get classpath jars' list
- final Path cpJar = PathFactory.get("classpath.jar");
- if (Files.exists(cpJar)) {
- try (InputStream is = Files.newInputStream(cpJar)) {
- final java.util.jar.JarInputStream jarStream = new java.util.jar.JarInputStream(is);
- final java.util.jar.Manifest manifest = jarStream.getManifest();
- if (manifest != null) {
- final String cp = manifest.getMainAttributes().getValue("Class-Path");
- if (cp != null) {
- Stream.of(cp.split(" "))
- .filter(c -> !c.equals(".") || !c.trim().isEmpty())
- .map(PathFactory::get)
- .filter(Files::exists)
- .forEach(p -> {
- runtimeClasspath.add(p.toAbsolutePath().toString());
- log.debug("[sysinfo] Runtime classpath entry (manifest): "
- + p.toAbsolutePath());
- });
- }
- }
- } catch (IOException e) {
- info("Unable to read classpath.jar manifest: " + e.getMessage());
- }
- }
- } else {
- Stream.of(classpath.split(File.pathSeparator))
- .map(PathFactory::get)
- .filter(Files::exists)
- .forEach(p -> {
- runtimeClasspath.add(p.toAbsolutePath().toString());
- log.debug("[sysinfo] Runtime classpath entry: " + p.toAbsolutePath());
- });
- }
- } catch (Exception e) {
- info("Unable to get runtime classpath: " + e.getMessage());
- }
- }
-
public File getRootRepositoryLocation() {
return rootRepositoryLocation.toFile();
}
@@ -479,6 +432,70 @@ private void getSystemInformations() {
}
}
+ /**
+ * Catch the runtime classpath to be able to resolve plugins from it if needed.
+ * This may be useful in case of running in an environment like jobServer where
+ * the classpath is not linked to a single directory like `../lib` but scattered
+ * through cache dirs.
+ */
+ private void readRuntimeClasspath() {
+ try {
+ final RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
+ final String classpath = rt.getClassPath();
+ Stream.of(classpath.split(File.pathSeparator))
+ .filter(p -> p.endsWith(".jar"))
+ .map(PathFactory::get)
+ .filter(Files::exists)
+ .forEach(p -> {
+ if ("classpath.jar".equals(p.getFileName().toString())) {
+ runtimeClasspath.addAll(getClasspathFromJar(p));
+ } else {
+ runtimeClasspath.add(p.toAbsolutePath().toString());
+ log.debug("[sysinfo] Runtime classpath entry: " + p.toAbsolutePath());
+ }
+ });
+ // cleanup the classpath from entries that are known as framework artifacts
+ final Predicate frameworkFilter = p -> p.matches(".*" + File.separatorChar +
+ "(container-core|component-api|component-spi|component-runtime-impl|component-runtime-manager|" +
+ "component-spi|component-runtime-design-extension|component-runtime-di|geronimo|johnzon|xbean|slf4j|log4j|log4j2)"
+ + "-.*jar$");
+ runtimeClasspath.removeIf(p -> frameworkFilter.test(p));
+ } catch (Exception e) {
+ info("Unable to get runtime classpath: " + e.getMessage());
+ }
+ }
+
+ /**
+ * In some environments like jobServer, the classpath can be set to a single "classpath.jar" file containing in its
+ * manifest the list of real classpath entries.
+ * In this case we need to read MANIFEST.MF in "classpath.jar" to get classpath jars' list
+ *
+ * @param jar
+ * @return classpath entries defined in the manifest of the given jar, or an empty list if the manifest can't be
+ * read or doesn't contain a Class-Path entry
+ */
+ private List getClasspathFromJar(final Path jar) {
+ try (final java.util.jar.JarFile jarFile = new java.util.jar.JarFile(jar.toFile())) {
+ final java.util.jar.Manifest manifest = jarFile.getManifest();
+ if (manifest != null) {
+ final String cp = manifest.getMainAttributes().getValue("Class-Path");
+ if (cp != null) {
+ return Stream.of(cp.split(" "))
+ .filter(c -> !c.equals(".") || !c.trim().isEmpty())
+ .filter(c -> c.endsWith(".jar"))
+ .map(PathFactory::get)
+ .filter(Files::exists)
+ .map(p -> p.toAbsolutePath().toString())
+ .peek(p -> log.debug("[sysinfo] Runtime classpath entry from manifest: " + p))
+ .collect(toList());
+ }
+ }
+ } catch (IOException e) {
+ info("Unable to read " + jar + " manifest: " + e.getMessage());
+ }
+ return emptyList();
+ }
+
@Override
public void close() {
lifecycle.closeIfNeeded(() -> {
From db4dd22f3f107a61feb834a050a13d6d3db6ce25 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Tue, 10 Feb 2026 15:17:26 +0100
Subject: [PATCH 13/37] feat(QTDI-2134): fix framework filter
---
.../talend/sdk/component/container/ContainerManager.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index c77125fc768f9..ba238dd2419be 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -456,8 +456,10 @@ private void readRuntimeClasspath() {
});
// cleanup the classpath from entries that are known as framework artifacts
final Predicate frameworkFilter = p -> p.matches(".*" + File.separatorChar +
- "(container-core|component-api|component-spi|component-runtime-impl|component-runtime-manager|" +
- "component-spi|component-runtime-design-extension|component-runtime-di|geronimo|johnzon|xbean|slf4j|log4j|log4j2)"
+ "(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
+ "component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
+ "geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
+ "slf4j-api|slf4j-log4j12|slf4j-reload4j|xbean-asm9-shaded|xbean-finder-shaded|xbean-reflect-)"
+ "-.*jar$");
runtimeClasspath.removeIf(p -> frameworkFilter.test(p));
} catch (Exception e) {
From 146a4689fd3f32bedffdc160b869e47156abb27d Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Tue, 10 Feb 2026 15:53:23 +0100
Subject: [PATCH 14/37] feat(QTDI-2134): fix framework filter xbean + rhino
---
.../org/talend/sdk/component/container/ContainerManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index ba238dd2419be..de12cf9651967 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -459,7 +459,7 @@ private void readRuntimeClasspath() {
"(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
"component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
"geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
- "slf4j-api|slf4j-log4j12|slf4j-reload4j|xbean-asm9-shaded|xbean-finder-shaded|xbean-reflect-)"
+ "rhino|slf4j-api|slf4j-log4j12|slf4j-reload4j|xbean-asm9-shaded|xbean-finder-shaded|xbean-reflect)"
+ "-.*jar$");
runtimeClasspath.removeIf(p -> frameworkFilter.test(p));
} catch (Exception e) {
From 60bedfcdba6dc92937d7d2538c6d117fbdc90939 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 26 Feb 2026 17:45:39 +0100
Subject: [PATCH 15/37] feat(QTDI-2134): add interface javadoc
---
.../talend/sdk/component/runtime/manager/ComponentManager.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index ffc740fd8d580..968a11df65593 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -2246,7 +2246,8 @@ public interface Customizer {
Stream containerClassesAndPackages();
/**
- * @return
+ * @return a stream of string representing resources. They will be considered
+ * as loaded from the "container" (ComponentManager loader) and not the components classloaders.
*/
default Stream parentResources() {
return Stream.empty();
From 28c4dd1d8d2c30184222f755cb6ce5acab21dc0e Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 26 Feb 2026 17:45:57 +0100
Subject: [PATCH 16/37] feat(QTDI-2134): change visibility for testing
---
.../org/talend/sdk/component/container/ContainerManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index de12cf9651967..6bbf41839a188 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -476,7 +476,7 @@ private void readRuntimeClasspath() {
* @return classpath entries defined in the manifest of the given jar, or an empty list if the manifest can't be
* read or doesn't contain a Class-Path entry
*/
- private List getClasspathFromJar(final Path jar) {
+ public List getClasspathFromJar(final Path jar) {
try (final java.util.jar.JarFile jarFile = new java.util.jar.JarFile(jar.toFile())) {
final java.util.jar.Manifest manifest = jarFile.getManifest();
if (manifest != null) {
From 62ce939e16ccfceaadf9962cfb39aaacdc6a17ec Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 26 Feb 2026 17:46:35 +0100
Subject: [PATCH 17/37] feat(QTDI-2134): add some container-core tests
---
.../sdk/component/ContainerManagerTest.java | 73 +++++++++++++++++++
.../ConfigurableClassLoaderTest.java | 31 ++++++++
...ndencyListLocalRepositoryResolverTest.java | 25 +++++++
3 files changed, 129 insertions(+)
diff --git a/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java b/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java
index bd96c1d5ef643..8287d9da6c766 100644
--- a/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java
+++ b/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java
@@ -23,16 +23,25 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
import org.talend.sdk.component.container.Container;
import org.talend.sdk.component.container.ContainerListener;
import org.talend.sdk.component.container.ContainerManager;
@@ -151,6 +160,70 @@ void close(final TempJars jars) {
});
}
+ @Test
+ void getClasspathFromJarWithValidManifestClassPath(@TempDir final Path tempDir) throws Exception {
+ final File jar1 = createJarFile(tempDir, "lib1.jar");
+ final File jar2 = createJarFile(tempDir, "lib2.jar");
+ final File mainJar = createJarWithManifest(tempDir, "main.jar",
+ jar1.getAbsolutePath() + " " + jar2.getAbsolutePath());
+
+ try (final ContainerManager containerManager = createDefaultManager()) {
+ final List result = containerManager.getClasspathFromJar(mainJar.toPath());
+
+ assertEquals(2, result.size());
+ assertTrue(result.contains(jar1.getAbsolutePath()));
+ assertTrue(result.contains(jar2.getAbsolutePath()));
+ }
+ }
+
+ @Test
+ void getClasspathFromJarWithNoManifest(@TempDir final Path tempDir) throws Exception {
+ final File jar = createJarFile(tempDir, "noManifest.jar");
+
+ try (final ContainerManager containerManager = createDefaultManager()) {
+ final List result = containerManager.getClasspathFromJar(jar.toPath());
+
+ assertTrue(result.isEmpty());
+ }
+ }
+
+ @Test
+ void getClasspathFromJarWithEmptyClassPath(@TempDir final Path tempDir) throws Exception {
+ final File jar = createJarWithManifest(tempDir, "emptyCP.jar", "");
+
+ try (final ContainerManager containerManager = createDefaultManager()) {
+ final List result = containerManager.getClasspathFromJar(jar.toPath());
+
+ assertTrue(result.isEmpty());
+ }
+ }
+
+ private static File createJarFile(final Path tempDir, final String fileName) throws IOException {
+ final File jar = new File(tempDir.toFile(), fileName);
+ try (final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jar))) {
+ jos.putNextEntry(new ZipEntry("dummy.txt"));
+ jos.write("dummy content".getBytes(StandardCharsets.UTF_8));
+ jos.closeEntry();
+ }
+ return jar;
+ }
+
+ private static File createJarWithManifest(final Path tempDir, final String fileName, final String classPath)
+ throws IOException {
+ final File jar = new File(tempDir.toFile(), fileName);
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ if (classPath != null && !classPath.isEmpty()) {
+ manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPath);
+ }
+ try (final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jar), manifest)) {
+ jos.putNextEntry(new ZipEntry("dummy.txt"));
+ jos.write("dummy content".getBytes(StandardCharsets.UTF_8));
+ jos.closeEntry();
+ }
+ return jar;
+ }
+
private File createZiplockJar(final TempJars jars) {
return jars.create("org.apache.tomee:ziplock:jar:" +
"8.0.14");
diff --git a/container/container-core/src/test/java/org/talend/sdk/component/classloader/ConfigurableClassLoaderTest.java b/container/container-core/src/test/java/org/talend/sdk/component/classloader/ConfigurableClassLoaderTest.java
index 3eca4c0a51ee3..20800a7e4ec02 100644
--- a/container/container-core/src/test/java/org/talend/sdk/component/classloader/ConfigurableClassLoaderTest.java
+++ b/container/container-core/src/test/java/org/talend/sdk/component/classloader/ConfigurableClassLoaderTest.java
@@ -20,6 +20,7 @@
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -344,6 +345,36 @@ void cacheableClasses(@TempDir final File temporaryFolder) throws Exception {
}
}
+ @Test
+ void parentResourceFiltering(@TempDir final File temporaryFolder) throws IOException {
+ final File parentJar = new File(temporaryFolder, "multipleResourceFiltering.jar");
+ temporaryFolder.mkdirs();
+ try (final JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(parentJar))) {
+ outputStream.putNextEntry(new JarEntry("META-INF/services/allowed.Service"));
+ outputStream.write("impl.AllowedService".getBytes(StandardCharsets.UTF_8));
+ outputStream.closeEntry();
+
+ outputStream.putNextEntry(new JarEntry("META-INF/services/filtered.Service"));
+ outputStream.write("impl.FilteredService".getBytes(StandardCharsets.UTF_8));
+ outputStream.closeEntry();
+ }
+
+ try (final URLClassLoader parent =
+ new URLClassLoader(new URL[] { parentJar.toURI().toURL() },
+ Thread.currentThread().getContextClassLoader());
+ final ConfigurableClassLoader loader =
+ new ConfigurableClassLoader("", new URL[0], parent,
+ name -> true, name -> false, new String[0], new String[0],
+ name -> name.contains("allowed"))) {
+ // Allowed service should be found via getResources
+ final Enumeration allowedResources = loader.getResources("META-INF/services/allowed.Service");
+ assertTrue(allowedResources.hasMoreElements());
+ // Filtered service is NOT accessible via getResources (filtered out by resourcesFilter)
+ final Enumeration filteredResources = loader.getResources("META-INF/services/filtered.Service");
+ assertFalse(filteredResources.hasMoreElements());
+ }
+ }
+
private void assertXmlReader() throws SAXException {
final XMLReader xmlReader = XMLReaderFactory.createXMLReader();
final Class extends XMLReader> clazz = xmlReader.getClass();
diff --git a/container/container-core/src/test/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolverTest.java b/container/container-core/src/test/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolverTest.java
index f3c66f6cd71ca..24dc8e15ac050 100644
--- a/container/container-core/src/test/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolverTest.java
+++ b/container/container-core/src/test/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolverTest.java
@@ -33,6 +33,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.talend.sdk.component.container.ContainerManager;
import org.talend.sdk.component.test.dependencies.DependenciesTxtBuilder;
class MvnDependencyListLocalRepositoryResolverTest {
@@ -95,4 +96,28 @@ void nestedDependencyWithJira(@TempDir final File temporaryFolder) throws IOExce
"org/apache/tomee/javaee-api/7.0-1/javaee-api-7.0-1.jar"), toResolve);
}
}
+
+ @Test
+ void resolveWithDynamicDependencies(@TempDir final File temporaryFolder) throws Exception {
+ final File file = new File(temporaryFolder, UUID.randomUUID().toString() + ".jar");
+ file.getParentFile().mkdirs();
+ final String module = ContainerManager.buildAutoIdFromName("dummy-1.0.0.jar");
+ final String deps = "org.apache.tomee:ziplock:jar:8.0.14:runtime,org.apache.tomee:javaee-api:jar:7.0-1:compile";
+ try (final JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) {
+ jar.putNextEntry(new ZipEntry("TALEND-INF/dynamic-dependencies.properties"));
+ jar.write((module + "=" + deps).getBytes(StandardCharsets.UTF_8));
+ jar.closeEntry();
+ }
+
+ try (final URLClassLoader tempLoader =
+ new URLClassLoader(new URL[] { file.toURI().toURL() }, getSystemClassLoader())) {
+ final List toResolve =
+ new MvnDependencyListLocalRepositoryResolver("TALEND-INF/dependencies.txt", d -> null)
+ .resolve(tempLoader, "foo/bar/dummy/1.0.0/dummy-1.0.0.jar")
+ .map(Artifact::toPath)
+ .collect(toList());
+ assertEquals(asList("org/apache/tomee/ziplock/8.0.14/ziplock-8.0.14.jar",
+ "org/apache/tomee/javaee-api/7.0-1/javaee-api-7.0-1.jar"), toResolve);
+ }
+ }
}
From 24999a3a00c1970e08c96aeac5f35f3880dfc3de Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 26 Feb 2026 18:32:10 +0100
Subject: [PATCH 18/37] feat(QTDI-2134): add some optimizations from pr comment
---
.../sdk/component/container/ContainerManager.java | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 6bbf41839a188..5d84a0e8daacd 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -78,6 +78,10 @@ public class ContainerManager implements Lifecycle {
private static final Consumer NOOP_CUSTOMIZER = c -> {
};
+ private static final Pattern JIRA_TICKET_PATTERN = Pattern.compile("-[A-Z]{2,}-\\d+$");
+
+ private static final Pattern MILESTONE_PATTERN = Pattern.compile("M\\d+$");
+
private final ConcurrentMap containers = new ConcurrentHashMap<>();
private final ClassLoaderConfiguration classLoaderConfiguration;
@@ -293,11 +297,11 @@ public static String buildAutoIdFromName(final String module) {
if (autoId.endsWith("-SNAPSHOT")) {
autoId = autoId.substring(0, autoId.length() - "-SNAPSHOT".length());
}
- final Matcher jiraTicket = Pattern.compile("-[A-Z]{2,}-\\d+$").matcher(autoId);
+ final Matcher jiraTicket = JIRA_TICKET_PATTERN.matcher(autoId);
if (jiraTicket.find()) {
autoId = autoId.substring(0, jiraTicket.start());
}
- final Matcher milestone = Pattern.compile("M\\d+$").matcher(autoId);
+ final Matcher milestone = MILESTONE_PATTERN.matcher(autoId);
if (milestone.find()) {
autoId = autoId.substring(0, milestone.start());
}
@@ -652,4 +656,4 @@ private Stream getBuiltInClasspath(final String moduleLocation) {
return resolver.resolve(classLoaderConfiguration.getParent(), moduleLocation);
}
}
-}
\ No newline at end of file
+}
From cb48ba7f7a3750643415de4ffdcc36184e8b1979 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 26 Feb 2026 18:50:04 +0100
Subject: [PATCH 19/37] feat(QTDI-2134): fix wrong stream filter
---
.../org/talend/sdk/component/container/ContainerManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 5d84a0e8daacd..844c8c9d71077 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -487,7 +487,7 @@ public List getClasspathFromJar(final Path jar) {
final String cp = manifest.getMainAttributes().getValue("Class-Path");
if (cp != null) {
return Stream.of(cp.split(" "))
- .filter(c -> !c.equals(".") || !c.trim().isEmpty())
+ .filter(c -> !c.equals(".") && !c.trim().isEmpty())
.filter(c -> c.endsWith(".jar"))
.map(PathFactory::get)
.filter(Files::exists)
From 4e612ee93af9254f572f6a574fae2fce4d03439f Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 27 Feb 2026 10:09:13 +0100
Subject: [PATCH 20/37] feat(QTDI-2134): pr comments flow
---
.../runtime/manager/ComponentManager.java | 4 ++--
.../classloader/ConfigurableClassLoader.java | 1 -
.../component/container/ContainerManager.java | 19 ++++++++++++-------
...DependencyListLocalRepositoryResolver.java | 2 +-
4 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 968a11df65593..0cefc2a72e366 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -930,7 +930,7 @@ private void autoDiscoverPlugins0(final boolean callers, final boolean classpath
&& file.getParentFile().getName().equals("TALEND-INF")) {
file = file.getParentFile().getParentFile();
}
- if (!hasPlugin(container.buildAutoIdFromName(file.getName()))) {
+ if (!hasPlugin(ContainerManager.buildAutoIdFromName(file.getName()))) {
addPlugin(file.getAbsolutePath());
}
} else {
@@ -939,7 +939,7 @@ private void autoDiscoverPlugins0(final boolean callers, final boolean classpath
final String urlFile = marker.getFile();
final String jarPath = urlFile.substring(0, urlFile.lastIndexOf("!"));
final String jarFilePath = jarPath.substring(jarPath.lastIndexOf("/") + 1);
- if (!hasPlugin(container.buildAutoIdFromName(jarFilePath))) {
+ if (!hasPlugin(ContainerManager.buildAutoIdFromName(jarFilePath))) {
addPlugin(jarPath);
}
}
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index f86c2acfe5256..a084ea937c293 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -490,7 +490,6 @@ private boolean isNestedDependencyResource(final String name) {
private boolean isInJvm(final URL resource) {
// Services and parent allowed resources that should always be found by top level classloader.
- // By default, META-INF/services/ is always allowed otherwise SPI won't work properly in nested environments.
// Warning: selection shouldn't be too generic! Use very specific paths only like jndi.properties.
if (resourcesFilter.test(resource.getFile())) {
return true;
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 844c8c9d71077..ee3db90a79219 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -78,6 +78,13 @@ public class ContainerManager implements Lifecycle {
private static final Consumer NOOP_CUSTOMIZER = c -> {
};
+ private static final Pattern FRAMEWORK_JAR_PATTERN = Pattern.compile(".*" + File.separatorChar +
+ "(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
+ "component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
+ "geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
+ "rhino|slf4j-api|slf4j-log4j12|slf4j-reload4j|xbean-asm9-shaded|xbean-finder-shaded|xbean-reflect)" +
+ "-.*jar$");
+
private static final Pattern JIRA_TICKET_PATTERN = Pattern.compile("-[A-Z]{2,}-\\d+$");
private static final Pattern MILESTONE_PATTERN = Pattern.compile("M\\d+$");
@@ -108,7 +115,6 @@ public class ContainerManager implements Lifecycle {
private final boolean hasNestedRepository;
- @Getter
private final List runtimeClasspath = new ArrayList<>();
public ContainerManager(final DependenciesResolutionConfiguration dependenciesResolutionConfiguration,
@@ -421,6 +427,10 @@ private String getJre() {
.orElseThrow(IllegalArgumentException::new);
}
+ public List getRuntimeClasspath() {
+ return Collections.unmodifiableList(runtimeClasspath);
+ }
+
private void getSystemInformations() {
try {
final RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
@@ -459,12 +469,7 @@ private void readRuntimeClasspath() {
}
});
// cleanup the classpath from entries that are known as framework artifacts
- final Predicate frameworkFilter = p -> p.matches(".*" + File.separatorChar +
- "(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
- "component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
- "geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
- "rhino|slf4j-api|slf4j-log4j12|slf4j-reload4j|xbean-asm9-shaded|xbean-finder-shaded|xbean-reflect)"
- + "-.*jar$");
+ final Predicate frameworkFilter = p -> FRAMEWORK_JAR_PATTERN.matcher(p).matches();
runtimeClasspath.removeIf(p -> frameworkFilter.test(p));
} catch (Exception e) {
info("Unable to get runtime classpath: " + e.getMessage());
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
index 100560695739f..68c3d14ba2679 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
@@ -121,7 +121,7 @@ private String findDynamicDependencies(final ClassLoader rootLoader, final Strin
};
final String module = ContainerManager.buildAutoIdFromName(artifact);
final String dyndeps = properties.getProperty(module, "");
- return dyndeps.replaceAll(",", System.lineSeparator());
+ return dyndeps.replace(",", System.lineSeparator());
} catch (final IOException e) {
log.debug(e.getMessage(), e);
return "";
From 6a4caf179ec38ebd4fa0f062962fb045d95554f0 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 16 Mar 2026 16:27:27 +0100
Subject: [PATCH 21/37] feat(QTDI-2134): fix platform specific regexp
---
.../org/talend/sdk/component/container/ContainerManager.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index ee3db90a79219..b0320cf3a9097 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -78,7 +78,8 @@ public class ContainerManager implements Lifecycle {
private static final Consumer NOOP_CUSTOMIZER = c -> {
};
- private static final Pattern FRAMEWORK_JAR_PATTERN = Pattern.compile(".*" + File.separatorChar +
+ private static final Pattern FRAMEWORK_JAR_PATTERN = Pattern.compile(".*" +
+ Matcher.quoteReplacement(File.separator) +
"(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
"component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
"geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
From 326dd2b66ffb7c4ec017b602d7785c6ebaf776f5 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Mon, 16 Mar 2026 17:07:40 +0100
Subject: [PATCH 22/37] feat(QTDI-2134): fix platform specific regexp (fmt
space)
---
.../org/talend/sdk/component/container/ContainerManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index b0320cf3a9097..d63d0624c2236 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -79,7 +79,7 @@ public class ContainerManager implements Lifecycle {
};
private static final Pattern FRAMEWORK_JAR_PATTERN = Pattern.compile(".*" +
- Matcher.quoteReplacement(File.separator) +
+ Matcher.quoteReplacement(File.separator) +
"(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
"component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
"geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
From 44a6bf643df9c0df99faadfb84efa12feaf80b4b Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 11:44:13 +0100
Subject: [PATCH 23/37] feat(QTDI-2134): Improve resolver to customize the
volatile classloader
---
.../dependency/ClassLoaderDefinition.java | 63 +++++++++
.../api/service/dependency/Resolver.java | 18 +++
component-runtime-manager/pom.xml | 10 ++
.../runtime/manager/service/ResolverImpl.java | 26 +++-
.../manager/service/ResolverImplTest.java | 125 +++++++++++++++++-
container/container-core/pom.xml | 5 +
.../component/container/ContainerManager.java | 3 +-
7 files changed, 239 insertions(+), 11 deletions(-)
create mode 100644 component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
new file mode 100644
index 0000000000000..53ef315022cdb
--- /dev/null
+++ b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2006-2026 Talend Inc. - www.talend.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.talend.sdk.component.api.service.dependency;
+
+import java.util.function.Predicate;
+
+public interface ClassLoaderDefinition {
+
+ /**
+ *
+ * @return the parent classloader to use for the classloader to create.
+ */
+ ClassLoader getParent();
+
+ /**
+ *
+ * @return a filter to apply on the classes to load from the classloader to create. If the filter returns false for
+ * a class, the classloader to create will try to load it from its parent.
+ */
+ Predicate getClassesFilter();
+
+ /**
+ *
+ * @return a filter to apply on the classes to load from the parent classloader. If the filter returns false for a
+ * class, the classloader to create will try to load it by itself instead of delegating to its parent.
+ */
+ Predicate getParentClassesFilter();
+
+ /**
+ *
+ * @return a filter to apply on the resources to load from the parent classloader. If the filter returns false for a
+ * resource, the classloader to create will try to load it by itself instead of delegating to its parent.
+ */
+ Predicate getParentResourcesFilter();
+
+ /**
+ *
+ * @return true if the classloader to create should support resource dependencies (i.e. the dependencies.txt can
+ * also list resources to load and not only classes). Note that if this is true, the classloader to create will try
+ * to load resources by itself instead of delegating to its parent.
+ *
+ */
+ boolean isSupportsResourceDependencies();
+
+ /**
+ *
+ * @return the resource path to the plugins mapping file ("TALEND-INF/plugins.properties").
+ */
+ String getNestedPluginMappingResource();
+}
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
index 71211111fddd1..03f6ca21d07f7 100644
--- a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
+++ b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
@@ -46,6 +46,24 @@ default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs
new ByteArrayInputStream(String.join("\n", gavs).getBytes(StandardCharsets.UTF_8)));
}
+ /**
+ * Creates a classloader from the passed classloader configuration and descriptor (dependencies.txt).
+ *
+ * WARNING: note it is very important to close the descriptor once no more used otherwise
+ * you can leak memory.
+ *
+ * @param descriptor the dependencies.txt InputStream.
+ * @param configuration
+ * @return the classloader initialized with the configuration provided and the resolved dependencies.
+ */
+ ClassLoaderDescriptor mapDescriptorToClassLoader(InputStream descriptor, final ClassLoaderDefinition configuration);
+
+ default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs,
+ final ClassLoaderDefinition configuration) {
+ return mapDescriptorToClassLoader(
+ new ByteArrayInputStream(String.join("\n", gavs).getBytes(StandardCharsets.UTF_8)), configuration);
+ }
+
/**
* Resolves the dependencies from the descriptor passed as an InputStream.
*
diff --git a/component-runtime-manager/pom.xml b/component-runtime-manager/pom.xml
index 844be9df2df8c..73b8286fb3e71 100644
--- a/component-runtime-manager/pom.xml
+++ b/component-runtime-manager/pom.xml
@@ -145,6 +145,7 @@
${project.build.directory}/test-dependencies/org/apache/tomee/openejb-itests-beans/${ziplock.test.version}
openejb-itests-beans-${ziplock.test.version}.jar
+
org.apache.tomee
arquillian-tomee-codi-tests
@@ -154,6 +155,15 @@
${project.build.directory}/test-dependencies/org/apache/tomee/arquillian-tomee-codi-tests/8.0.9
arquillian-tomee-codi-tests-8.0.9.jar
+
+ org.apache.poi
+ poi-ooxml
+ 5.4.1
+ jar
+ true
+ ${project.build.directory}/test-dependencies/org/apache/poi/poi-ooxml/5.4.1
+ poi-ooxml-5.4.1.jar
+
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
index 4f0d48b785bf5..3b94753405432 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
@@ -31,8 +31,10 @@
import java.util.Collection;
import java.util.function.Function;
+import org.talend.sdk.component.api.service.dependency.ClassLoaderDefinition;
import org.talend.sdk.component.api.service.dependency.Resolver;
import org.talend.sdk.component.classloader.ConfigurableClassLoader;
+import org.talend.sdk.component.container.ContainerManager.ClassLoaderConfiguration;
import org.talend.sdk.component.dependencies.maven.Artifact;
import org.talend.sdk.component.dependencies.maven.MvnDependencyListLocalRepositoryResolver;
import org.talend.sdk.component.runtime.serialization.SerializableService;
@@ -48,6 +50,19 @@ public class ResolverImpl implements Resolver, Serializable {
@Override
public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descriptor) {
+ return mapDescriptorToClassLoader(descriptor,
+ ClassLoaderConfiguration.builder()
+ .parent(Thread.currentThread().getContextClassLoader())
+ .classesFilter(it -> true)
+ .parentClassesFilter(it -> false)
+ .supportsResourceDependencies(true)
+ .parentResourcesFilter(it -> true)
+ .create());
+ }
+
+ @Override
+ public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descriptor,
+ final ClassLoaderDefinition configuration) {
final Collection urls = new ArrayList<>();
final Collection nested = new ArrayList<>();
final Collection resolved = new ArrayList<>();
@@ -72,10 +87,15 @@ public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descri
resolved.add(artifact.toCoordinate());
} // else will be missing
});
+ final ClassLoader parent = configuration.getParent();
final ConfigurableClassLoader volatileLoader = new ConfigurableClassLoader(plugin + "#volatile-resolver",
- urls.toArray(new URL[0]), classLoader, it -> false, it -> true, nested.toArray(new String[0]),
- ConfigurableClassLoader.class.isInstance(classLoader)
- ? ConfigurableClassLoader.class.cast(classLoader).getJvmMarkers()
+ urls.toArray(new URL[0]),
+ parent,
+ configuration.getParentClassesFilter(),
+ configuration.getClassesFilter(),
+ nested.toArray(new String[0]),
+ ConfigurableClassLoader.class.isInstance(parent)
+ ? ConfigurableClassLoader.class.cast(parent).getJvmMarkers()
: new String[] { "" });
return new ClassLoaderDescriptorImpl(volatileLoader, resolved);
} catch (final IOException e) {
diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
index 2d6519bf6f3b9..5b1f12702158f 100644
--- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
+++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
@@ -18,6 +18,8 @@
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
@@ -30,25 +32,46 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Properties;
import java.util.UUID;
+import java.util.function.Function;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.talend.sdk.component.api.service.dependency.ClassLoaderDefinition;
import org.talend.sdk.component.api.service.dependency.Resolver;
import org.talend.sdk.component.classloader.ConfigurableClassLoader;
+import org.talend.sdk.component.container.ContainerManager;
import org.talend.sdk.component.path.PathFactory;
class ResolverImplTest {
+ public final String ARTIFACT_ID = "artifactId";
+
+ public final String EXPECTED_ARTIFACT_ID = "arquillian-tomee-codi-tests";
+
+ public final String GAV_CODI_TESTS = "org.apache.tomee:arquillian-tomee-codi-tests:jar:8.0.9";
+
+ public final String GAV_OPENEJB = "org.apache.tomee:openejb-itests-beans:jar:8.0.14";
+
+ public final String POM_PROPS_TOMEE = "META-INF/maven/org.apache.tomee/arquillian-tomee-codi-tests/pom.properties";
+
+ public final String CLASS_OPENEJB = "org.apache.openejb.test.ApplicationException";
+
+ public final String CLASS_POIXML = "org.apache.poi.ooxml.POIXMLException";
+
+ public final String POI_PATH = "target/test-dependencies/org/apache/poi/poi-ooxml/5.4.1/poi-ooxml-5.4.1.jar";
+
@Test
void createClassLoader(@TempDir final Path temporaryFolder) throws Exception {
final File root = temporaryFolder.toFile();
root.mkdirs();
- final String dep = "org.apache.tomee:arquillian-tomee-codi-tests:jar:8.0.9";
+ final String dep = GAV_CODI_TESTS;
final File nestedJar = new File(root, UUID.randomUUID().toString() + ".jar");
try (final JarOutputStream out = new JarOutputStream(new FileOutputStream(nestedJar))) {
addDepToJar(dep, out);
@@ -70,14 +93,11 @@ void createClassLoader(@TempDir final Path temporaryFolder) throws Exception {
assertNotNull(desc.asClassLoader());
assertEquals(singletonList(dep), desc.resolvedDependencies());
final Properties props = new Properties();
- try (final InputStream in = desc
- .asClassLoader()
- .getResourceAsStream(
- "META-INF/maven/org.apache.tomee/arquillian-tomee-codi-tests/pom.properties")) {
+ try (final InputStream in = desc.asClassLoader().getResourceAsStream(POM_PROPS_TOMEE)) {
assertNotNull(in);
props.load(in);
}
- assertEquals("arquillian-tomee-codi-tests", props.getProperty("artifactId"));
+ assertEquals(EXPECTED_ARTIFACT_ID, props.getProperty(ARTIFACT_ID));
} finally {
thread.setContextClassLoader(contextClassLoader);
appLoader.close();
@@ -97,6 +117,95 @@ void resolvefromDescriptor() throws IOException {
}
}
+ @Test
+ void createBareConfigurableClassLoader(@TempDir final Path temporaryFolder) throws Exception {
+ createConfigurableClassLoader(temporaryFolder, true, false);
+ }
+
+ @Test
+ void createConfigurableClassLoaderWithParentLoading(@TempDir final Path temporaryFolder) throws Exception {
+ createConfigurableClassLoader(temporaryFolder, false, true);
+ }
+
+ @Test
+ void createConfigurableClassLoaderWithoutParentLoading(@TempDir final Path temporaryFolder) throws Exception {
+ createConfigurableClassLoader(temporaryFolder, false, false);
+ }
+
+ private void createConfigurableClassLoader(final Path temporaryFolder, final boolean bare,
+ final boolean parentLoading)
+ throws Exception {
+ final File root = temporaryFolder.toFile();
+ root.mkdirs();
+ final List deps = Arrays.asList(GAV_CODI_TESTS, GAV_OPENEJB);
+ final File nestedJar = new File(root, UUID.randomUUID().toString() + ".jar");
+ try (final JarOutputStream out = new JarOutputStream(new FileOutputStream(nestedJar))) {
+ deps.forEach(d -> addDepToJar(d, out));
+ }
+
+ final Thread thread = Thread.currentThread();
+ final ClassLoader contextCl = thread.getContextClassLoader();
+ // component loader simulation which is always the parent of that
+ // here the parent of the component is the jar containing the nested repo
+ final URLClassLoader appLoader = new URLClassLoader(new URL[] { nestedJar.toURI().toURL() }, contextCl);
+ final File poiJar = new File(POI_PATH);
+ assertTrue(Files.exists(poiJar.toPath()));
+ final ConfigurableClassLoader componentLoader =
+ new ConfigurableClassLoader("test", new URL[] { poiJar.toURI().toURL() }, appLoader,
+ it -> true, it -> true, new String[0], new String[0]);
+ // force loading of a class from the future parent for the created classloader with no parent loading allowed.
+ final Class poiClazz = componentLoader.loadClass(CLASS_POIXML, true);
+ assertEquals(componentLoader, poiClazz.getClassLoader());
+
+ thread.setContextClassLoader(componentLoader);
+ final ClassLoaderDefinition classLoaderDefinition = ContainerManager.ClassLoaderConfiguration.builder()
+ .parent(componentLoader)
+ .classesFilter(it -> true)
+ .parentClassesFilter(it -> parentLoading)
+ .supportsResourceDependencies(true)
+ .parentResourcesFilter(it -> true)
+ .create();
+
+ final Function fileResolver = coord -> PathFactory.get("maven2").resolve(coord);
+ try (final Resolver.ClassLoaderDescriptor desc = bare
+ ? new ResolverImpl(null, fileResolver).mapDescriptorToClassLoader(deps)
+ : new ResolverImpl(null, fileResolver).mapDescriptorToClassLoader(deps, classLoaderDefinition)) {
+ assertNotNull(desc);
+ final ClassLoader dumbCl = desc.asClassLoader();
+ assertNotNull(dumbCl);
+ // the classloader should be a child of the component loader and a ConfigurableClassLoader
+ assertTrue(dumbCl.getParent() == componentLoader);
+ assertTrue(dumbCl instanceof ConfigurableClassLoader);
+ final ConfigurableClassLoader ccl = (ConfigurableClassLoader) dumbCl;
+ // class loading should work and be done by the created classloader
+ Class clazz = ccl.loadClass(CLASS_OPENEJB, true);
+ assertNotNull(clazz);
+ assertEquals(ccl, clazz.getClassLoader());
+ // check that parent loading is or not allowed
+ if (parentLoading) {
+ Class clzz = ccl.loadClass(CLASS_POIXML, true);
+ assertNotNull(clzz);
+ assertEquals(poiClazz, clzz);
+ } else {
+ assertThrows(ClassNotFoundException.class, () -> ccl.loadClass(CLASS_POIXML, true));
+ }
+ // checks dependencies and resources
+ assertEquals(deps, desc.resolvedDependencies());
+ final Properties props = new Properties();
+ try (final InputStream in = desc.asClassLoader().getResourceAsStream(POM_PROPS_TOMEE)) {
+ assertNotNull(in);
+ props.load(in);
+ }
+ assertEquals(EXPECTED_ARTIFACT_ID, props.getProperty(ARTIFACT_ID));
+ //
+ // TODO: check parent loading of resources is allowed or not depending on the configuration.
+ //
+ } finally {
+ thread.setContextClassLoader(contextCl);
+ appLoader.close(); // cascade close the classloaders
+ }
+ }
+
private void addDepToJar(final String dep, final JarOutputStream out) {
final String[] segments = dep.split(":");
final String path = "MAVEN-INF/repository/" + segments[0].replace(".", "/") + "/" + segments[1] + "/"
@@ -110,7 +219,9 @@ private void addDepToJar(final String dep, final JarOutputStream out) {
try {
out.putNextEntry(new ZipEntry(current.toString()));
} catch (final IOException e) {
- fail(e.getMessage());
+ if (!e.getMessage().contains("duplicate entry")) {
+ fail(e.getMessage());
+ }
}
}
// add the dep
diff --git a/container/container-core/pom.xml b/container/container-core/pom.xml
index b8e227c1ef2e1..418f3a3b0018e 100644
--- a/container/container-core/pom.xml
+++ b/container/container-core/pom.xml
@@ -32,6 +32,11 @@
+
+ org.talend.sdk.component
+ component-api
+
+
org.slf4j
slf4j-api
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index d63d0624c2236..f0edb421dc47c 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -59,6 +59,7 @@
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import org.talend.sdk.component.api.service.dependency.ClassLoaderDefinition;
import org.talend.sdk.component.classloader.ConfigurableClassLoader;
import org.talend.sdk.component.dependencies.Resolver;
import org.talend.sdk.component.dependencies.maven.Artifact;
@@ -532,7 +533,7 @@ public static class DependenciesResolutionConfiguration {
@Getter
@Builder(buildMethodName = "create")
- public static class ClassLoaderConfiguration {
+ public static class ClassLoaderConfiguration implements ClassLoaderDefinition {
private final ClassLoader parent;
From 7eddd30f9b3b8c99c8b2151d677b3e13b75e38fc Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 14:25:05 +0100
Subject: [PATCH 24/37] feat(QTDI-2134): fixes pr comments
---
.../api/service/dependency/Resolver.java | 6 ++++-
.../runtime/manager/service/ResolverImpl.java | 9 +++++--
.../component/container/ContainerManager.java | 9 +++----
...DependencyListLocalRepositoryResolver.java | 8 ++----
.../sdk/component/ContainerManagerTest.java | 27 +++++++++++++++++++
5 files changed, 45 insertions(+), 14 deletions(-)
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
index 03f6ca21d07f7..9383c8471f26e 100644
--- a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
+++ b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
@@ -56,7 +56,11 @@ default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs
* @param configuration
* @return the classloader initialized with the configuration provided and the resolved dependencies.
*/
- ClassLoaderDescriptor mapDescriptorToClassLoader(InputStream descriptor, final ClassLoaderDefinition configuration);
+ default ClassLoaderDescriptor mapDescriptorToClassLoader(InputStream descriptor,
+ final ClassLoaderDefinition configuration) {
+ throw new UnsupportedOperationException(
+ "This method is not implemented yet, please use the mapDescriptorToClassLoader(InputStream) method instead");
+ }
default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs,
final ClassLoaderDefinition configuration) {
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
index 3b94753405432..34e13e25fe38d 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
@@ -50,9 +50,13 @@ public class ResolverImpl implements Resolver, Serializable {
@Override
public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descriptor) {
+ final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ final ClassLoader loader =
+ ofNullable(classLoader).map(ClassLoader::getParent).orElseGet(ClassLoader::getSystemClassLoader);
+
return mapDescriptorToClassLoader(descriptor,
ClassLoaderConfiguration.builder()
- .parent(Thread.currentThread().getContextClassLoader())
+ .parent(loader)
.classesFilter(it -> true)
.parentClassesFilter(it -> false)
.supportsResourceDependencies(true)
@@ -96,7 +100,8 @@ public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descri
nested.toArray(new String[0]),
ConfigurableClassLoader.class.isInstance(parent)
? ConfigurableClassLoader.class.cast(parent).getJvmMarkers()
- : new String[] { "" });
+ : new String[] { "" },
+ configuration.getParentResourcesFilter());
return new ClassLoaderDescriptorImpl(volatileLoader, resolved);
} catch (final IOException e) {
throw new IllegalArgumentException(e);
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index f0edb421dc47c..950782ee6e877 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -76,17 +76,16 @@
@Slf4j
public class ContainerManager implements Lifecycle {
- private static final Consumer NOOP_CUSTOMIZER = c -> {
- };
-
- private static final Pattern FRAMEWORK_JAR_PATTERN = Pattern.compile(".*" +
- Matcher.quoteReplacement(File.separator) +
+ public static final Pattern FRAMEWORK_JAR_PATTERN = Pattern.compile("(.*[\\\\/])?" + // Optional path with separator
"(component-api|component-runtime-design-extension|component-runtime-di|component-runtime-impl|" +
"component-runtime-manager|component-spi|container-core|geronimo-annotation_1.3_spec|" +
"geronimo-json_1.1_spec|geronimo-jsonb_1.0_spec|johnzon-core|johnzon-jsonb|johnzon-mapper|" +
"rhino|slf4j-api|slf4j-log4j12|slf4j-reload4j|xbean-asm9-shaded|xbean-finder-shaded|xbean-reflect)" +
"-.*jar$");
+ private static final Consumer NOOP_CUSTOMIZER = c -> {
+ };
+
private static final Pattern JIRA_TICKET_PATTERN = Pattern.compile("-[A-Z]{2,}-\\d+$");
private static final Pattern MILESTONE_PATTERN = Pattern.compile("M\\d+$");
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
index 68c3d14ba2679..46809803e656a 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/dependencies/maven/MvnDependencyListLocalRepositoryResolver.java
@@ -113,12 +113,8 @@ private String findDynamicDependencies(final ClassLoader rootLoader, final Strin
if (stream == null) {
return "";
}
- final Properties properties = new Properties() {
-
- {
- load(stream);
- }
- };
+ final Properties properties = new Properties();
+ properties.load(stream);
final String module = ContainerManager.buildAutoIdFromName(artifact);
final String dyndeps = properties.getProperty(module, "");
return dyndeps.replace(",", System.lineSeparator());
diff --git a/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java b/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java
index 8287d9da6c766..12555e0060620 100644
--- a/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java
+++ b/container/container-core/src/test/java/org/talend/sdk/component/ContainerManagerTest.java
@@ -18,9 +18,11 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.talend.sdk.component.container.ContainerManager.FRAMEWORK_JAR_PATTERN;
import java.io.File;
import java.io.FileOutputStream;
@@ -160,6 +162,31 @@ void close(final TempJars jars) {
});
}
+ @Test
+ void frameworkJarPatternShouldMatchBothUnixAndWindowsPaths() {
+ // Basic filename tests
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("component-api-1.2.3.jar").matches());
+ assertFalse(FRAMEWORK_JAR_PATTERN.matcher("component-api-1.2.3.txt").matches());
+ // Unix-style paths
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("/usr/lib/component-api-1.2.3.jar").matches());
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("/opt/talend/lib/slf4j-api-1.7.30.jar").matches());
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("lib/johnzon-mapper-1.2.18.jar").matches());
+ // Windows-style paths
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("lib\\component-api-1.2.3.jar").matches());
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("C:\\Program Files\\Talend\\lib\\component-api-1.2.3.jar").matches());
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("D:\\workspace\\lib\\slf4j-api-1.7.30.jar").matches());
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("lib\\johnzon-mapper-1.2.18.jar").matches());
+ // Edge cases: paths with multiple separators
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("foo/bar/baz/component-spi-2.0.0.jar").matches());
+ assertTrue(FRAMEWORK_JAR_PATTERN.matcher("foo\\bar\\baz\\container-core-1.0.0.jar").matches());
+ // Should NOT match non-framework jars
+ assertFalse(FRAMEWORK_JAR_PATTERN.matcher("/usr/lib/my-custom-component-1.0.0.jar").matches());
+ assertFalse(FRAMEWORK_JAR_PATTERN.matcher("C:\\libs\\other-library-2.0.jar").matches());
+ // Edge case: filename that starts with framework name but isn't a framework jar
+ assertFalse(FRAMEWORK_JAR_PATTERN.matcher("/usr/lib/component-api.txt").matches());
+ assertFalse(FRAMEWORK_JAR_PATTERN.matcher("/usr/lib/component-api").matches());
+ }
+
@Test
void getClasspathFromJarWithValidManifestClassPath(@TempDir final Path tempDir) throws Exception {
final File jar1 = createJarFile(tempDir, "lib1.jar");
From c9efe1b3f672ded9e7ea1c996e5e6ccc539d9cd3 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 14:34:55 +0100
Subject: [PATCH 25/37] feat(QTDI-2134): fix classloader's parent test
---
.../sdk/component/runtime/manager/service/ResolverImplTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
index 5b1f12702158f..1550a98d856e3 100644
--- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
+++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
@@ -173,8 +173,8 @@ private void createConfigurableClassLoader(final Path temporaryFolder, final boo
assertNotNull(desc);
final ClassLoader dumbCl = desc.asClassLoader();
assertNotNull(dumbCl);
+ assertTrue(dumbCl.getParent() == (bare ? appLoader : componentLoader));
// the classloader should be a child of the component loader and a ConfigurableClassLoader
- assertTrue(dumbCl.getParent() == componentLoader);
assertTrue(dumbCl instanceof ConfigurableClassLoader);
final ConfigurableClassLoader ccl = (ConfigurableClassLoader) dumbCl;
// class loading should work and be done by the created classloader
From c8c6aa14ba1189432e6692781c19699bead54dcf Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 15:26:33 +0100
Subject: [PATCH 26/37] feat(QTDI-2134): rename isInJvm method
---
.../component/classloader/ConfigurableClassLoader.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index a084ea937c293..53059eda1eef1 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -389,10 +389,10 @@ public Enumeration getResources(final String name) throws IOException {
return selfResources;
}
if (!selfResources.hasMoreElements()) {
- return new FilteringUrlEnum(parentResources, this::isInJvm);
+ return new FilteringUrlEnum(parentResources, this::shouldLoadFromParent);
}
return new ChainedEnumerations(
- asList(selfResources, new FilteringUrlEnum(parentResources, this::isInJvm)).iterator());
+ asList(selfResources, new FilteringUrlEnum(parentResources, this::shouldLoadFromParent)).iterator());
}
@Override
@@ -403,7 +403,7 @@ public URL getResource(final String name) {
}
if (!isBlacklistedFromParent(name)) {
resource = getParent().getResource(name);
- if (resource != null && (isNestedDependencyResource(name) || isInJvm(resource))) {
+ if (resource != null && (isNestedDependencyResource(name) || shouldLoadFromParent(resource))) {
return resource;
}
}
@@ -488,7 +488,7 @@ private boolean isNestedDependencyResource(final String name) {
return name != null && name.startsWith(NESTED_MAVEN_REPOSITORY);
}
- private boolean isInJvm(final URL resource) {
+ private boolean shouldLoadFromParent(final URL resource) {
// Services and parent allowed resources that should always be found by top level classloader.
// Warning: selection shouldn't be too generic! Use very specific paths only like jndi.properties.
if (resourcesFilter.test(resource.getFile())) {
From b54e276d6ad057845455579668bdf4ffd0e6e459 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 15:40:01 +0100
Subject: [PATCH 27/37] Update
component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../manager/service/ResolverImplTest.java | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
index 1550a98d856e3..5084441279b69 100644
--- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
+++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/service/ResolverImplTest.java
@@ -51,21 +51,23 @@
class ResolverImplTest {
- public final String ARTIFACT_ID = "artifactId";
+ private static final String ARTIFACT_ID = "artifactId";
- public final String EXPECTED_ARTIFACT_ID = "arquillian-tomee-codi-tests";
+ private static final String EXPECTED_ARTIFACT_ID = "arquillian-tomee-codi-tests";
- public final String GAV_CODI_TESTS = "org.apache.tomee:arquillian-tomee-codi-tests:jar:8.0.9";
+ private static final String GAV_CODI_TESTS = "org.apache.tomee:arquillian-tomee-codi-tests:jar:8.0.9";
- public final String GAV_OPENEJB = "org.apache.tomee:openejb-itests-beans:jar:8.0.14";
+ private static final String GAV_OPENEJB = "org.apache.tomee:openejb-itests-beans:jar:8.0.14";
- public final String POM_PROPS_TOMEE = "META-INF/maven/org.apache.tomee/arquillian-tomee-codi-tests/pom.properties";
+ private static final String POM_PROPS_TOMEE =
+ "META-INF/maven/org.apache.tomee/arquillian-tomee-codi-tests/pom.properties";
- public final String CLASS_OPENEJB = "org.apache.openejb.test.ApplicationException";
+ private static final String CLASS_OPENEJB = "org.apache.openejb.test.ApplicationException";
- public final String CLASS_POIXML = "org.apache.poi.ooxml.POIXMLException";
+ private static final String CLASS_POIXML = "org.apache.poi.ooxml.POIXMLException";
- public final String POI_PATH = "target/test-dependencies/org/apache/poi/poi-ooxml/5.4.1/poi-ooxml-5.4.1.jar";
+ private static final String POI_PATH =
+ "target/test-dependencies/org/apache/poi/poi-ooxml/5.4.1/poi-ooxml-5.4.1.jar";
@Test
void createClassLoader(@TempDir final Path temporaryFolder) throws Exception {
From bf5dea75200ffc5a2dcf232aaa78a0e1e52c082e Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 16:01:59 +0100
Subject: [PATCH 28/37] feat(QTDI-2134): fix loader (pr comment)
---
.../component/runtime/manager/service/ResolverImpl.java | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
index 34e13e25fe38d..d7f7817a71ba8 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
@@ -70,9 +70,9 @@ public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descri
final Collection urls = new ArrayList<>();
final Collection nested = new ArrayList<>();
final Collection resolved = new ArrayList<>();
- final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- final ClassLoader loader =
- ofNullable(classLoader).map(ClassLoader::getParent).orElseGet(ClassLoader::getSystemClassLoader);
+ final ClassLoader parent = configuration.getParent() != null ? configuration.getParent()
+ : ofNullable(Thread.currentThread().getContextClassLoader()).map(ClassLoader::getParent)
+ .orElseGet(ClassLoader::getSystemClassLoader);
try {
new MvnDependencyListLocalRepositoryResolver(null, fileResolver)
.resolveFromDescriptor(descriptor)
@@ -86,12 +86,11 @@ public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descri
} catch (final MalformedURLException e) {
throw new IllegalStateException(e);
}
- } else if (loader.getResource("MAVEN-INF/repository/" + path) != null) {
+ } else if (parent.getResource("MAVEN-INF/repository/" + path) != null) {
nested.add(path);
resolved.add(artifact.toCoordinate());
} // else will be missing
});
- final ClassLoader parent = configuration.getParent();
final ConfigurableClassLoader volatileLoader = new ConfigurableClassLoader(plugin + "#volatile-resolver",
urls.toArray(new URL[0]),
parent,
From 5b21af17d00c1c48f0cacbbd86b0b3eb1a357dca Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 16:16:35 +0100
Subject: [PATCH 29/37] feat(QTDI-2134): fix debug (pr comment)
---
.../org/talend/sdk/component/container/ContainerManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
index 950782ee6e877..d137831baaef8 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/container/ContainerManager.java
@@ -466,7 +466,7 @@ private void readRuntimeClasspath() {
runtimeClasspath.addAll(getClasspathFromJar(p));
} else {
runtimeClasspath.add(p.toAbsolutePath().toString());
- log.debug("[sysinfo] Runtime classpath entry: " + p.toAbsolutePath());
+ log.debug("[sysinfo] Runtime classpath entry: {}", p.toAbsolutePath());
}
});
// cleanup the classpath from entries that are known as framework artifacts
From ab9ae4fc6cb55457e1f6232915925ccb519a0058 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 16:18:12 +0100
Subject: [PATCH 30/37] Update
component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../talend/sdk/component/runtime/manager/ComponentManager.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index 0cefc2a72e366..cd893347a28e5 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -219,6 +219,7 @@ private static ComponentManager buildNewComponentManager() {
info("Components: " + availablePlugins());
} catch (Exception e) {
info("Failed to load plugins from plugins.properties: " + e.getMessage());
+ log.debug("Failed to load plugins from plugins.properties.", e);
}
}
From f7b1f6afbd9093d4b919f7268361911cdc5591df Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 16:19:36 +0100
Subject: [PATCH 31/37] Update
component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../api/service/dependency/ClassLoaderDefinition.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
index 53ef315022cdb..4bc3a76eec709 100644
--- a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
+++ b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/ClassLoaderDefinition.java
@@ -41,8 +41,11 @@ public interface ClassLoaderDefinition {
/**
*
- * @return a filter to apply on the resources to load from the parent classloader. If the filter returns false for a
- * resource, the classloader to create will try to load it by itself instead of delegating to its parent.
+ * @return a filter to apply on the resources to load from the parent classloader. The {@link String} argument of
+ * the predicate is the path of the resource as obtained from {@code URL#getFile()} (i.e. the full URL/file path),
+ * not the logical resource name used in {@link ClassLoader#getResource(String)}. If the filter returns
+ * {@code false} for a resource, the classloader to create will try to load it by itself instead of delegating to
+ * its parent.
*/
Predicate getParentResourcesFilter();
From 3c2845443542078e100ee8cfc92e0b1810d178bf Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Fri, 20 Mar 2026 16:40:12 +0100
Subject: [PATCH 32/37] Update
component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../component/api/service/dependency/Resolver.java | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
index 9383c8471f26e..b7908fd6583b4 100644
--- a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
+++ b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
@@ -52,14 +52,21 @@ default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs
* WARNING: note it is very important to close the descriptor once no more used otherwise
* you can leak memory.
*
+ * The default implementation of this method is unsupported and will always throw
+ * {@link UnsupportedOperationException}. Implementations that support configurable
+ * classloader creation must override this method.
+ *
* @param descriptor the dependencies.txt InputStream.
- * @param configuration
+ * @param configuration the classloader configuration to apply when creating the classloader.
* @return the classloader initialized with the configuration provided and the resolved dependencies.
+ * @throws UnsupportedOperationException if this implementation does not support configurable
+ * classloader creation.
*/
default ClassLoaderDescriptor mapDescriptorToClassLoader(InputStream descriptor,
final ClassLoaderDefinition configuration) {
throw new UnsupportedOperationException(
- "This method is not implemented yet, please use the mapDescriptorToClassLoader(InputStream) method instead");
+ "ClassLoader configuration is not supported by this implementation; "
+ + "override mapDescriptorToClassLoader(InputStream, ClassLoaderDefinition) to provide an implementation");
}
default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs,
From d11f4dea5187f5524000524c5659889ef4bbb125 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Wed, 25 Mar 2026 16:10:32 +0100
Subject: [PATCH 33/37] feat(QTDI-2134): fix parametrize for log.debug
---
.../talend/sdk/component/runtime/manager/ComponentManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
index cd893347a28e5..d56beeafa6b45 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java
@@ -1765,7 +1765,7 @@ private Archive toArchive(final String module, final OriginalId originalId,
JarInputStream jarStream = null;
try {
jarStream = new JarInputStream(nested.openStream());
- log.debug("Found a nested resource for " + nested);
+ log.debug("Found a nested resource for {}", nested);
return new NestedJarArchive(nested, jarStream, loader);
} catch (final IOException e) {
if (jarStream != null) {
From 40e6c786acfd1134b594ac07d3ed3836961173fd Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Wed, 25 Mar 2026 18:07:36 +0100
Subject: [PATCH 34/37] feat(QTDI-2134): apply parent restrictions to
resourceAsStream
---
.../sdk/component/classloader/ConfigurableClassLoader.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
index 53059eda1eef1..ee4a5987c168c 100644
--- a/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
+++ b/container/container-core/src/main/java/org/talend/sdk/component/classloader/ConfigurableClassLoader.java
@@ -429,7 +429,10 @@ private InputStream doGetResourceAsStream(final String name) {
try {
if (resource == null && !isBlacklistedFromParent(name)) {
final URL url = getParent().getResource(name);
- return url != null ? getInputStream(url) : null;
+ if (url != null && shouldLoadFromParent(url)) {
+ return getInputStream(url);
+ }
+ return null;
}
if (resource == null) {
return null;
From 85e779507f5e6885ad4d14adeb0e4cd2751e1ec0 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Wed, 25 Mar 2026 18:31:28 +0100
Subject: [PATCH 35/37] Update
component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../sdk/component/runtime/manager/service/ResolverImpl.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
index d7f7817a71ba8..822916b66ffa0 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/ResolverImpl.java
@@ -94,13 +94,13 @@ public ClassLoaderDescriptor mapDescriptorToClassLoader(final InputStream descri
final ConfigurableClassLoader volatileLoader = new ConfigurableClassLoader(plugin + "#volatile-resolver",
urls.toArray(new URL[0]),
parent,
- configuration.getParentClassesFilter(),
- configuration.getClassesFilter(),
+ ofNullable(configuration.getParentClassesFilter()).orElse(it -> false),
+ ofNullable(configuration.getClassesFilter()).orElse(it -> true),
nested.toArray(new String[0]),
ConfigurableClassLoader.class.isInstance(parent)
? ConfigurableClassLoader.class.cast(parent).getJvmMarkers()
: new String[] { "" },
- configuration.getParentResourcesFilter());
+ ofNullable(configuration.getParentResourcesFilter()).orElse(it -> true));
return new ClassLoaderDescriptorImpl(volatileLoader, resolved);
} catch (final IOException e) {
throw new IllegalArgumentException(e);
From e6edd39365d30bb6018065e4b5775b09a37432d4 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Wed, 25 Mar 2026 18:48:20 +0100
Subject: [PATCH 36/37] feat(QTDI-2134): fix failing test
---
.../sdk/component/api/service/dependency/Resolver.java | 6 ++++--
.../runtime/manager/xbean/NestedJarArchiveTest.java | 2 +-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
index b7908fd6583b4..447350586b015 100644
--- a/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
+++ b/component-api/src/main/java/org/talend/sdk/component/api/service/dependency/Resolver.java
@@ -52,9 +52,11 @@ default ClassLoaderDescriptor mapDescriptorToClassLoader(final List gavs
* WARNING: note it is very important to close the descriptor once no more used otherwise
* you can leak memory.
*
- * The default implementation of this method is unsupported and will always throw
+ *
+ * The default implementation of this method is unsupported and will always throw
* {@link UnsupportedOperationException}. Implementations that support configurable
- * classloader creation must override this method.
+ * classloader creation must override this method.
+ *
*
* @param descriptor the dependencies.txt InputStream.
* @param configuration the classloader configuration to apply when creating the classloader.
diff --git a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/xbean/NestedJarArchiveTest.java b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/xbean/NestedJarArchiveTest.java
index 983081616ef06..f8081252fdc00 100644
--- a/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/xbean/NestedJarArchiveTest.java
+++ b/component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/xbean/NestedJarArchiveTest.java
@@ -53,7 +53,7 @@ void xbeanNestedScanning(final TestInfo info, @TempDir final File temporaryFolde
final File jar = createPlugin(temporaryFolder, info.getTestMethod().get().getName());
final ConfigurableClassLoader configurableClassLoader = new ConfigurableClassLoader("", new URL[0],
new URLClassLoader(new URL[] { jar.toURI().toURL() }, Thread.currentThread().getContextClassLoader()),
- n -> true, n -> true, new String[] { "com/foo/bar/1.0/bar-1.0.jar" }, new String[0]);
+ n -> true, n -> true, new String[] { "com/foo/bar/1.0/bar-1.0.jar" }, new String[0], n -> true);
try (final JarInputStream jis = new JarInputStream(
configurableClassLoader.getResourceAsStream("MAVEN-INF/repository/com/foo/bar/1.0/bar-1.0.jar"))) {
assertNotNull(jis, "test is wrongly setup, no nested jar, fix the createPlugin() method please");
From deecff7deab9ef550587851bbf49413690d1cc71 Mon Sep 17 00:00:00 2001
From: Emmanuel GALLOIS
Date: Thu, 7 May 2026 17:58:38 +0200
Subject: [PATCH 37/37] feat(QTDI-2134): fix RegExp loading via SPI
---
.../form/internal/validation/JavascriptRegex.java | 12 ++++++++++++
.../runtime/manager/reflect/ReflectionService.java | 13 +++++++++++++
2 files changed, 25 insertions(+)
diff --git a/component-form/component-form-core/src/main/java/org/talend/sdk/component/form/internal/validation/JavascriptRegex.java b/component-form/component-form-core/src/main/java/org/talend/sdk/component/form/internal/validation/JavascriptRegex.java
index 4eff67920014b..66839b8b4750a 100644
--- a/component-form/component-form-core/src/main/java/org/talend/sdk/component/form/internal/validation/JavascriptRegex.java
+++ b/component-form/component-form-core/src/main/java/org/talend/sdk/component/form/internal/validation/JavascriptRegex.java
@@ -15,9 +15,12 @@
*/
package org.talend.sdk.component.form.internal.validation;
+import java.util.ServiceLoader;
import java.util.function.Predicate;
import org.mozilla.javascript.Context;
+import org.mozilla.javascript.RegExpLoader;
+import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
public class JavascriptRegex implements Predicate {
@@ -47,6 +50,15 @@ public boolean test(final CharSequence text) {
final String script = "new RegExp(regex, indicators).test(text)";
final Context context = Context.enter();
try {
+ // Rhino 1.9.0+: RegExp is registered via ServiceLoader.
+ // The ServiceLoader uses Thread.currentThread().getContextClassLoader() by default,
+ // which may not find the service in an isolated classloader context.
+ // Explicitly register RegExpProxy using Rhino's own classloader as fallback.
+ if (ScriptRuntime.getRegExpProxy(context) == null) {
+ ServiceLoader.load(RegExpLoader.class, context.getClass().getClassLoader())
+ .findFirst()
+ .ifPresent(loader -> ScriptRuntime.setRegExpProxy(context, loader.newProxy()));
+ }
final Scriptable scope = context.initStandardObjects();
scope.put("text", scope, text);
scope.put("regex", scope, regex);
diff --git a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/reflect/ReflectionService.java b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/reflect/ReflectionService.java
index cb8b313a95757..cbc46f55ad124 100644
--- a/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/reflect/ReflectionService.java
+++ b/component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/reflect/ReflectionService.java
@@ -44,6 +44,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -69,6 +70,8 @@
import org.apache.xbean.recipe.ObjectRecipe;
import org.apache.xbean.recipe.UnsetPropertiesRecipe;
import org.mozilla.javascript.Context;
+import org.mozilla.javascript.RegExpLoader;
+import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.talend.sdk.component.api.record.Schema;
import org.talend.sdk.component.api.service.configuration.Configuration;
@@ -855,6 +858,16 @@ public boolean test(final CharSequence text) {
final String script = "new RegExp(regex, indicators).test(text)";
final Context context = Context.enter();
try {
+ // Rhino 1.9.0+: RegExp is registered via ServiceLoader.
+ // The ServiceLoader uses Thread.currentThread().getContextClassLoader() by default,
+ // which may not find the service in an isolated classloader context.
+ // Explicitly register RegExpProxy using Rhino's own classloader as fallback.
+ if (ScriptRuntime.getRegExpProxy(context) == null) {
+ ServiceLoader
+ .load(RegExpLoader.class, context.getClass().getClassLoader())
+ .findFirst()
+ .ifPresent(loader -> ScriptRuntime.setRegExpProxy(context, loader.newProxy()));
+ }
final Scriptable scope = context.initStandardObjects();
scope.put("text", scope, text);
scope.put("regex", scope, regex);