This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/extensions
in repository https://gitbox.apache.org/repos/asf/cxf-xjc-utils.git

commit f596a9dff08e6fa8505ac56593cc315da0fd2f17
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Sun Oct 25 11:09:49 2026 +0000

    Keep project dependencies off the classpath XJC loads code from
---
 README.md                                          |   2 +
 .../cxf/maven_plugin/AbstractXSDToJavaMojo.java    |  18 ++-
 .../apache/cxf/maven_plugin/XSDToJavaRunner.java   |  47 ++++++-
 .../cxf/maven_plugin/ExtensionClasspathTest.java   | 143 +++++++++++++++++++++
 4 files changed, 202 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index d8bf2a5..c9bd82b 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,8 @@ Then activate the plugin in your `wsdl2java` configuration:
 </plugin>
 ```
 
+XJC extensions are loaded only from the artifacts listed under `<extensions>` 
and from the `<dependencies>` of the `cxf-xjc-plugin` itself, not from the 
dependencies of your project. If you previously relied on an XJC extension 
being found as a project dependency, add it to `<extensions>` instead.
+
 ## Requirements
 
 - Java 17+
diff --git 
a/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/AbstractXSDToJavaMojo.java
 
b/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/AbstractXSDToJavaMojo.java
index 9727d98..44f5453 100644
--- 
a/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/AbstractXSDToJavaMojo.java
+++ 
b/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/AbstractXSDToJavaMojo.java
@@ -81,6 +81,8 @@ public abstract class AbstractXSDToJavaMojo extends 
AbstractMojo {
     
     /**
      * The extension artifacts that will be retrieved and added to the 
classpath.
+     * XJC plugins are only loaded from these and from the dependencies of 
this Maven plugin,
+     * not from the dependencies of the project.
      */
     @Parameter
     private List<String> extensions;
@@ -518,19 +520,29 @@ public abstract class AbstractXSDToJavaMojo extends 
AbstractMojo {
             JarArchiver jar = new JarArchiver();
             jar.setDestFile(file.getAbsoluteFile());
 
+            // Only the plugin's own dependencies go on the class path of the 
forked process. The
+            // project classpath is passed separately, for resolving 
classpath: catalog entries,
+            // as XJC loads plugins (and JAXP implementations) from the class 
path, and so would
+            // run code from any dependency of the project that registered one.
             Manifest manifest = new Manifest();
             Attribute attr = new Attribute();
             attr.setName("Class-Path");
             StringBuilder b = new StringBuilder(8000);
+            for (Artifact a : pluginArtifacts) {
+                
b.append(a.getFile().toURI().toURL().toExternalForm()).append(' ');
+            }
+            attr.setValue(b.toString());
+            manifest.getMainSection().addConfiguredAttribute(attr);
+
+            attr = new Attribute();
+            attr.setName(XSDToJavaRunner.PROJECT_CLASSPATH_ATTRIBUTE);
+            b = new StringBuilder(8000);
             for (String cp : getClasspathElements()) {
                 URI uri = mapLocation(cp);
                 if (uri != null) {
                     b.append(uri.toString()).append(' ');
                 }
             }
-            for (Artifact a : pluginArtifacts) {
-                
b.append(a.getFile().toURI().toURL().toExternalForm()).append(' ');
-            }
             attr.setValue(b.toString());
             manifest.getMainSection().addConfiguredAttribute(attr);
 
diff --git 
a/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java 
b/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java
index 2ee7ac9..2bb145a 100644
--- 
a/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java
+++ 
b/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java
@@ -28,6 +28,8 @@ import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
 
 import javax.xml.XMLConstants;
 import javax.xml.parsers.ParserConfigurationException;
@@ -66,6 +68,9 @@ import org.sonatype.plexus.build.incremental.BuildContext;
  * 
  */
 public class XSDToJavaRunner {
+    // The manifest attribute of the forked process's jar holding the project 
classpath
+    static final String PROJECT_CLASSPATH_ATTRIBUTE = 
"CXF-XJC-Project-Classpath";
+
     static Class<?> modelLoaderClass;
 
     // The JAXP system property (JAXP 1.5) that restricts which protocols may 
be used to resolve
@@ -149,16 +154,21 @@ public class XSDToJavaRunner {
                 urls.add(file.toURI().toURL());
             }
         }
+        // The "-classpath" arguments are the configured extension artifacts. 
XJC plugins are only
+        // loaded from these (and the cxf-xjc-plugin's own dependencies), not 
from the project's
+        // classpath above: XJC instantiates every plugin registered in 
META-INF/services on the
+        // classpath it is given, so any dependency of the project could 
otherwise run code here.
+        List<URL> extensionUrls = new ArrayList<URL>();
         for (int x = 0; x < args.length - 1; x++) {
             if ("-classpath".equals(args[x])) {
                 File file = getFile(args[x + 1], listener);
                 if (file != null && file.exists()) {
-                    cpList.add(file.getAbsolutePath());
-                    urls.add(file.getAbsoluteFile().toURI().toURL());
+                    extensionUrls.add(file.getAbsoluteFile().toURI().toURL());
                 }
                 x++;
             }
         }
+        urls.addAll(extensionUrls);
 
         try (URLClassLoader loader = new URLClassLoader(urls.toArray(new 
URL[0]),
                                                       
this.getClass().getClassLoader())) {
@@ -203,7 +213,9 @@ public class XSDToJavaRunner {
                 }
             };
 
-            for (URL url : urls) {
+            // the project classpath stays available to the catalog resolver 
(for classpath: entries)
+            // through "loader", but only the extensions are on the classpath 
XJC loads plugins from
+            for (URL url : extensionUrls) {
                 opt.classpaths.add(url);
             }
             if (checkXmlElementRef()) {
@@ -411,12 +423,37 @@ public class XSDToJavaRunner {
         return false;
     }
 
+    /**
+     * Reads the project classpath that AbstractXSDToJavaMojo records in the 
manifest of the jar
+     * used to run a forked process. It is kept out of the manifest 
Class-Path, so that the
+     * project's dependencies are not on the class path XJC loads plugins from.
+     */
+    static List<String> getProjectClasspath(File jar) throws IOException {
+        List<String> cplist = new ArrayList<String>();
+        if (!jar.isFile()) {
+            return cplist;
+        }
+        try (JarFile jarFile = new JarFile(jar)) {
+            Manifest manifest = jarFile.getManifest();
+            String cp = manifest == null ? null : 
manifest.getMainAttributes().getValue(PROJECT_CLASSPATH_ATTRIBUTE);
+            if (cp != null) {
+                for (String s : cp.split(" ")) {
+                    if (!s.isEmpty()) {
+                        cplist.add(s);
+                    }
+                }
+            }
+        }
+        return cplist;
+    }
+
     public static void main(String[] args) throws Exception {
         BuildContext context = new XJCBuildContext();
         XJCErrorListener listener = new XJCErrorListener(context);
 
-        List<String> cplist = new ArrayList<String>();
-        
+        // with "-jar" the class path is just the jar AbstractXSDToJavaMojo 
created for this run
+        List<String> cplist = getProjectClasspath(new 
File(System.getProperty("java.class.path")));
+
         File outputFile = getFile(args[args.length - 1], listener);
         if (outputFile == null) {
             outputFile = new File(args[args.length - 1]);
diff --git 
a/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/ExtensionClasspathTest.java
 
b/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/ExtensionClasspathTest.java
new file mode 100644
index 0000000..bdcadf0
--- /dev/null
+++ 
b/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/ExtensionClasspathTest.java
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.maven_plugin;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.xml.sax.ErrorHandler;
+
+import com.sun.tools.xjc.Options;
+import com.sun.tools.xjc.Plugin;
+import com.sun.tools.xjc.outline.Outline;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * XJC instantiates every plugin registered in META-INF/services on the 
classpath it loads plugins
+ * from, so that must only hold the configured extensions, not the project's 
dependencies.
+ */
+public class ExtensionClasspathTest {
+    @Rule public TemporaryFolder tmp = new TemporaryFolder();
+
+    /**
+     * A plugin that records whether XJC instantiated it.
+     */
+    public static class ProbePlugin extends Plugin {
+        static volatile boolean instantiated;
+
+        public ProbePlugin() {
+            instantiated = true;
+        }
+
+        @Override
+        public String getOptionName() {
+            return "Xcxf-probe";
+        }
+
+        @Override
+        public String getUsage() {
+            return "";
+        }
+
+        @Override
+        public boolean run(Outline outline, Options opt, ErrorHandler 
errorHandler) {
+            return true;
+        }
+    }
+
+    @Before
+    public void reset() {
+        ProbePlugin.instantiated = false;
+    }
+
+    // a jar registering ProbePlugin, as a dependency with a hostile 
META-INF/services entry would
+    private File probeJar() throws Exception {
+        File jar = tmp.newFile("probe-" + System.nanoTime() + ".jar");
+        try (JarOutputStream out = new 
JarOutputStream(Files.newOutputStream(jar.toPath()))) {
+            out.putNextEntry(new JarEntry("META-INF/services/" + 
Plugin.class.getName()));
+            
out.write(ProbePlugin.class.getName().getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+        }
+        return jar;
+    }
+
+    private int compile(List<String> extraArgs, List<String> projectClasspath) 
throws Exception {
+        File xsd = tmp.newFile("schema-" + System.nanoTime() + ".xsd");
+        Files.write(xsd.toPath(), ("<xs:schema 
xmlns:xs='http://www.w3.org/2001/XMLSchema'"
+            + " targetNamespace='urn:t'><xs:element name='root' 
type='xs:string'/></xs:schema>")
+            .getBytes(StandardCharsets.UTF_8));
+        File out = tmp.newFolder("gen-" + System.nanoTime());
+        List<String> args = new ArrayList<>(extraArgs);
+        // -Xlocator is a plugin built into XJC: enabling it makes XJC look up 
all plugins
+        args.addAll(Arrays.asList("-Xlocator", "-d", out.getAbsolutePath(), 
xsd.toURI().toString()));
+        XJCErrorListener listener = new XJCErrorListener(new 
XJCBuildContext());
+        return new XSDToJavaRunner(args.toArray(new String[0]), listener, out,
+                                   new ArrayList<>(projectClasspath)).run();
+    }
+
+    @Test
+    public void pluginOnProjectClasspathIsNotLoaded() throws Exception {
+        File jar = probeJar();
+        int rc = compile(Collections.<String>emptyList(), 
Collections.singletonList(jar.getAbsolutePath()));
+        Assert.assertEquals(0, rc);
+        Assert.assertFalse("a plugin registered by a project dependency must 
not be instantiated",
+                           ProbePlugin.instantiated);
+    }
+
+    @Test
+    public void pluginInExtensionIsLoaded() throws Exception {
+        File jar = probeJar();
+        int rc = compile(Arrays.asList("-classpath", jar.getAbsolutePath(), 
"-Xcxf-probe"),
+                         Collections.<String>emptyList());
+        Assert.assertEquals(0, rc);
+        Assert.assertTrue("a plugin from a configured extension must be 
loaded", ProbePlugin.instantiated);
+    }
+
+    @Test
+    public void projectClasspathIsReadFromManifest() throws Exception {
+        File jar = tmp.newFile("forked.jar");
+        Manifest manifest = new Manifest();
+        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, 
"1.0");
+        manifest.getMainAttributes().put(new 
Attributes.Name(XSDToJavaRunner.PROJECT_CLASSPATH_ATTRIBUTE),
+                                         "file:/a/b.jar file:/c/d%20e/ ");
+        try (OutputStream out = new 
JarOutputStream(Files.newOutputStream(jar.toPath()), manifest)) {
+            // just the manifest
+        }
+        Assert.assertEquals(Arrays.asList("file:/a/b.jar", "file:/c/d%20e/"),
+                            XSDToJavaRunner.getProjectClasspath(jar));
+        Assert.assertEquals(Collections.emptyList(),
+                            XSDToJavaRunner.getProjectClasspath(new 
File(tmp.getRoot(), "missing.jar")));
+    }
+}

Reply via email to