You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/09/17 08:24:33 UTC

(ws-xmlschema) branch coheigea/errors created (now 78691945)

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

coheigea pushed a change to branch coheigea/errors
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git


      at 78691945 Keep document-walk failures distinguishable

This branch includes the following new commits:

     new 78691945 Keep document-walk failures distinguishable

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(ws-xmlschema) 01/01: Keep document-walk failures distinguishable

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/errors
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git

commit 78691945844bbf32f4be2241c93bea8760d3b01c
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Sep 17 09:24:17 2026 +0100

    Keep document-walk failures distinguishable
---
 .../schema/docpath/XmlSchemaPathFinder.java        |  55 +++++---
 .../schema/docpath/TestPathFinderFailureTypes.java | 146 +++++++++++++++++++++
 2 files changed, 185 insertions(+), 16 deletions(-)

diff --git a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/XmlSchemaPathFinder.java b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/XmlSchemaPathFinder.java
index e92184e2..0fa67992 100644
--- a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/XmlSchemaPathFinder.java
+++ b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/XmlSchemaPathFinder.java
@@ -67,10 +67,17 @@ public final class XmlSchemaPathFinder<U, V> extends DefaultHandler {
      * cross-product of choices. Both bounds are configurable via system
      * properties.
      */
-    private static final int MAX_DECISION_POINTS =
-        getIntProperty("org.apache.ws.commons.schema.walker.maxDecisionPoints", 10000);
-    private static final long MAX_REPLAYED_EVENTS =
-        getIntProperty("org.apache.ws.commons.schema.walker.maxReplayedEvents", 1000000);
+    private static final String MAX_DECISION_POINTS_PROPERTY =
+        "org.apache.ws.commons.schema.walker.maxDecisionPoints";
+    private static final String MAX_REPLAYED_EVENTS_PROPERTY =
+        "org.apache.ws.commons.schema.walker.maxReplayedEvents";
+
+    // Read per instance: a static final is read once per class load, so setting either
+    // property had no effect on a JVM that had already touched this class.
+    private final int maxDecisionPoints =
+        getIntProperty(MAX_DECISION_POINTS_PROPERTY, 10000);
+    private final long maxReplayedEvents =
+        getIntProperty(MAX_REPLAYED_EVENTS_PROPERTY, 1000000);
 
     private final XmlSchemaNamespaceContext nsContext;
 
@@ -447,25 +454,41 @@ public final class XmlSchemaPathFinder<U, V> extends DefaultHandler {
         decisionPoints = null; // Hopefully there won't be any!
     }
 
+    /**
+     * Keeps the outcomes a caller can act on - an invalid document, an unusable facet, an
+     * exhausted budget - distinguishable from an internal error, which stays a
+     * {@link RuntimeException}. {@link ValidationException} is checked, so it is carried as a
+     * cause rather than rethrown.
+     */
+    private static RuntimeException reportable(String context, Exception e) {
+        if (e instanceof XmlSchemaException) {
+            return (XmlSchemaException)e;
+        }
+        if (e instanceof ValidationException) {
+            return new XmlSchemaException(context, e);
+        }
+        return new RuntimeException(context, e);
+    }
+
     private void recordDecisionPoint() {
         ++decisionPointCount;
-        if (decisionPointCount > MAX_DECISION_POINTS) {
-            throw new XmlSchemaException("More than " + MAX_DECISION_POINTS
+        if (decisionPointCount > maxDecisionPoints) {
+            throw new XmlSchemaException("More than " + maxDecisionPoints
                 + " decision points were created while matching this document; the schema"
                 + " likely contains ambiguous (Unique Particle Attribution violating)"
                 + " content models. The limit may be changed with the"
-                + " org.apache.ws.commons.schema.walker.maxDecisionPoints system property.");
+                + " " + MAX_DECISION_POINTS_PROPERTY + " system property.");
         }
     }
 
     private void recordReplayedEvent() {
         ++replayedEventCount;
-        if (replayedEventCount > MAX_REPLAYED_EVENTS) {
-            throw new XmlSchemaException("More than " + MAX_REPLAYED_EVENTS
+        if (replayedEventCount > maxReplayedEvents) {
+            throw new XmlSchemaException("More than " + maxReplayedEvents
                 + " traversed elements were replayed while backtracking through this"
                 + " document; the schema likely contains ambiguous (Unique Particle"
                 + " Attribution violating) content models. The limit may be changed with the"
-                + " org.apache.ws.commons.schema.walker.maxReplayedEvents system property.");
+                + " " + MAX_REPLAYED_EVENTS_PROPERTY + " system property.");
         }
     }
 
@@ -801,8 +824,8 @@ public final class XmlSchemaPathFinder<U, V> extends DefaultHandler {
              * internal exception is thrown instead. Likewise, any useful info
              * about the error reported in the wrapper SAXException is lost.
              */
-            throw new RuntimeException("Error occurred while starting element " + elemQName
-                                       + "; traversed path is " + getElementsTraversedAsString(), e);
+            throw reportable("Error occurred while starting element " + elemQName
+                             + "; traversed path is " + getElementsTraversedAsString(), e);
         }
     }
 
@@ -874,8 +897,8 @@ public final class XmlSchemaPathFinder<U, V> extends DefaultHandler {
                 .add(new TraversedElement(element.getQName(), TraversedElement.Traversal.CONTENT));
 
         } catch (Exception e) {
-            throw new RuntimeException("Error occurred while processing characters; traversed path was "
-                                       + getElementsTraversedAsString(), e);
+            throw reportable("Error occurred while processing characters; traversed path was "
+                             + getElementsTraversedAsString(), e);
         }
     }
 
@@ -944,8 +967,8 @@ public final class XmlSchemaPathFinder<U, V> extends DefaultHandler {
             }
 
         } catch (Exception e) {
-            throw new RuntimeException("Error occurred while ending element " + elemQName
-                                       + "; traversed path was " + getElementsTraversedAsString(), e);
+            throw reportable("Error occurred while ending element " + elemQName
+                             + "; traversed path was " + getElementsTraversedAsString(), e);
         }
     }
 
