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 <[email protected]>
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>");
+    }
+}

Reply via email to