This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git
The following commit(s) were added to refs/heads/master by this push:
new 6a1b2ca8 Keep document-walk failures distinguishable (#154)
6a1b2ca8 is described below
commit 6a1b2ca8209d6e80cd327bf54ba8015d662ba77f
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Sep 17 09:27:40 2026 +0100
Keep document-walk failures distinguishable (#154)
---
.../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>");
+ }
+}