diff --git a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestPathFinderFailureTypes.java b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestPathFinderFailureTypes.java
new file mode 100644
index 00000000..7aaec5c0
--- /dev/null
+++ b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestPathFinderFailureTypes.java
@@ -0,0 +1,146 @@
+/**
+ * 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.ws.commons.schema.docpath;
+
+import java.io.StringReader;
+
+import javax.xml.bind.ValidationException;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.ws.commons.schema.XmlSchemaCollection;
+import org.apache.ws.commons.schema.XmlSchemaException;
+import org.apache.ws.commons.schema.walker.XmlSchemaWalker;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.InputSource;
+
+/**
+ * A failure during the document walk must stay distinguishable from an internal error, which
+ * reporting everything as a bare RuntimeException did not allow.
+ */
+public class TestPathFinderFailureTypes extends Assert {
+
+    private static final String MAX_DECISION_POINTS_PROPERTY =
+        "org.apache.ws.commons.schema.walker.maxDecisionPoints";
+
+    private DocumentBuilder docBuilder;
+
+    @Before
+    public void setUp() throws Exception {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        docBuilder = factory.newDocumentBuilder();
+    }
+
+    private void walk(String schemaBody, String xml) throws Exception {
+        XmlSchemaCollection collection = new XmlSchemaCollection();
+        collection.read(new StringReader(
+            "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:t=\"urn:t\""
+            + " targetNamespace=\"urn:t\" elementFormDefault=\"qualified\">"
+            + schemaBody + "</xs:schema>"));
+
+        XmlSchemaStateMachineGenerator generator = new XmlSchemaStateMachineGenerator();
+        new XmlSchemaWalker(collection, generator)
+            .walk(collection.getElementByQName(new QName("urn:t", "root")));
+
+        XmlSchemaPathFinder<Object, Object> pathFinder =
+            new XmlSchemaPathFinder<Object, Object>(generator.getStartNode());
+        new SaxWalkerOverDom(pathFinder)
+            .walk(docBuilder.parse(new InputSource(new StringReader(xml))));
+    }
+
+    private static Throwable rootCause(Throwable t) {
+        Throwable cause = t;
+        while (cause.getCause() != null && cause.getCause() != cause) {
+            cause = cause.getCause();
+        }
+        return cause;
+    }
+
+    @Test
+    public void testFacetViolationIsReportedAsXmlSchemaException() throws Exception {
+        try {
+            walk("<xs:simpleType name=\"s\"><xs:restriction base=\"xs:string\">"
+                 + "<xs:maxLength value=\"2\"/></xs:restriction></xs:simpleType>"
+                 + "<xs:element name=\"root\" type=\"t:s\"/>",
+                 "<root xmlns=\"urn:t\">toolong</root>");
+            fail("expected the facet violation to be reported");
+        } catch (XmlSchemaException expected) {
+            assertTrue("the ValidationException must be kept as the cause",
+                       expected.getCause() instanceof ValidationException);
+        }
+    }
+
+    @Test
+    public void testMalformedFacetIsReportedAsXmlSchemaException() throws Exception {
+        try {
+            walk("<xs:simpleType name=\"s\"><xs:restriction base=\"xs:string\">"
+                 + "<xs:maxLength value=\"bogus\"/></xs:restriction></xs:simpleType>"
+                 + "<xs:element name=\"root\" type=\"t:s\"/>",
+                 "<root xmlns=\"urn:t\">hello</root>");
+            fail("expected the malformed facet to be reported");
+        } catch (XmlSchemaException expected) {
+            assertTrue("the NumberFormatException must survive in the cause chain",
+                       rootCause(expected) instanceof NumberFormatException);
+        }
+    }
+
+    @Test
+    public void testDecisionPointBudgetSurvivesUnwrapped() throws Exception {
+        System.setProperty(MAX_DECISION_POINTS_PROPERTY, "1");
+        try {
+            StringBuilder schema = new StringBuilder(
+                "<xs:element name=\"root\"><xs:complexType><xs:sequence>");
+            StringBuilder xml = new StringBuilder("<root xmlns=\"urn:t\">");
+            for (int i = 0; i < 12; i++) {
+                schema.append("<xs:choice minOccurs=\"0\">")
+                      .append("<xs:element name=\"a\" type=\"xs:string\"/>")
+                      .append("<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/>")
+                      .append("<xs:element name=\"b\" type=\"xs:string\" minOccurs=\"0\"/>")
+                      .append("</xs:sequence></xs:choice>");
+                xml.append("<a>x</a>");
+            }
+            schema.append("<xs:element name=\"end\" type=\"xs:string\"/>")
+                  .append("</xs:sequence></xs:complexType></xs:element>");
+            xml.append("</root>");
+
+            walk(schema.toString(), xml.toString());
+            fail("expected the decision point budget to be reported");
+        } catch (XmlSchemaException expected) {
+            assertTrue(expected.getMessage(),
+                       expected.getMessage().contains("decision points"));
+            assertNull("the budget exception must not be wrapped", expected.getCause());
+        } finally {
+            System.clearProperty(MAX_DECISION_POINTS_PROPERTY);
+        }
+    }
+
+    @Test
+    public void testValidDocumentStillWalks() throws Exception {
+        walk("<xs:simpleType name=\"s\"><xs:restriction base=\"xs:string\">"
+             + "<xs:maxLength value=\"16\"/></xs:restriction></xs:simpleType>"
+             + "<xs:element name=\"root\" type=\"t:s\"/>",
+             "<root xmlns=\"urn:t\">short</root>");
+    }
+}