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

ppkarwasz pushed a commit to branch feature/jaxp-factory-methods
in repository https://gitbox.apache.org/repos/asf/commons-xml.git

commit 444dc82fc90e2e670722b91f68c2ceeb89c4c944
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Thu Aug 27 22:40:12 2026 +0200

    Add the Java 13 newNSInstance factory methods, resolved at runtime
    
    DocumentBuilderFactory and SAXParserFactory gain the newNSInstance,
    newNSInstance(String, ClassLoader) and newDefaultNSInstance mirrors,
    resolved through MethodHandles.publicLookup() like the Java 9 methods.
    Where the platform predates them, the fallback enables namespace
    awareness on the corresponding newInstance lookup, the behavior the
    JAXP methods are specified to have, so the non-default variants work on
    every supported platform including Android; newDefaultNSInstance
    inherits the newDefaultInstance fallback chain.
    
    Assisted-By: Claude Fable 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01WWJ4LAxx3TX5RwNvwKbAS3
---
 .../xml/HardeningDocumentBuilderFactory.java       | 98 ++++++++++++++++++++++
 .../commons/xml/HardeningSAXParserFactory.java     | 98 ++++++++++++++++++++++
 .../commons/xml/HardeningFactoriesSmokeTest.java   | 64 ++++++++++++++
 3 files changed, 260 insertions(+)

