-
Notifications
You must be signed in to change notification settings - Fork 51
feat(QTDI-2134): dynamic dependencies - nested loading #1135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
7c98df4
0c45a2a
80fc6e7
aa0409a
d4a0aa3
9d972a2
7b109ec
4cdad2e
594632c
33c16e8
061727f
24efc01
ff3973c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| /** | ||
| * Copyright (C) 2006-2025 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.configuration.type; | ||
|
|
||
| import static java.lang.annotation.ElementType.TYPE; | ||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
|
||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| import org.talend.sdk.component.api.configuration.type.meta.ConfigurationType; | ||
| import org.talend.sdk.component.api.meta.Documentation; | ||
|
|
||
| @Target(TYPE) | ||
| @Retention(RUNTIME) | ||
| @ConfigurationType("dynamicDependenciesConfiguration") | ||
| @Documentation("Mark a model (complex object) as being the configuration expected to compute dynamic dependencies.") | ||
| public @interface DynamicDependenciesConfiguration { | ||
|
|
||
| String value() default "default"; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.ByteArrayOutputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.Closeable; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.File; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.FileNotFoundException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.FilterInputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.InputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.lang.instrument.ClassFileTransformer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -38,7 +40,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.URLClassLoader; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.URLConnection; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.URLStreamHandler; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.file.Files; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.file.Path; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.file.Paths; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.file.StandardCopyOption; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.security.CodeSource; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.security.cert.Certificate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -55,6 +60,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.function.Predicate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.jar.JarEntry; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.jar.JarFile; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.jar.JarInputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.jar.Manifest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -150,7 +156,7 @@ private ConfigurableClassLoader(final String id, final URL[] urls, final ClassLo | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void loadNestedDependencies(final ClassLoader parent, final String[] nestedDependencies) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final byte[] buffer = new byte[8192]; // should be good for most cases | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Stream.of(nestedDependencies).map(d -> NESTED_MAVEN_REPOSITORY + d).forEach(resource -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Stream.of(nestedDependencies).forEach(resource -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final URL url = ofNullable(super.findResource(resource)).orElseGet(() -> parent.getResource(resource)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (url == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("Didn't find " + resource + " in " + asList(nestedDependencies)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -459,7 +465,7 @@ public Enumeration<URL> findResources(final String name) throws IOException { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private boolean isNestedDependencyResource(final String name) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name != null && name.startsWith(NESTED_MAVEN_REPOSITORY); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name.startsWith(NESTED_MAVEN_REPOSITORY) || name.endsWith(".jar"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name.startsWith(NESTED_MAVEN_REPOSITORY) || name.endsWith(".jar"); | |
| return name.startsWith(NESTED_MAVEN_REPOSITORY) && name.endsWith(".jar"); |
Outdated
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The innerJar JarFile is closed in the try-with-resources block before the FilterInputStream is returned, which will cause the stream to be invalid. The JarFile must remain open until the FilterInputStream is closed. Consider creating a custom InputStream that holds references to both the JarFile and the temp file, closing the JarFile before deleting the temp file.
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an exception occurs after creating the temp file but before the FilterInputStream's close() is called, the temp file will leak. The temp file should be deleted in a finally block or the exception handler at line 558-561 should be moved outside the inner try block to ensure cleanup.
| final Path tempInnerJar = Files.createTempFile("nested-inner-", ".jar"); | |
| try (InputStream innerStream = outerJar.getInputStream(innerEntry)) { | |
| Files.copy(innerStream, tempInnerJar, StandardCopyOption.REPLACE_EXISTING); | |
| try (JarFile innerJar = new JarFile(tempInnerJar.toFile())) { | |
| final JarEntry resourceEntry = innerJar.getJarEntry(resourcePath); | |
| if (resourceEntry == null) { | |
| throw new FileNotFoundException("Resource not found: " + resourcePath); | |
| } | |
| // Return a stream that cleans up automatically when closed | |
| return new FilterInputStream(innerJar.getInputStream(resourceEntry)) { | |
| @Override | |
| public void close() throws IOException { | |
| super.close(); | |
| Files.deleteIfExists(tempInnerJar); | |
| } | |
| }; | |
| } | |
| } catch (IOException e) { | |
| Files.deleteIfExists(tempInnerJar); | |
| throw e; | |
| Path tempInnerJar = null; | |
| boolean success = false; | |
| try { | |
| tempInnerJar = Files.createTempFile("nested-inner-", ".jar"); | |
| try (InputStream innerStream = outerJar.getInputStream(innerEntry)) { | |
| Files.copy(innerStream, tempInnerJar, StandardCopyOption.REPLACE_EXISTING); | |
| try (JarFile innerJar = new JarFile(tempInnerJar.toFile())) { | |
| final JarEntry resourceEntry = innerJar.getJarEntry(resourcePath); | |
| if (resourceEntry == null) { | |
| throw new FileNotFoundException("Resource not found: " + resourcePath); | |
| } | |
| // Return a stream that cleans up automatically when closed | |
| FilterInputStream fis = new FilterInputStream(innerJar.getInputStream(resourceEntry)) { | |
| @Override | |
| public void close() throws IOException { | |
| super.close(); | |
| Files.deleteIfExists(tempInnerJar); | |
| } | |
| }; | |
| success = true; | |
| return fis; | |
| } | |
| } | |
| } finally { | |
| if (tempInnerJar != null && !success) { | |
| try { | |
| Files.deleteIfExists(tempInnerJar); | |
| } catch (IOException ex) { | |
| // log or ignore, as appropriate | |
| log.warn("Failed to delete temp file: " + tempInnerJar, ex); | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
lastIndexOf(\"/\")for extracting the filename is platform-dependent and will fail on Windows where paths use backslashes. UsePaths.get(new URI(jarPath)).getFileName().toString()or similar platform-independent approach.