This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-secure-xml.git
The following commit(s) were added to refs/heads/main by this push:
new ec64a5f Add JaxbJakartaTest (#124)
ec64a5f is described below
commit ec64a5f240ce9dd80380948d02946ecbcb67c553
Author: Gary Gregory <[email protected]>
AuthorDate: Mon Nov 9 11:34:50 2026 -0500
Add JaxbJakartaTest (#124)
---
android-tests/build.gradle.kts | 3 +-
pom.xml | 60 ++++++++++++-
.../java/org/apache/commons/xml/osgi/OsgiIT.java | 7 +-
.../apache/commons/xml/secure/JaxbJakartaTest.java | 98 ++++++++++++++++++++++
4 files changed, 160 insertions(+), 8 deletions(-)
diff --git a/android-tests/build.gradle.kts b/android-tests/build.gradle.kts
index f16bde4..60e2c8a 100644
--- a/android-tests/build.gradle.kts
+++ b/android-tests/build.gradle.kts
@@ -107,7 +107,8 @@ android {
tasks.withType<JavaCompile>().configureEach {
exclude(
"**/ShadingFootprintTest.java",
- "**/JaxbTest.java",
+ "**/JaxbJavaxTest.java",
+ "**/JaxbJakartaTest.java",
"**/OsgiIT.java",
"**/FallbackIgnoreLSResourceResolverInitializationTest.java"
)
diff --git a/pom.xml b/pom.xml
index dae26f8..75b7703 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,8 @@ limitations under the License.
<!-- Test-only JAXB dependencies. -->
<commons.jaxb.api.version>2.3.1</commons.jaxb.api.version>
<commons.jaxb.runtime.version>2.3.9</commons.jaxb.runtime.version>
+ <commons.jaxb.jakarta.api.version>3.0.1</commons.jaxb.jakarta.api.version>
+
<commons.jaxb.jakarta.runtime.version>3.0.2</commons.jaxb.jakarta.runtime.version>
<!--
jacoco-maven-plugin: Should only get better.
@@ -153,6 +155,19 @@ limitations under the License.
<artifactId>jaxb-runtime</artifactId>
<version>${commons.jaxb.runtime.version}</version>
<scope>test</scope>
+ <exclusions>
+ <!-- The Javax API is supplied explicitly above; Jakarta 3 uses the
same Maven coordinates as this transitive 2.x API. -->
+ <exclusion>
+ <groupId>jakarta.xml.bind</groupId>
+ <artifactId>jakarta.xml.bind-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.xml.bind</groupId>
+ <artifactId>jakarta.xml.bind-api</artifactId>
+ <version>${commons.jaxb.jakarta.api.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -209,14 +224,15 @@ limitations under the License.
</pluginManagement>
<plugins>
<!--
- Run the test suite eight times, each with a distinct JAXP combination:
+ Run the test suite with eight distinct JAXP combinations, plus an
isolated Jakarta JAXB execution:
- test-jdk-xerces: stock JDK TrAX and XPath over Apache Xerces
SAX/DOM
(the only cell where
jdk.xml.overrideDefaultParser decides which parser rewrites sources)
- test-saxon: Saxon-HE (TrAX, XPath) over stock JDK SAX
- test-saxon-xerces: Saxon-HE (TrAX, XPath) over stock JDK SAX or
Apache Xerces SAX
(Saxon prefers the JDK SAX provider but may
resolve to Xerces)
- - test-stockjdk: stock JDK factories only (every group except
xpath3)
+ - test-stockjdk: stock JDK factories only (every group except
xpath3 and jaxb-jakarta)
+ - test-jaxb-jakarta: Jakarta JAXB over stock JDK SAX and StAX
- test-woodstox: Woodstox (StAX)
- test-xalan: Apache Xalan (TrAX, XPath) over stock JDK SAX
- test-xalan-xerces: Apache Xalan (TrAX, XPath) over Apache Xerces
SAX
@@ -233,6 +249,8 @@ limitations under the License.
Inherited by every execution defined below.
-->
<configuration>
+ <!-- Jakarta JAXB has its own isolated runtime execution. -->
+ <excludedGroups>jaxb-jakarta</excludedGroups>
<systemPropertyVariables>
<!-- Allow all protocols for external DTDs, schemas, and
stylesheets. -->
<javax.xml.accessExternalDTD>all</javax.xml.accessExternalDTD>
@@ -245,6 +263,34 @@ limitations under the License.
<id>default-test</id>
<phase>none</phase>
</execution>
+ <execution>
+ <id>test-jaxb-jakarta</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+
<reportsDirectory>${project.build.directory}/surefire-reports/jaxb-jakarta</reportsDirectory>
+ <groups>jaxb-jakarta</groups>
+ <excludedGroups>xpath3</excludedGroups>
+ <!-- Remove the Javax runtime closure before adding the Jakarta
runtime closure. -->
+ <classpathDependencyExcludes>
+ <exclude>net.sf.saxon:Saxon-HE</exclude>
+ <exclude>javax.xml.bind:jaxb-api</exclude>
+ <exclude>javax.activation:javax.activation-api</exclude>
+ <exclude>org.glassfish.jaxb:jaxb-runtime</exclude>
+ <exclude>org.glassfish.jaxb:txw2</exclude>
+ <exclude>com.sun.istack:istack-commons-runtime</exclude>
+ <exclude>com.sun.activation:jakarta.activation</exclude>
+ </classpathDependencyExcludes>
+ <additionalClasspathDependencies>
+ <dependency>
+ <groupId>org.glassfish.jaxb</groupId>
+ <artifactId>jaxb-runtime</artifactId>
+ <version>${commons.jaxb.jakarta.runtime.version}</version>
+ </dependency>
+ </additionalClasspathDependencies>
+ </configuration>
+ </execution>
<execution>
<id>test-saxon</id>
<goals>
@@ -281,8 +327,8 @@ limitations under the License.
</goals>
<configuration>
<reportsDirectory>${project.build.directory}/surefire-reports/stockjdk</reportsDirectory>
- <!-- Run every tagged and untagged test except xpath3, which
needs Saxon on the classpath. -->
- <excludedGroups>xpath3</excludedGroups>
+ <!-- Run every tagged and untagged test except xpath3 and
jaxb-jakarta, which have separate executions. -->
+ <excludedGroups>xpath3,jaxb-jakarta</excludedGroups>
<classpathDependencyExcludes>
<exclude>net.sf.saxon:Saxon-HE</exclude>
</classpathDependencyExcludes>
@@ -612,6 +658,7 @@ limitations under the License.
<configuration>
<!-- The suite runs once per JAXP combination, each execution
into its own directory. -->
<reportsDirectories>
+
<reportsDirectory>${project.build.directory}/surefire-reports/jaxb-jakarta</reportsDirectory>
<reportsDirectory>${project.build.directory}/surefire-reports/jdk-xerces</reportsDirectory>
<reportsDirectory>${project.build.directory}/surefire-reports/saxon</reportsDirectory>
<reportsDirectory>${project.build.directory}/surefire-reports/saxon-xerces</reportsDirectory>
@@ -804,6 +851,11 @@ limitations under the License.
Every other execution is silenced; their classpath swaps have
no native equivalent.
-->
+ <!-- The native image replays the Javax classpath; Jakarta is
tested in its separate JVM execution. -->
+ <execution>
+ <id>test-jaxb-jakarta</id>
+ <phase>none</phase>
+ </execution>
<execution>
<id>test-xalan</id>
<phase>none</phase>
diff --git a/src/test/java/org/apache/commons/xml/osgi/OsgiIT.java
b/src/test/java/org/apache/commons/xml/osgi/OsgiIT.java
index 6b0647f..746ed70 100644
--- a/src/test/java/org/apache/commons/xml/osgi/OsgiIT.java
+++ b/src/test/java/org/apache/commons/xml/osgi/OsgiIT.java
@@ -32,7 +32,6 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
@@ -122,9 +121,11 @@ class OsgiIT {
private static final String HOST_SYMBOLIC_NAME =
"org.apache.commons.xml.secure";
/**
- * Test classes that cannot run inside a framework: {@code
ShadingFootprintTest} reads {@code target/classes} directly.
+ * Test classes that cannot run inside a framework: {@code
ShadingFootprintTest} reads {@code target/classes} directly;
+ * {@code JaxbJakartaTest} requires its isolated Jakarta runtime.
*/
- private static final List<String> NOT_IN_OSGI =
Collections.singletonList("org.apache.commons.xml.secure.ShadingFootprintTest");
+ private static final List<String> NOT_IN_OSGI =
Arrays.asList("org.apache.commons.xml.secure.ShadingFootprintTest",
+ "org.apache.commons.xml.secure.JaxbJakartaTest");
/**
* Test methods that cannot run inside a framework.
diff --git a/src/test/java/org/apache/commons/xml/secure/JaxbJakartaTest.java
b/src/test/java/org/apache/commons/xml/secure/JaxbJakartaTest.java
new file mode 100644
index 0000000..55ae4da
--- /dev/null
+++ b/src/test/java/org/apache/commons/xml/secure/JaxbJakartaTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ *
+ * https://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.commons.xml.secure;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.sax.SAXSource;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.Unmarshaller;
+import jakarta.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Tests JAXB integration.
+ * <p>
+ * This class' two public methods serve as examples for the Javadoc {@code
overview.html} file.
+ * </p>
+ */
+@Tag("jaxb-jakarta")
+public class JaxbJakartaTest {
+
+ @XmlRootElement
+ static class MyJaxbModel {
+ // JAXB model fields and methods
+ }
+
+ @Test
+ void testUnmarshalSecurelyWithSax() throws Exception {
+ try (InputStream xmlStream = new
ByteArrayInputStream("<myJaxbModel/>".getBytes(StandardCharsets.UTF_8))) {
+ assertNotNull(new
JaxbJakartaTest().unmarshalSecurelyWithSax(xmlStream));
+ }
+ }
+
+ @Test
+ void testUnmarshalSecurelyWithStax() throws Exception {
+ try (InputStream xmlStream = new
ByteArrayInputStream("<myJaxbModel/>".getBytes(StandardCharsets.UTF_8))) {
+ assertNotNull(new
JaxbJakartaTest().unmarshalSecurelyWithStax(xmlStream));
+ }
+ }
+
+ public MyJaxbModel unmarshalSecurelyWithSax(final InputStream xmlStream)
throws Exception {
+ final JAXBContext context = JAXBContext.newInstance(MyJaxbModel.class);
+ final Unmarshaller unmarshaller = context.createUnmarshaller();
+ // Create a secure SAXParserFactory via Apache Commons Secure XML
+ final SAXParserFactory spf =
SecureSAXParserFactory.newDefaultNSInstance();
+ // Generate a hardened XMLReader and wrap the input source
+ final XMLReader xmlReader = spf.newSAXParser().getXMLReader();
+ final SAXSource source = new SAXSource(xmlReader, new
InputSource(xmlStream));
+ // With the default resolver configuration, external DTDs and entities
are prevented from being fetched or resolved, protecting against XXE attacks,
protecting against XXE attacks.
+ // Entity-expansion limits use FEATURE_SECURE_PROCESSING on JDK
parsers;
+ // Android support is implementation-dependent and best-effort.
+ return (MyJaxbModel) unmarshaller.unmarshal(source);
+ }
+
+ public MyJaxbModel unmarshalSecurelyWithStax(final InputStream xmlStream)
throws Exception {
+ final JAXBContext context = JAXBContext.newInstance(MyJaxbModel.class);
+ final Unmarshaller unmarshaller = context.createUnmarshaller();
+ // Create a secure XMLInputFactory via Apache Commons Secure XML
+ final XMLInputFactory xif = SecureXMLInputFactory.newDefaultFactory();
+ // Create a hardened cursor reader
+ final XMLStreamReader xmlReader = xif.createXMLStreamReader(xmlStream);
+ try {
+ // With the default resolver configuration, external DTDs and
entities are prevented from being fetched or resolved, protecting against XXE
attacks, protecting against XXE attacks.
+ // Entity-expansion protection is implementation-dependent and
best-effort
+ // because StAX exposes no secure-processing feature.
+ return (MyJaxbModel) unmarshaller.unmarshal(xmlReader);
+ } finally {
+ xmlReader.close();
+ }
+ }
+}