diff --git 
a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java 
b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
index 5909133..aee284c 100644
--- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
@@ -56,6 +56,13 @@ public final class HardeningDocumentBuilderFactory {
 
     private static final MethodHandle NEW_DEFAULT_INSTANCE = 
findStatic("newDefaultInstance", 
MethodType.methodType(DocumentBuilderFactory.class));
 
+    private static final MethodHandle NEW_DEFAULT_NS_INSTANCE = 
findStatic("newDefaultNSInstance", 
MethodType.methodType(DocumentBuilderFactory.class));
+
+    private static final MethodHandle NEW_NS_INSTANCE = 
findStatic("newNSInstance", 
MethodType.methodType(DocumentBuilderFactory.class));
+
+    private static final MethodHandle NEW_NS_INSTANCE_BY_CLASS_NAME = 
findStatic("newNSInstance",
+            MethodType.methodType(DocumentBuilderFactory.class, String.class, 
ClassLoader.class));
+
     private static MethodHandle findStatic(final String name, final MethodType 
type) {
         try {
             return 
MethodHandles.publicLookup().findStatic(DocumentBuilderFactory.class, name, 
type);
@@ -127,6 +134,36 @@ public static DocumentBuilderFactory newDefaultInstance() {
         return newInstance(JDK_DOCUMENT_BUILDER_FACTORY, null);
     }
 
+    /**
+     * Returns a new, hardened, namespace-aware {@link DocumentBuilderFactory} 
of the system-default implementation.
+     * <p>
+     * Obtained as by {@code DocumentBuilderFactory.newDefaultNSInstance()} 
where the platform provides it (Java 13 or later), and by enabling namespace
+     * awareness on {@link #newDefaultInstance()} otherwise, the behavior the 
JAXP method is specified to have.
+     * </p>
+     *
+     * @return A hardened, namespace-aware factory.
+     * @throws IllegalStateException     Thrown if a required hardening 
setting cannot be applied to the underlying implementation.
+     * @throws FactoryConfigurationError Thrown if the running platform 
provides neither {@code newDefaultInstance()} nor the JDK's built-in 
implementation
+     *                                   (for example Android).
+     */
+    public static DocumentBuilderFactory newDefaultNSInstance() {
+        if (NEW_DEFAULT_NS_INSTANCE != null) {
+            final DocumentBuilderFactory factory;
+            try {
+                factory = (DocumentBuilderFactory) 
NEW_DEFAULT_NS_INSTANCE.invokeExact();
+            } catch (final FactoryConfigurationError e) {
+                throw e;
+            } catch (final Throwable e) {
+                // Unreachable: the looked-up method declares no other 
exceptions.
+                throw new IllegalStateException(e);
+            }
+            return harden(factory);
+        }
+        final DocumentBuilderFactory factory = newDefaultInstance();
+        factory.setNamespaceAware(true);
+        return factory;
+    }
+
     /**
      * Returns a new, hardened {@link DocumentBuilderFactory}.
      *
@@ -156,6 +193,67 @@ public static DocumentBuilderFactory newInstance(final 
String factoryClassName,
         return harden(DocumentBuilderFactory.newInstance(factoryClassName, 
classLoader));
     }
 
+    /**
+     * Returns a new, hardened, namespace-aware {@link DocumentBuilderFactory}.
+     * <p>
+     * Obtained as by {@code DocumentBuilderFactory.newNSInstance()} where the 
platform provides it (Java 13 or later), and by enabling namespace awareness on
+     * {@link #newInstance()} otherwise, the behavior the JAXP method is 
specified to have.
+     * </p>
+     *
+     * @return A hardened, namespace-aware factory.
+     * @throws IllegalStateException     Thrown if a required hardening 
setting cannot be applied to the underlying implementation.
+     * @throws FactoryConfigurationError Thrown from a factory in case of a 
{@link java.util.ServiceConfigurationError service configuration error} or if 
the
+     *                                   implementation is not available or 
cannot be instantiated.
+     */
+    public static DocumentBuilderFactory newNSInstance() {
+        if (NEW_NS_INSTANCE != null) {
+            final DocumentBuilderFactory factory;
+            try {
+                factory = (DocumentBuilderFactory) 
NEW_NS_INSTANCE.invokeExact();
+            } catch (final FactoryConfigurationError e) {
+                throw e;
+            } catch (final Throwable e) {
+                // Unreachable: the looked-up method declares no other 
exceptions.
+                throw new IllegalStateException(e);
+            }
+            return harden(factory);
+        }
+        final DocumentBuilderFactory factory = newInstance();
+        factory.setNamespaceAware(true);
+        return factory;
+    }
+
+    /**
+     * Returns a new, hardened, namespace-aware {@link DocumentBuilderFactory} 
of the given implementation class.
+     * <p>
+     * Obtained as by {@code DocumentBuilderFactory.newNSInstance(String, 
ClassLoader)} where the platform provides it (Java 13 or later), and by enabling
+     * namespace awareness on {@link #newInstance(String, ClassLoader)} 
otherwise, the behavior the JAXP method is specified to have.
+     * </p>
+     *
+     * @param factoryClassName The fully qualified class name of the {@link 
DocumentBuilderFactory} implementation.
+     * @param classLoader      The class loader used to load the factory 
class; {@code null} means the current thread's context class loader.
+     * @return A hardened, namespace-aware factory.
+     * @throws IllegalStateException     Thrown if a required hardening 
setting cannot be applied to the underlying implementation.
+     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is 
{@code null} or the factory class cannot be loaded or instantiated.
+     */
+    public static DocumentBuilderFactory newNSInstance(final String 
factoryClassName, final ClassLoader classLoader) {
+        if (NEW_NS_INSTANCE_BY_CLASS_NAME != null) {
+            final DocumentBuilderFactory factory;
+            try {
+                factory = (DocumentBuilderFactory) 
NEW_NS_INSTANCE_BY_CLASS_NAME.invokeExact(factoryClassName, classLoader);
+            } catch (final FactoryConfigurationError e) {
+                throw e;
+            } catch (final Throwable e) {
+                // Unreachable: the looked-up method declares no other 
exceptions.
+                throw new IllegalStateException(e);
+            }
+            return harden(factory);
+        }
+        final DocumentBuilderFactory factory = newInstance(factoryClassName, 
classLoader);
+        factory.setNamespaceAware(true);
+        return factory;
+    }
+
     /**
      * Sets a feature on the given factory, throwing a {@link 
HardeningException} if the implementation does not recognize it.
      *
diff --git 
a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java 
b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
index eb16def..36d4585 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
@@ -66,6 +66,13 @@ public final class HardeningSAXParserFactory {
 
     private static final MethodHandle NEW_DEFAULT_INSTANCE = 
findStatic("newDefaultInstance", MethodType.methodType(SAXParserFactory.class));
 
+    private static final MethodHandle NEW_DEFAULT_NS_INSTANCE = 
findStatic("newDefaultNSInstance", 
MethodType.methodType(SAXParserFactory.class));
+
+    private static final MethodHandle NEW_NS_INSTANCE = 
findStatic("newNSInstance", MethodType.methodType(SAXParserFactory.class));
+
+    private static final MethodHandle NEW_NS_INSTANCE_BY_CLASS_NAME = 
findStatic("newNSInstance",
+            MethodType.methodType(SAXParserFactory.class, String.class, 
ClassLoader.class));
+
     private static MethodHandle findStatic(final String name, final MethodType 
type) {
         try {
             return 
MethodHandles.publicLookup().findStatic(SAXParserFactory.class, name, type);
@@ -184,6 +191,36 @@ public static SAXParserFactory newDefaultInstance() {
         return newInstance(JDK_SAX_PARSER_FACTORY, null);
     }
 
+    /**
+     * Returns a new, hardened, namespace-aware {@link SAXParserFactory} of 
the system-default implementation.
+     * <p>
+     * Obtained as by {@code SAXParserFactory.newDefaultNSInstance()} where 
the platform provides it (Java 13 or later), and by enabling namespace 
awareness on
+     * {@link #newDefaultInstance()} otherwise, the behavior the JAXP method 
is specified to have.
+     * </p>
+     *
+     * @return A hardened, namespace-aware factory.
+     * @throws IllegalStateException     Thrown if a required hardening 
setting cannot be applied to the underlying implementation.
+     * @throws FactoryConfigurationError Thrown if the running platform 
provides neither {@code newDefaultInstance()} nor the JDK's built-in 
implementation
+     *                                   (for example Android).
+     */
+    public static SAXParserFactory newDefaultNSInstance() {
+        if (NEW_DEFAULT_NS_INSTANCE != null) {
+            final SAXParserFactory factory;
+            try {
+                factory = (SAXParserFactory) 
NEW_DEFAULT_NS_INSTANCE.invokeExact();
+            } catch (final FactoryConfigurationError e) {
+                throw e;
+            } catch (final Throwable e) {
+                // Unreachable: the looked-up method declares no other 
exceptions.
+                throw new IllegalStateException(e);
+            }
+            return harden(factory);
+        }
+        final SAXParserFactory factory = newDefaultInstance();
+        factory.setNamespaceAware(true);
+        return factory;
+    }
+
     /**
      * Creates a new hardened, namespace-aware {@link XMLReader} for the TrAX 
wrappers to parse sources with.
      *
@@ -227,6 +264,67 @@ public static SAXParserFactory newInstance(final String 
factoryClassName, final
         return harden(SAXParserFactory.newInstance(factoryClassName, 
classLoader));
     }
 
+    /**
+     * Returns a new, hardened, namespace-aware {@link SAXParserFactory}.
+     * <p>
+     * Obtained as by {@code SAXParserFactory.newNSInstance()} where the 
platform provides it (Java 13 or later), and by enabling namespace awareness on
+     * {@link #newInstance()} otherwise, the behavior the JAXP method is 
specified to have.
+     * </p>
+     *
+     * @return A hardened, namespace-aware factory.
+     * @throws IllegalStateException     Thrown if a required hardening 
setting cannot be applied to the underlying implementation.
+     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} 
in case of a {@link java.util.ServiceConfigurationError service configuration
+     *                                   error} or if the implementation is 
not available or cannot be instantiated.
+     */
+    public static SAXParserFactory newNSInstance() {
+        if (NEW_NS_INSTANCE != null) {
+            final SAXParserFactory factory;
+            try {
+                factory = (SAXParserFactory) NEW_NS_INSTANCE.invokeExact();
+            } catch (final FactoryConfigurationError e) {
+                throw e;
+            } catch (final Throwable e) {
+                // Unreachable: the looked-up method declares no other 
exceptions.
+                throw new IllegalStateException(e);
+            }
+            return harden(factory);
+        }
+        final SAXParserFactory factory = newInstance();
+        factory.setNamespaceAware(true);
+        return factory;
+    }
+
+    /**
+     * Returns a new, hardened, namespace-aware {@link SAXParserFactory} of 
the given implementation class.
+     * <p>
+     * Obtained as by {@code SAXParserFactory.newNSInstance(String, 
ClassLoader)} where the platform provides it (Java 13 or later), and by 
enabling namespace
+     * awareness on {@link #newInstance(String, ClassLoader)} otherwise, the 
behavior the JAXP method is specified to have.
+     * </p>
+     *
+     * @param factoryClassName The fully qualified class name of the {@link 
SAXParserFactory} implementation.
+     * @param classLoader      The class loader used to load the factory 
class; {@code null} means the current thread's context class loader.
+     * @return A hardened, namespace-aware factory.
+     * @throws IllegalStateException     Thrown if a required hardening 
setting cannot be applied to the underlying implementation.
+     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is 
{@code null} or the factory class cannot be loaded or instantiated.
+     */
+    public static SAXParserFactory newNSInstance(final String 
factoryClassName, final ClassLoader classLoader) {
+        if (NEW_NS_INSTANCE_BY_CLASS_NAME != null) {
+            final SAXParserFactory factory;
+            try {
+                factory = (SAXParserFactory) 
NEW_NS_INSTANCE_BY_CLASS_NAME.invokeExact(factoryClassName, classLoader);
+            } catch (final FactoryConfigurationError e) {
+                throw e;
+            } catch (final Throwable e) {
+                // Unreachable: the looked-up method declares no other 
exceptions.
+                throw new IllegalStateException(e);
+            }
+            return harden(factory);
+        }
+        final SAXParserFactory factory = newInstance(factoryClassName, 
classLoader);
+        factory.setNamespaceAware(true);
+        return factory;
+    }
+
     private static void setFeature(final SAXParserFactory factory, final 
String feature, final boolean value) {
         try {
             factory.setFeature(feature, value);
diff --git 
a/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java 
b/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java
index 69acdd8..0b4372f 100644
--- a/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java
+++ b/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java
@@ -226,6 +226,70 @@ void newDefaultInstanceSAXParserFactoryIsUsable() throws 
Exception {
         assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
     }
 
+    // The newNSInstance family (Java 13) falls back to enabling namespace 
awareness on the corresponding newInstance lookup, the behavior the JAXP methods
+    // are specified to have, so the non-default variants work on every 
platform including Android.
+    @Test
+    @Tag("dom")
+    void newNSInstanceDocumentBuilderFactoryIsNamespaceAware() throws 
Exception {
+        final DocumentBuilderFactory factory = 
HardeningDocumentBuilderFactory.newNSInstance();
+        assertTrue(factory.isNamespaceAware());
+        assertNotNull(factory.newDocumentBuilder().parse(new InputSource(new 
StringReader(BENIGN_XML))).getDocumentElement());
+        if (!AttackTestSupport.IS_ANDROID) {
+            
assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+        }
+    }
+
+    @Test
+    @Tag("dom")
+    void newDefaultNSInstanceDocumentBuilderFactoryIsNamespaceAware() throws 
Exception {
+        if (AttackTestSupport.IS_ANDROID) {
+            assertThrows(FactoryConfigurationError.class, 
HardeningDocumentBuilderFactory::newDefaultNSInstance);
+            return;
+        }
+        final DocumentBuilderFactory factory = 
HardeningDocumentBuilderFactory.newDefaultNSInstance();
+        assertTrue(factory.isNamespaceAware());
+        assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+    }
+
+    @Test
+    @Tag("sax")
+    void newNSInstanceSAXParserFactoryIsNamespaceAware() throws Exception {
+        final SAXParserFactory factory = 
HardeningSAXParserFactory.newNSInstance();
+        assertTrue(factory.isNamespaceAware());
+        factory.newSAXParser().parse(new InputSource(new 
StringReader(BENIGN_XML)), new DefaultHandler());
+        if (!AttackTestSupport.IS_ANDROID) {
+            
assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+        }
+    }
+
+    @Test
+    @Tag("sax")
+    void newDefaultNSInstanceSAXParserFactoryIsNamespaceAware() throws 
Exception {
+        if (AttackTestSupport.IS_ANDROID) {
+            assertThrows(FactoryConfigurationError.class, 
HardeningSAXParserFactory::newDefaultNSInstance);
+            return;
+        }
+        final SAXParserFactory factory = 
HardeningSAXParserFactory.newDefaultNSInstance();
+        assertTrue(factory.isNamespaceAware());
+        assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+    }
+
+    @Test
+    void explicitClassNameNSDocumentBuilderFactoryIsNamespaceAware() throws 
Exception {
+        final Class<?> impl = DocumentBuilderFactory.newInstance().getClass();
+        final DocumentBuilderFactory factory = 
HardeningDocumentBuilderFactory.newNSInstance(impl.getName(), 
impl.getClassLoader());
+        assertTrue(factory.isNamespaceAware());
+        assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+    }
+
+    @Test
+    void explicitClassNameNSSAXParserFactoryIsNamespaceAware() throws 
Exception {
+        final Class<?> impl = SAXParserFactory.newInstance().getClass();
+        final SAXParserFactory factory = 
HardeningSAXParserFactory.newNSInstance(impl.getName(), impl.getClassLoader());
+        assertTrue(factory.isNamespaceAware());
+        assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+    }
+
     @Test
     void newDefaultInstanceSchemaFactoryIsHardened() throws Exception {
         final SchemaFactory factory = 
HardeningSchemaFactory.newDefaultInstance();

Reply via email to