From a7019d5e5944c00ef8303d44c1dda91bbbbba2fb Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Mon, 5 Oct 2026 16:34:22 +0200 Subject: [PATCH 1/7] fix(QTDI-3531): Fix uispec goal failure Run the static UI spec generator in an isolated classloader built from the plugin's own component-tools-webapp graph, so transitive dependencies of connector CARs (e.g. CXF 4 / jakarta servlet) no longer shadow the ones component-server needs and trigger a VerifyError. Make StaticResourceGenerator.route null-safe so the /api/v1/cache/clear route no longer fails with a NullPointerException. Add a unit test and an invoker IT guarding the uispec goal. Co-authored-by: Claude Sonnet 5.5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../generator/StaticResourceGenerator.java | 5 +- .../StaticResourceGeneratorTest.java | 49 ++++++++ talend-component-maven-plugin/pom.xml | 16 +++ .../src/it/uispec/pom.xml | 78 +++++++++++++ .../src/it/uispec/postbuild.groovy | 38 +++++++ .../component/maven/UiSpecGeneratorMojo.java | 107 +++++++++++++++++- .../maven/uispec-generator.properties | 16 +++ 7 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 component-tools-webapp/src/test/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGeneratorTest.java create mode 100644 talend-component-maven-plugin/src/it/uispec/pom.xml create mode 100644 talend-component-maven-plugin/src/it/uispec/postbuild.groovy create mode 100644 talend-component-maven-plugin/src/main/resources/org/talend/sdk/component/maven/uispec-generator.properties diff --git a/component-tools-webapp/src/main/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGenerator.java b/component-tools-webapp/src/main/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGenerator.java index 32de35b98fdc7..90795a8440175 100644 --- a/component-tools-webapp/src/main/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGenerator.java +++ b/component-tools-webapp/src/main/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGenerator.java @@ -381,14 +381,15 @@ private byte[] swallow(final Supplier dependency) { } } - private static Route route(final String id, final String path, final SortedMap queries, + // visible for testing + static Route route(final String id, final String path, final SortedMap queries, final Map requestHeaders, final Map responseHeaders, final String content) { return new Route(id, 200, path, queries, responseHeaders.isEmpty() ? singletonMap("Accept", responseHeaders.getOrDefault("Content-Type", "application/json")) : requestHeaders, responseHeaders.isEmpty() ? singletonMap("Content-Type", "application/json") : responseHeaders, - content.getBytes(StandardCharsets.UTF_8)); + content == null ? new byte[0] : content.getBytes(StandardCharsets.UTF_8)); } private static Route route(final String id, final String path, final SortedMap queries, diff --git a/component-tools-webapp/src/test/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGeneratorTest.java b/component-tools-webapp/src/test/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGeneratorTest.java new file mode 100644 index 0000000000000..dc6e396e3120e --- /dev/null +++ b/component-tools-webapp/src/test/java/org/talend/sdk/component/tools/webapp/standalone/generator/StaticResourceGeneratorTest.java @@ -0,0 +1,49 @@ +/** + * 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.tools.webapp.standalone.generator; + +import static java.util.Collections.emptyMap; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import java.util.TreeMap; + +import org.junit.jupiter.api.Test; +import org.talend.sdk.component.tools.webapp.standalone.Route; + +class StaticResourceGeneratorTest { + + @Test + void routeWithNullContentHasEmptyBody() { + final Route route = StaticResourceGenerator + .route("component_server_environment", "/api/v1/cache/clear", new TreeMap<>(), emptyMap(), emptyMap(), + null); + + assertEquals(200, route.getStatus()); + assertEquals("/api/v1/cache/clear", route.getPath()); + assertArrayEquals(new byte[0], route.getContent()); + } + + @Test + void routeWithContentKeepsUtf8Body() { + final Route route = StaticResourceGenerator + .route("component_server_environment", "/api/v1/environment", new TreeMap<>(), emptyMap(), emptyMap(), + "{\"a\":\"é\"}"); + + assertArrayEquals("{\"a\":\"é\"}".getBytes(StandardCharsets.UTF_8), route.getContent()); + } +} diff --git a/talend-component-maven-plugin/pom.xml b/talend-component-maven-plugin/pom.xml index 4af3c2d481c18..6709c236a5129 100644 --- a/talend-component-maven-plugin/pom.xml +++ b/talend-component-maven-plugin/pom.xml @@ -147,6 +147,22 @@ + + + src/main/resources + + org/talend/sdk/component/maven/uispec-generator.properties + + + + + src/main/resources + true + + org/talend/sdk/component/maven/uispec-generator.properties + + + org.apache.maven.plugins diff --git a/talend-component-maven-plugin/src/it/uispec/pom.xml b/talend-component-maven-plugin/src/it/uispec/pom.xml new file mode 100644 index 0000000000000..bdc79f4353b22 --- /dev/null +++ b/talend-component-maven-plugin/src/it/uispec/pom.xml @@ -0,0 +1,78 @@ + + + + 4.0.0 + + org.talend.sdk.component + component-runtime + @project.version@ + + + sample-it-uispec + Component Runtime :: Maven Plugin :: IT :: UiSpec + + + ${project.basedir} + ${talend.build.name.base}.sample.it.uispec + true + + + + + org.talend.sdk.component + component-api + @project.version@ + provided + + + + + + + org.talend.sdk.component + talend-component-maven-plugin + @project.version@ + + + org.talend.components + @it-component-artifact-id@ + @it-component-version@ + component + car + + + + org.apache.cxf + cxf-rt-transports-http + 4.1.8 + + + + + uispec + + uispec + + package + + ${project.build.directory}/talend-component-kit/uispec.zip + + + + + + + diff --git a/talend-component-maven-plugin/src/it/uispec/postbuild.groovy b/talend-component-maven-plugin/src/it/uispec/postbuild.groovy new file mode 100644 index 0000000000000..f17086c176ec5 --- /dev/null +++ b/talend-component-maven-plugin/src/it/uispec/postbuild.groovy @@ -0,0 +1,38 @@ +/** + * 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. + */ +import java.nio.file.Paths +import java.util.zip.ZipFile + +/** + * Checks that the uispec mojo generated its archive even if the plugin classpath is polluted by conflicting libraries + */ +static def checkUiSpecMojo(final File basedir) { + final File zip = Paths.get(basedir.toString()).resolve('target/talend-component-kit/uispec.zip').toFile() + if (!zip.exists()) { + throw new FileNotFoundException("Could not find uispec archive ${zip}") + } + final ZipFile archive = new ZipFile(zip) + try { + final List names = archive.entries().collect { it.name } + assert names.contains('component/') + assert names.contains('configuration/') + assert names.any { it.startsWith('component/') && it != 'component/' } + } finally { + archive.close() + } +} + +checkUiSpecMojo(basedir) diff --git a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java index f48d8577fb59f..68e51133c44ca 100644 --- a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java +++ b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java @@ -20,31 +20,62 @@ import static org.talend.sdk.component.maven.api.Audience.Type.TALEND_INTERNAL; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProjectHelper; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResolutionException; import org.talend.sdk.component.maven.api.Audience; -import org.talend.sdk.component.tools.webapp.standalone.generator.StaticUiSpecGenerator; @Audience(TALEND_INTERNAL) @Mojo(name = "uispec", defaultPhase = PACKAGE, threadSafe = true, requiresDependencyResolution = TEST) public class UiSpecGeneratorMojo extends BuildComponentM2RepositoryMojo { + private static final String GENERATOR_CLASS = + "org.talend.sdk.component.tools.webapp.standalone.generator.StaticUiSpecGenerator"; + + private static final String VERSIONS_RESOURCE = "uispec-generator.properties"; + @Parameter(defaultValue = "en") private Collection languages; @Parameter(defaultValue = "${maven.multiModuleProjectDirectory}/target/talend-component-kit/uispec.zip") private File uiSpecZip; + @Parameter(defaultValue = "${plugin}", readonly = true) + private PluginDescriptor pluginDescriptor; + + @Parameter(defaultValue = "${project.remotePluginRepositories}", readonly = true) + private List pluginRepositories; + @Component private MavenProjectHelper helper; + @Component + private RepositorySystem uiSpecRepositorySystem; + @Override public void doExecute() throws MojoExecutionException { super.doExecute(); @@ -52,7 +83,79 @@ public void doExecute() throws MojoExecutionException { setup.put("talend.component.server.maven.repository", m2Root.getAbsolutePath()); setup.put("talend.component.server.component.registry", getRegistry().toAbsolutePath().toString()); setup.put("talend.component.server.component.extend.dependencies", "false"); - new StaticUiSpecGenerator(setup, languages, uiSpecZip.toPath()).run(); + generate(setup); helper.attachArtifact(project, "zip", "uispec", uiSpecZip); } + + /** + * The plugin classloader also contains the dependencies of the components declared as plugin dependencies, + * which can shadow the libraries the embedded server needs (servlet APIs, CXF, ...) or add unrelated CDI + * extensions. The generator therefore runs in its own classloader, built like the component server image: + * the webapp graph plus the CDI container and Beam, which the components expect from the runtime. + */ + private void generate(final Map setup) throws MojoExecutionException { + final Thread thread = Thread.currentThread(); + final ClassLoader contextLoader = thread.getContextClassLoader(); + try (final URLClassLoader loader = + new URLClassLoader(isolatedClasspath(), ClassLoader.getPlatformClassLoader())) { + thread.setContextClassLoader(loader); + final Runnable generator = (Runnable) loader + .loadClass(GENERATOR_CLASS) + .getConstructor(Map.class, Collection.class, Path.class) + .newInstance(setup, languages, uiSpecZip.toPath()); + generator.run(); + } catch (final IOException | ReflectiveOperationException e) { + throw new MojoExecutionException(e.getMessage(), e); + } finally { + thread.setContextClassLoader(contextLoader); + } + } + + private URL[] isolatedClasspath() throws MojoExecutionException { + final Properties versions = loadGeneratorVersions(); + final String pluginVersion = pluginDescriptor.getVersion(); + final CollectRequest collect = new CollectRequest().setRepositories(pluginRepositories); + Stream + .of(new DefaultArtifact("org.talend.sdk.component", "component-tools-webapp", "jar", pluginVersion), + new DefaultArtifact("org.talend.sdk.component", "component-runtime-beam", "jar", + pluginVersion), + new DefaultArtifact("org.apache.openwebbeans", "openwebbeans-se", "jar", + versions.getProperty("openwebbeans.version")), + new DefaultArtifact("org.apache.beam", "beam-sdks-java-core", "jar", + versions.getProperty("beam.version"))) + .map(artifact -> new Dependency(artifact, "runtime")) + .forEach(collect::addDependency); + try { + return uiSpecRepositorySystem + .resolveDependencies(repositorySystemSession, new DependencyRequest(collect, null)) + .getArtifactResults() + .stream() + .map(ArtifactResult::getArtifact) + .map(a -> toUrl(a.getFile())) + .toArray(URL[]::new); + } catch (final DependencyResolutionException e) { + throw new MojoExecutionException("Can't resolve the uispec generator classpath: " + e.getMessage(), e); + } + } + + private Properties loadGeneratorVersions() throws MojoExecutionException { + try (final InputStream stream = UiSpecGeneratorMojo.class.getResourceAsStream(VERSIONS_RESOURCE)) { + if (stream == null) { + throw new MojoExecutionException("Missing " + VERSIONS_RESOURCE + " in the plugin"); + } + final Properties versions = new Properties(); + versions.load(stream); + return versions; + } catch (final IOException e) { + throw new MojoExecutionException("Can't read " + VERSIONS_RESOURCE, e); + } + } + + private static URL toUrl(final File file) { + try { + return file.toURI().toURL(); + } catch (final MalformedURLException e) { + throw new IllegalStateException(e); + } + } } diff --git a/talend-component-maven-plugin/src/main/resources/org/talend/sdk/component/maven/uispec-generator.properties b/talend-component-maven-plugin/src/main/resources/org/talend/sdk/component/maven/uispec-generator.properties new file mode 100644 index 0000000000000..7ed2c2ec3544d --- /dev/null +++ b/talend-component-maven-plugin/src/main/resources/org/talend/sdk/component/maven/uispec-generator.properties @@ -0,0 +1,16 @@ +# 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. + +openwebbeans.version=${owb.version} +beam.version=${beam.version} From 2e492f63e20ca0e36967acb54c6c55e97db5a539 Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Mon, 5 Oct 2026 16:45:13 +0200 Subject: [PATCH 2/7] docs(QTDI-3531): record uispec isolated classloader and manual validation knowledge Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- repository-knowledge.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/repository-knowledge.md b/repository-knowledge.md index a52a1c00c8e60..6ee1177733fa6 100644 --- a/repository-knowledge.md +++ b/repository-knowledge.md @@ -133,6 +133,24 @@ The repo has **two** JUnit testing modules under `component-runtime-testing/`: Always depend on `component-runtime-junit` for new test code. +### `uispec` goal — isolated classloader and how to verify it + +[2026-10-05 | QTDI-3531] `talend-component:uispec` (`StaticUiSpecGenerator`) starts an embedded server, so it must not run in +the mojo's own realm: that realm contains the plugin dependencies plus the transitive dependencies of +every connector CAR declared as plugin dependency, and a CXF 4 (jakarta servlet) there shadows CXF 3.6 +(javax) → `VerifyError`. The design therefore relies on an isolated classloader +(`UiSpecGeneratorMojo` + `uispec-generator.properties`, versions kept in lock-step with the plugin +version), guarded by the invoker IT `src/it/uispec`: + +```bash +mvn invoker:run -pl talend-component-maven-plugin -Dinvoker.test=uispec +``` + +[2026-10-05 | QTDI-3531] Manual validation against real connectors: in +`connectors/cloud/cloud-components-docker`, run +`mvn -o talend-component:uispec -PPUSH_DOCKER -Dcomponent-runtime.version=` +(~2 min, produces `target/talend-component-kit/uispec.zip`). + --- ## CI / Jenkinsfile patterns From 1860795f4cf37ce8470992e937b55377293410b5 Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Mon, 5 Oct 2026 16:56:55 +0200 Subject: [PATCH 3/7] fix: Deduplicate gmavenplus-plugin declaration in component-starter-server Maven 3.10+ rejects duplicate declarations for the same groupId/artifactId at POM validation, breaking `mvn -pl -am` and whole-reactor builds that include component-starter-server. The plugin was split into two blocks (one carrying , the other ) with identical ; merge them into a single declaration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- component-starter-server/pom.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/component-starter-server/pom.xml b/component-starter-server/pom.xml index 493239f96b90f..4417ef7c8f5fe 100644 --- a/component-starter-server/pom.xml +++ b/component-starter-server/pom.xml @@ -462,16 +462,6 @@ - - - org.codehaus.gmavenplus - gmavenplus-plugin - - true - - - - grab-maven-wrapper From a47512f7d70f6086f9fe82248e0634aac5aa656a Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Mon, 5 Oct 2026 17:11:54 +0200 Subject: [PATCH 4/7] fix(QTDI-3531): Complete JVM --add-opens flags for uispec invoker IT The uispec invoker IT's .mvn/jvm.config only opened java.net, java.lang and java.util, missing java.lang.invoke (and others). On JDK 17+ this makes the embedded Meecrowave/Tomcat + CDI server fail at startup with InaccessibleObjectException on MethodHandles$Lookup, failing the IT both locally and on Jenkins CI. Complete the set to match the --add-opens list already used by the root pom's java9 profile. Note: .mvn/ directories are not tracked anywhere else in this repo and are commonly excluded by global gitignore templates; this file was force-added since the invoker plugin requires it to configure the forked build's JVM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- talend-component-maven-plugin/src/it/uispec/.mvn/jvm.config | 1 + 1 file changed, 1 insertion(+) create mode 100644 talend-component-maven-plugin/src/it/uispec/.mvn/jvm.config diff --git a/talend-component-maven-plugin/src/it/uispec/.mvn/jvm.config b/talend-component-maven-plugin/src/it/uispec/.mvn/jvm.config new file mode 100644 index 0000000000000..d551cde9369db --- /dev/null +++ b/talend-component-maven-plugin/src/it/uispec/.mvn/jvm.config @@ -0,0 +1 @@ +--add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.nio.cs=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/sun.util.calendar=ALL-UNNAMED From 0bf42b04ef1db33e738d043db486ab0e92058fa3 Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Mon, 5 Oct 2026 18:04:02 +0200 Subject: [PATCH 5/7] fix(QTDI-3531): Address review round 1 on uispec mojo Resolve the generator classpath from the project repositories (the webapp, Beam and OpenWebBeans are regular dependencies, not plugins), extract the isolated run into a package-private method and add default build unit tests for the classloader isolation, TCCL restoration and error wrapping. Document the -Pci-build invoker command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- repository-knowledge.md | 9 +- .../component/maven/UiSpecGeneratorMojo.java | 20 +- .../maven/UiSpecGeneratorMojoTest.java | 183 ++++++++++++++++++ 3 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java diff --git a/repository-knowledge.md b/repository-knowledge.md index 6ee1177733fa6..f3a8b02e2d73b 100644 --- a/repository-knowledge.md +++ b/repository-knowledge.md @@ -140,12 +140,17 @@ the mojo's own realm: that realm contains the plugin dependencies plus the trans every connector CAR declared as plugin dependency, and a CXF 4 (jakarta servlet) there shadows CXF 3.6 (javax) → `VerifyError`. The design therefore relies on an isolated classloader (`UiSpecGeneratorMojo` + `uispec-generator.properties`, versions kept in lock-step with the plugin -version), guarded by the invoker IT `src/it/uispec`: +version), guarded by the invoker IT `src/it/uispec`. The invoker configuration lives in the `ci-build` +profile (also auto-activated when `JENKINS_URL` is set), and the IT must run against the freshly +packaged plugin (a bare `invoker:run` resolves a stale remote SNAPSHOT instead): ```bash -mvn invoker:run -pl talend-component-maven-plugin -Dinvoker.test=uispec +mvn verify -pl talend-component-maven-plugin -Pci-build -DskipTests -Dinvoker.test=uispec ``` +The mojo resolves the generator classpath from `${project.remoteProjectRepositories}` (the artifacts are +regular dependencies, not plugins); `UiSpecGeneratorMojoTest` covers the isolation logic in the default build. + [2026-10-05 | QTDI-3531] Manual validation against real connectors: in `connectors/cloud/cloud-components-docker`, run `mvn -o talend-component:uispec -PPUSH_DOCKER -Dcomponent-runtime.version=` diff --git a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java index 68e51133c44ca..e23e51f02564e 100644 --- a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java +++ b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java @@ -67,8 +67,8 @@ public class UiSpecGeneratorMojo extends BuildComponentM2RepositoryMojo { @Parameter(defaultValue = "${plugin}", readonly = true) private PluginDescriptor pluginDescriptor; - @Parameter(defaultValue = "${project.remotePluginRepositories}", readonly = true) - private List pluginRepositories; + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true) + private List projectRepositories; @Component private MavenProjectHelper helper; @@ -94,15 +94,19 @@ public void doExecute() throws MojoExecutionException { * the webapp graph plus the CDI container and Beam, which the components expect from the runtime. */ private void generate(final Map setup) throws MojoExecutionException { + runIsolated(isolatedClasspath(), GENERATOR_CLASS, setup, languages, uiSpecZip.toPath()); + } + + static void runIsolated(final URL[] classpath, final String generatorClass, final Map setup, + final Collection languages, final Path output) throws MojoExecutionException { final Thread thread = Thread.currentThread(); final ClassLoader contextLoader = thread.getContextClassLoader(); - try (final URLClassLoader loader = - new URLClassLoader(isolatedClasspath(), ClassLoader.getPlatformClassLoader())) { + try (final URLClassLoader loader = new URLClassLoader(classpath, ClassLoader.getPlatformClassLoader())) { thread.setContextClassLoader(loader); final Runnable generator = (Runnable) loader - .loadClass(GENERATOR_CLASS) + .loadClass(generatorClass) .getConstructor(Map.class, Collection.class, Path.class) - .newInstance(setup, languages, uiSpecZip.toPath()); + .newInstance(setup, languages, output); generator.run(); } catch (final IOException | ReflectiveOperationException e) { throw new MojoExecutionException(e.getMessage(), e); @@ -114,7 +118,7 @@ private void generate(final Map setup) throws MojoExecutionExcep private URL[] isolatedClasspath() throws MojoExecutionException { final Properties versions = loadGeneratorVersions(); final String pluginVersion = pluginDescriptor.getVersion(); - final CollectRequest collect = new CollectRequest().setRepositories(pluginRepositories); + final CollectRequest collect = new CollectRequest().setRepositories(projectRepositories); Stream .of(new DefaultArtifact("org.talend.sdk.component", "component-tools-webapp", "jar", pluginVersion), new DefaultArtifact("org.talend.sdk.component", "component-runtime-beam", "jar", @@ -138,7 +142,7 @@ private URL[] isolatedClasspath() throws MojoExecutionException { } } - private Properties loadGeneratorVersions() throws MojoExecutionException { + static Properties loadGeneratorVersions() throws MojoExecutionException { try (final InputStream stream = UiSpecGeneratorMojo.class.getResourceAsStream(VERSIONS_RESOURCE)) { if (stream == null) { throw new MojoExecutionException("Missing " + VERSIONS_RESOURCE + " in the plugin"); diff --git a/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java b/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java new file mode 100644 index 0000000000000..f9d8e37181505 --- /dev/null +++ b/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java @@ -0,0 +1,183 @@ +/** + * 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.maven; + +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.maven.plugin.MojoExecutionException; +import org.junit.jupiter.api.Test; + +class UiSpecGeneratorMojoTest { + + @Test + void generatorVersionsAreFiltered() throws MojoExecutionException { + final Properties versions = UiSpecGeneratorMojo.loadGeneratorVersions(); + assertFalse(versions.getProperty("openwebbeans.version", "").isEmpty()); + assertFalse(versions.getProperty("beam.version", "").isEmpty()); + versions.forEach((key, value) -> assertFalse(String.valueOf(value).contains("${"), key + "=" + value)); + } + + @Test + void generatorRunsInIsolatedLoader() throws Exception { + final Thread thread = Thread.currentThread(); + final ClassLoader before = thread.getContextClassLoader(); + final Map setup = new HashMap<>(); + + UiSpecGeneratorMojo + .runIsolated(testClasspath(), RecordingGenerator.class.getName(), setup, List.of("en"), + Paths.get("uispec.zip")); + + assertEquals("true", setup.get("tccl.is.generator.loader")); + assertEquals("true", setup.get("loader.is.url.loader")); + assertNotEquals(String.valueOf(System.identityHashCode(getClass().getClassLoader())), + setup.get("generator.loader")); + assertEquals("en", setup.get("languages")); + assertEquals("uispec.zip", setup.get("output")); + assertSame(before, thread.getContextClassLoader()); + } + + @Test + void contextLoaderIsRestoredWhenGeneratorFails() { + final Thread thread = Thread.currentThread(); + final ClassLoader before = thread.getContextClassLoader(); + + final IllegalStateException error = assertThrows(IllegalStateException.class, + () -> UiSpecGeneratorMojo + .runIsolated(testClasspath(), FailingGenerator.class.getName(), new HashMap<>(), List.of(), + Paths.get("uispec.zip"))); + + assertEquals("generator failure", error.getMessage()); + assertSame(before, thread.getContextClassLoader()); + } + + @Test + void unknownGeneratorIsWrapped() { + final Thread thread = Thread.currentThread(); + final ClassLoader before = thread.getContextClassLoader(); + + final MojoExecutionException error = assertThrows(MojoExecutionException.class, + () -> UiSpecGeneratorMojo + .runIsolated(testClasspath(), "org.talend.Missing", new HashMap<>(), List.of(), + Paths.get("uispec.zip"))); + + assertTrue(error.getCause() instanceof ClassNotFoundException); + assertSame(before, thread.getContextClassLoader()); + } + + @Test + void generatorWithoutExpectedConstructorIsWrapped() { + final MojoExecutionException error = assertThrows(MojoExecutionException.class, + () -> UiSpecGeneratorMojo + .runIsolated(testClasspath(), WrongConstructorGenerator.class.getName(), new HashMap<>(), + List.of(), Paths.get("uispec.zip"))); + + assertTrue(error.getCause() instanceof NoSuchMethodException); + } + + @Test + void generatorInstantiationErrorIsWrapped() { + final MojoExecutionException error = assertThrows(MojoExecutionException.class, + () -> UiSpecGeneratorMojo + .runIsolated(testClasspath(), BrokenConstructorGenerator.class.getName(), new HashMap<>(), + List.of(), Paths.get("uispec.zip"))); + + assertNotNull(error.getCause()); + assertTrue(error.getCause() instanceof InvocationTargetException); + } + + // the isolated loader only has the platform loader as parent, so it loads the generators from the test classes + private static URL[] testClasspath() { + return new URL[] { UiSpecGeneratorMojoTest.class.getProtectionDomain().getCodeSource().getLocation() }; + } + + public static class RecordingGenerator implements Runnable { + + private final Map setup; + + private final Collection languages; + + private final Path output; + + public RecordingGenerator(final Map setup, final Collection languages, + final Path output) { + this.setup = setup; + this.languages = languages; + this.output = output; + } + + @Override + public void run() { + final ClassLoader loader = getClass().getClassLoader(); + setup.put("generator.loader", String.valueOf(System.identityHashCode(loader))); + setup.put("loader.is.url.loader", String.valueOf(loader instanceof URLClassLoader)); + setup.put("tccl.is.generator.loader", + String.valueOf(loader == Thread.currentThread().getContextClassLoader())); + setup.put("languages", String.join(",", languages)); + setup.put("output", output.toString()); + } + } + + public static class FailingGenerator implements Runnable { + + public FailingGenerator(final Map setup, final Collection languages, + final Path output) { + // constructor shape expected by the mojo + } + + @Override + public void run() { + throw new IllegalStateException("generator failure"); + } + } + + public static class WrongConstructorGenerator implements Runnable { + + @Override + public void run() { + // never reached + } + } + + public static class BrokenConstructorGenerator implements Runnable { + + public BrokenConstructorGenerator(final Map setup, final Collection languages, + final Path output) { + throw new IllegalArgumentException("broken"); + } + + @Override + public void run() { + // never reached + } + } +} From 52215f2a04322020a465c01363c8f9fb3e7f321e Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Mon, 5 Oct 2026 18:50:00 +0200 Subject: [PATCH 6/7] fix(QTDI-3531): Address review round 2 on uispec mojo Extract the classpath resolution into a package-private static method and cover it with unit tests using a stubbed RepositorySystem. Hoist the arguments out of the assertThrows lambdas (java:S5778). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../component/maven/UiSpecGeneratorMojo.java | 16 ++- .../maven/UiSpecGeneratorMojoTest.java | 133 ++++++++++++++++-- 2 files changed, 132 insertions(+), 17 deletions(-) diff --git a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java index e23e51f02564e..6f44ae7ef2dc3 100644 --- a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java +++ b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java @@ -40,6 +40,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProjectHelper; import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.graph.Dependency; @@ -116,9 +117,14 @@ static void runIsolated(final URL[] classpath, final String generatorClass, fina } private URL[] isolatedClasspath() throws MojoExecutionException { - final Properties versions = loadGeneratorVersions(); - final String pluginVersion = pluginDescriptor.getVersion(); - final CollectRequest collect = new CollectRequest().setRepositories(projectRepositories); + return resolveClasspath(uiSpecRepositorySystem, repositorySystemSession, projectRepositories, + pluginDescriptor.getVersion(), loadGeneratorVersions()); + } + + static URL[] resolveClasspath(final RepositorySystem repositorySystem, final RepositorySystemSession session, + final List repositories, final String pluginVersion, final Properties versions) + throws MojoExecutionException { + final CollectRequest collect = new CollectRequest().setRepositories(repositories); Stream .of(new DefaultArtifact("org.talend.sdk.component", "component-tools-webapp", "jar", pluginVersion), new DefaultArtifact("org.talend.sdk.component", "component-runtime-beam", "jar", @@ -130,8 +136,8 @@ private URL[] isolatedClasspath() throws MojoExecutionException { .map(artifact -> new Dependency(artifact, "runtime")) .forEach(collect::addDependency); try { - return uiSpecRepositorySystem - .resolveDependencies(repositorySystemSession, new DependencyRequest(collect, null)) + return repositorySystem + .resolveDependencies(session, new DependencyRequest(collect, null)) .getArtifactResults() .stream() .map(ArtifactResult::getArtifact) diff --git a/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java b/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java index f9d8e37181505..1476f622c656f 100644 --- a/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java +++ b/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java @@ -23,7 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.File; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; @@ -33,8 +35,18 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; import org.apache.maven.plugin.MojoExecutionException; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResolutionException; +import org.eclipse.aether.resolution.DependencyResult; import org.junit.jupiter.api.Test; class UiSpecGeneratorMojoTest { @@ -71,10 +83,13 @@ void contextLoaderIsRestoredWhenGeneratorFails() { final Thread thread = Thread.currentThread(); final ClassLoader before = thread.getContextClassLoader(); + final URL[] classpath = testClasspath(); + final String generator = FailingGenerator.class.getName(); + final Map setup = new HashMap<>(); + final Path output = Paths.get("uispec.zip"); + final IllegalStateException error = assertThrows(IllegalStateException.class, - () -> UiSpecGeneratorMojo - .runIsolated(testClasspath(), FailingGenerator.class.getName(), new HashMap<>(), List.of(), - Paths.get("uispec.zip"))); + () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, List.of(), output)); assertEquals("generator failure", error.getMessage()); assertSame(before, thread.getContextClassLoader()); @@ -85,10 +100,12 @@ void unknownGeneratorIsWrapped() { final Thread thread = Thread.currentThread(); final ClassLoader before = thread.getContextClassLoader(); + final URL[] classpath = testClasspath(); + final Map setup = new HashMap<>(); + final Path output = Paths.get("uispec.zip"); + final MojoExecutionException error = assertThrows(MojoExecutionException.class, - () -> UiSpecGeneratorMojo - .runIsolated(testClasspath(), "org.talend.Missing", new HashMap<>(), List.of(), - Paths.get("uispec.zip"))); + () -> UiSpecGeneratorMojo.runIsolated(classpath, "org.talend.Missing", setup, List.of(), output)); assertTrue(error.getCause() instanceof ClassNotFoundException); assertSame(before, thread.getContextClassLoader()); @@ -96,25 +113,117 @@ void unknownGeneratorIsWrapped() { @Test void generatorWithoutExpectedConstructorIsWrapped() { + final URL[] classpath = testClasspath(); + final String generator = WrongConstructorGenerator.class.getName(); + final Map setup = new HashMap<>(); + final Path output = Paths.get("uispec.zip"); + final MojoExecutionException error = assertThrows(MojoExecutionException.class, - () -> UiSpecGeneratorMojo - .runIsolated(testClasspath(), WrongConstructorGenerator.class.getName(), new HashMap<>(), - List.of(), Paths.get("uispec.zip"))); + () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, List.of(), output)); assertTrue(error.getCause() instanceof NoSuchMethodException); } @Test void generatorInstantiationErrorIsWrapped() { + final URL[] classpath = testClasspath(); + final String generator = BrokenConstructorGenerator.class.getName(); + final Map setup = new HashMap<>(); + final Path output = Paths.get("uispec.zip"); + final MojoExecutionException error = assertThrows(MojoExecutionException.class, - () -> UiSpecGeneratorMojo - .runIsolated(testClasspath(), BrokenConstructorGenerator.class.getName(), new HashMap<>(), - List.of(), Paths.get("uispec.zip"))); + () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, List.of(), output)); assertNotNull(error.getCause()); assertTrue(error.getCause() instanceof InvocationTargetException); } + @Test + void classpathRequestCarriesRootsScopeAndRepositories() throws MojoExecutionException { + final RemoteRepository repository = new RemoteRepository.Builder("central", "default", "http://repo.local") + .build(); + final AtomicReference request = new AtomicReference<>(); + final RepositorySystem system = repositorySystem((req, session) -> { + request.set(req); + return new DependencyResult(req); + }); + + final URL[] classpath = + UiSpecGeneratorMojo.resolveClasspath(system, null, List.of(repository), "1.2.3", versions()); + + assertEquals(0, classpath.length); + final CollectRequest collect = request.get().getCollectRequest(); + assertEquals(List.of(repository), collect.getRepositories()); + assertEquals(List.of("org.talend.sdk.component:component-tools-webapp:jar:1.2.3", + "org.talend.sdk.component:component-runtime-beam:jar:1.2.3", + "org.apache.openwebbeans:openwebbeans-se:jar:9.9.1", + "org.apache.beam:beam-sdks-java-core:jar:9.9.2"), + collect.getDependencies().stream().map(d -> d.getArtifact().toString()).toList()); + assertTrue(collect.getDependencies().stream().allMatch(d -> "runtime".equals(d.getScope()))); + } + + @Test + void resolvedArtifactsAreConvertedToUrlsInOrder() throws MojoExecutionException { + final File first = new File("first.jar"); + final File second = new File("second.jar"); + final RepositorySystem system = repositorySystem((req, session) -> { + final DependencyResult result = new DependencyResult(req); + result.setArtifactResults(List.of(resolved(first), resolved(second))); + return result; + }); + + final URL[] classpath = UiSpecGeneratorMojo.resolveClasspath(system, null, List.of(), "1.2.3", versions()); + + assertEquals(2, classpath.length); + assertEquals(first.toURI().toString(), classpath[0].toString()); + assertEquals(second.toURI().toString(), classpath[1].toString()); + } + + @Test + void resolutionFailureIsWrapped() { + final Properties versions = versions(); + final List repositories = List.of(); + final RepositorySystem system = repositorySystem((req, session) -> { + throw new DependencyResolutionException(new DependencyResult(req), new IllegalStateException("offline")); + }); + + final MojoExecutionException error = assertThrows(MojoExecutionException.class, + () -> UiSpecGeneratorMojo.resolveClasspath(system, null, repositories, "1.2.3", versions)); + + assertTrue(error.getMessage().startsWith("Can't resolve the uispec generator classpath")); + assertTrue(error.getCause() instanceof DependencyResolutionException); + } + + private static Properties versions() { + final Properties versions = new Properties(); + versions.setProperty("openwebbeans.version", "9.9.1"); + versions.setProperty("beam.version", "9.9.2"); + return versions; + } + + private static ArtifactResult resolved(final File file) { + return new ArtifactResult(new ArtifactRequest()) + .setArtifact(new DefaultArtifact("g", "a", "jar", "1").setFile(file)); + } + + // only resolveDependencies is used by the mojo, every other method of the resolver is left unsupported + private static RepositorySystem repositorySystem(final DependencyResolver resolver) { + return (RepositorySystem) Proxy + .newProxyInstance(UiSpecGeneratorMojoTest.class.getClassLoader(), + new Class[] { RepositorySystem.class }, (proxy, method, args) -> { + if (!"resolveDependencies".equals(method.getName())) { + throw new UnsupportedOperationException(method.getName()); + } + return resolver.resolve((DependencyRequest) args[1], args[0]); + }); + } + + @FunctionalInterface + private interface DependencyResolver { + + DependencyResult resolve(DependencyRequest request, Object session) throws DependencyResolutionException; + } + // the isolated loader only has the platform loader as parent, so it loads the generators from the test classes private static URL[] testClasspath() { return new URL[] { UiSpecGeneratorMojoTest.class.getProtectionDomain().getCodeSource().getLocation() }; From 1b26a48629ae04b4be624c1eb9259d6cd57d352f Mon Sep 17 00:00:00 2001 From: Emmanuel GALLOIS Date: Tue, 6 Oct 2026 10:25:04 +0200 Subject: [PATCH 7/7] fix(QTDI-3531): Address review round 3 on uispec mojo Hoist List.of() out of the assertThrows lambdas (java:S5778) and cover the versions resource and malformed artifact URL error paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../component/maven/UiSpecGeneratorMojo.java | 6 +- .../maven/UiSpecGeneratorMojoTest.java | 65 +++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java index 6f44ae7ef2dc3..f7c59fccbb77b 100644 --- a/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java +++ b/talend-component-maven-plugin/src/main/java/org/talend/sdk/component/maven/UiSpecGeneratorMojo.java @@ -149,7 +149,11 @@ static URL[] resolveClasspath(final RepositorySystem repositorySystem, final Rep } static Properties loadGeneratorVersions() throws MojoExecutionException { - try (final InputStream stream = UiSpecGeneratorMojo.class.getResourceAsStream(VERSIONS_RESOURCE)) { + return readVersions(UiSpecGeneratorMojo.class.getResourceAsStream(VERSIONS_RESOURCE)); + } + + static Properties readVersions(final InputStream resource) throws MojoExecutionException { + try (final InputStream stream = resource) { if (stream == null) { throw new MojoExecutionException("Missing " + VERSIONS_RESOURCE + " in the plugin"); } diff --git a/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java b/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java index 1476f622c656f..ae1dc35e6c531 100644 --- a/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java +++ b/talend-component-maven-plugin/src/test/java/org/talend/sdk/component/maven/UiSpecGeneratorMojoTest.java @@ -24,8 +24,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; +import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; @@ -86,10 +90,11 @@ void contextLoaderIsRestoredWhenGeneratorFails() { final URL[] classpath = testClasspath(); final String generator = FailingGenerator.class.getName(); final Map setup = new HashMap<>(); + final List languages = List.of(); final Path output = Paths.get("uispec.zip"); final IllegalStateException error = assertThrows(IllegalStateException.class, - () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, List.of(), output)); + () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, languages, output)); assertEquals("generator failure", error.getMessage()); assertSame(before, thread.getContextClassLoader()); @@ -102,10 +107,11 @@ void unknownGeneratorIsWrapped() { final URL[] classpath = testClasspath(); final Map setup = new HashMap<>(); + final List languages = List.of(); final Path output = Paths.get("uispec.zip"); final MojoExecutionException error = assertThrows(MojoExecutionException.class, - () -> UiSpecGeneratorMojo.runIsolated(classpath, "org.talend.Missing", setup, List.of(), output)); + () -> UiSpecGeneratorMojo.runIsolated(classpath, "org.talend.Missing", setup, languages, output)); assertTrue(error.getCause() instanceof ClassNotFoundException); assertSame(before, thread.getContextClassLoader()); @@ -116,10 +122,11 @@ void generatorWithoutExpectedConstructorIsWrapped() { final URL[] classpath = testClasspath(); final String generator = WrongConstructorGenerator.class.getName(); final Map setup = new HashMap<>(); + final List languages = List.of(); final Path output = Paths.get("uispec.zip"); final MojoExecutionException error = assertThrows(MojoExecutionException.class, - () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, List.of(), output)); + () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, languages, output)); assertTrue(error.getCause() instanceof NoSuchMethodException); } @@ -129,10 +136,11 @@ void generatorInstantiationErrorIsWrapped() { final URL[] classpath = testClasspath(); final String generator = BrokenConstructorGenerator.class.getName(); final Map setup = new HashMap<>(); + final List languages = List.of(); final Path output = Paths.get("uispec.zip"); final MojoExecutionException error = assertThrows(MojoExecutionException.class, - () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, List.of(), output)); + () -> UiSpecGeneratorMojo.runIsolated(classpath, generator, setup, languages, output)); assertNotNull(error.getCause()); assertTrue(error.getCause() instanceof InvocationTargetException); @@ -194,6 +202,55 @@ void resolutionFailureIsWrapped() { assertTrue(error.getCause() instanceof DependencyResolutionException); } + @Test + void artifactWithMalformedUrlIsRejected() { + final File malformed = new File("malformed.jar") { + + @Override + public URI toURI() { + return URI.create("unknown-scheme://host/malformed.jar"); + } + }; + final Properties versions = versions(); + final List repositories = List.of(); + final RepositorySystem system = repositorySystem((req, session) -> { + final DependencyResult result = new DependencyResult(req); + result.setArtifactResults(List.of(resolved(malformed))); + return result; + }); + + final IllegalStateException error = assertThrows(IllegalStateException.class, + () -> UiSpecGeneratorMojo.resolveClasspath(system, null, repositories, "1.2.3", versions)); + + assertTrue(error.getCause() instanceof MalformedURLException); + } + + @Test + void missingVersionsResourceIsReported() { + final MojoExecutionException error = + assertThrows(MojoExecutionException.class, () -> UiSpecGeneratorMojo.readVersions(null)); + + assertEquals("Missing uispec-generator.properties in the plugin", error.getMessage()); + } + + @Test + void unreadableVersionsResourceIsWrapped() { + final IOException failure = new IOException("disk failure"); + final InputStream broken = new InputStream() { + + @Override + public int read() throws IOException { + throw failure; + } + }; + + final MojoExecutionException error = + assertThrows(MojoExecutionException.class, () -> UiSpecGeneratorMojo.readVersions(broken)); + + assertEquals("Can't read uispec-generator.properties", error.getMessage()); + assertSame(failure, error.getCause()); + } + private static Properties versions() { final Properties versions = new Properties(); versions.setProperty("openwebbeans.version", "9.9.1");