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 a80a109a Add max nesting depth (#131)
a80a109a is described below
commit a80a109af96a1d09267b75fe53be7dc0e532342b
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Aug 26 11:03:01 2026 +0100
Add max nesting depth (#131)
---
README.txt | 7 +
THREAT-MODEL.md | 58 ++++---
.../apache/ws/commons/schema/SchemaBuilder.java | 122 +++++++++++++-
.../ws/commons/schema/XmlSchemaCollection.java | 14 ++
.../src/test/java/tests/NestingDepthLimitTest.java | 185 +++++++++++++++++++++
5 files changed, 364 insertions(+), 22 deletions(-)
diff --git a/README.txt b/README.txt
index 218ce58b..f69ffb67 100644
--- a/README.txt
+++ b/README.txt
@@ -50,12 +50,19 @@ adjust the per-document limits:
Maximum number of schema documents resolved during a single top-level
read. The default is 1000.
+ org.apache.ws.commons.schema.maxNestingDepth
+ Maximum structural nesting depth while building the schema model,
+ including nested include/import/redefine document resolutions. The
+ default is 512.
+
For example, set a limit with:
-Dorg.apache.ws.commons.schema.walker.maxDecisionPoints=20000
-Dorg.apache.ws.commons.schema.maxImportDepth=128
+ -Dorg.apache.ws.commons.schema.maxNestingDepth=256
+
===================
Support
===================
diff --git a/THREAT-MODEL.md b/THREAT-MODEL.md
index 3a9be679..4fedfad5 100644
--- a/THREAT-MODEL.md
+++ b/THREAT-MODEL.md
@@ -29,7 +29,7 @@
at clone time. A report against project release *N* should be triaged
against the model as it stood at *N*, not at HEAD. Latest release
documented in `RELEASE-NOTE.txt`: 2.3.0.
-- **Date**: 2026-05-30.
+- **Date**: 2026-08-26.
- **Authors**: ASF Security team, awaiting XMLSchema / Webservices
PMC review.
- **Status**: under maintainer review.
@@ -47,7 +47,7 @@
*(inferred)* = synthesized by the producer from code structure or
domain knowledge, awaiting PMC ratification (every *(inferred)* tag has
a matching §14 question).
-- **Model confidence**: 21 documented / 0 maintainer / 24 inferred.
+- **Model confidence**: 22 documented / 0 maintainer / 24 inferred.
XMLSchema is a Java library that parses, models, walks, and serializes
W3C XML Schema documents (`.xsd` files). It is *not* a document
@@ -215,11 +215,12 @@ A finding is in-model only if it reaches a row marked
**yes**.
- **Memory**: schemas are held in memory; XMLSchema has no built-in
ceiling on schema-document size, imported bytes, or fetch rate. Import
and include resolution is bounded per read by a default maximum depth of
- 64 and maximum of 1000 resolved schema documents; both are configurable
- with JVM system properties. The walker has active-path cycle detection
- for the schema expansion graphs it traverses, but large acyclic schemas
- may still consume substantial memory and CPU *(documented:
- `README.txt`)*.
+ 64 and maximum of 1000 resolved schema documents, and schema-model
+ construction is bounded by a default structural nesting depth of 512;
+ these limits are configurable with JVM system properties. The walker has
+ active-path cycle detection for the schema expansion graphs it traverses,
+ but large acyclic schemas may still consume substantial memory and CPU
+ *(documented: `README.txt`)*.
- **System properties**: `org.apache.ws.commons.schema.extension_registry`
is consulted at `XmlSchemaCollection` construction time, and the
named class is loaded via `Class.forName()` *(documented:
@@ -262,6 +263,7 @@ points*:
| `org.apache.ws.commons.schema.walker.maxReplayedEvents` system property |
`1000000` *(documented: `XmlSchemaPathFinder.java`)* | operator-tunable
per-process limit | maximum previously traversed events replayed while
backtracking through one document |
| `org.apache.ws.commons.schema.maxImportDepth` system property | `64`
*(documented: `README.txt`)* | operator-tunable per-process limit | maximum
import/include resolution depth for one schema read |
| `org.apache.ws.commons.schema.maxSchemaResolutions` system property | `1000`
*(documented: `README.txt`)* | operator-tunable per-process limit | maximum
schema documents resolved during one top-level read |
+| `org.apache.ws.commons.schema.maxNestingDepth` system property | `512`
*(documented: `README.txt`)* | operator-tunable per-process limit | maximum
structural nesting depth while building the schema model, including nested
include/import/redefine document resolutions |
| `DocumentBuilderFactory` provider | JDK default (typically Xerces fork)
*(inferred — §14 Q6)* | depends on the JDK | shape of XML parsing for
`read(InputSource)` / `read(InputStream)` paths |
### The insecure-default case
@@ -313,6 +315,10 @@ feature is the intended defense and is sufficient) or
`MODEL-GAP`
default maximum of 1000 resolved schema documents per top-level read;
both limits are configurable with JVM system properties *(documented:
`README.txt`)*.
+- Schema-model construction has a default maximum structural nesting depth
+ of 512, including nested include/import/redefine document resolutions;
+ the limit is configurable with a JVM system property *(documented:
+ `README.txt`)*.
- Walker expansion cycles are rejected for type derivation, substitution
groups, model groups, and attribute groups. This prevents recursive
stack exhaustion for malformed but parseable schemas; it is not a general
@@ -406,6 +412,18 @@ feature is the intended defense and is sufficient) or
`MODEL-GAP`
- **Severity**: **medium** availability impact when the embedding
application accepts untrusted schemas *(inferred — §14 Q23)*.
+### P6 — Bounded schema-model structural descent
+
+- **Condition**: attacker-controlled schema bytes or DOM content are passed
+ to `XmlSchemaCollection.read(...)`.
+- **Property**: deeply nested schema structure, including depth split across
+ nested include/import/redefine document resolutions, terminates with
+ `XmlSchemaException` instead of exhausting the Java thread stack.
+- **Violation symptom**: a parseable schema causes `SchemaBuilder` to recurse
+ until `StackOverflowError` or another resource-exhaustion failure.
+- **Severity**: **medium** availability impact when the embedding
+ application accepts untrusted schemas.
+
## §9 Security properties the project does *not* provide
State each plainly so a triager can route an inbound report to the
@@ -432,10 +450,10 @@ matching disclaimer.
is moot — the caller's parser produced the DOM *(documented:
`XmlSchemaCollection.read(Document)` / `.read(Element)`)*.
- **No bound on schema-document size, imported bytes, or fetch rate.**
- Import/include depth and total resolved documents per top-level read are
- bounded, but a schema can still consume substantial resources within
- those limits *(inferred — §14 Q8; depth and resolution limits documented
- in `README.txt`)*.
+ Import/include depth, total resolved documents per top-level read, and
+ structural nesting depth are bounded, but a schema can still consume
+ substantial resources within those limits *(inferred — §14 Q8; resource
+ limits documented in `README.txt`)*.
- **No protection of imported schemas at rest.** Schemas pulled from
HTTP are fetched in cleartext if the URL is `http://`. The caller
must use TLS-protected URLs or install a restricting resolver
@@ -483,9 +501,10 @@ matching disclaimer.
- **SSRF via `xs:import schemaLocation`** — see §9 first bullet.
- **Billion-laughs / quadratic blowup** — partially mitigated by
`FEATURE_SECURE_PROCESSING=true`, but not universally.
-- **Schema-amplification DoS** — a deeply nested or heavily-recursive
- acyclic schema can exhaust memory or CPU; cyclic walker expansion is
- rejected as described in §8 P5.
+- **Schema-amplification DoS** — large or heavily-recursive acyclic schemas
+ can exhaust memory or CPU within the documented resource limits;
+ structural nesting and cyclic walker expansion are rejected as described
+ in §8 P5 and §8 P6.
- **Confused-deputy fetch via untrusted `baseUri` + relative
`schemaLocation`** — the operator-supplied base URI is trusted.
@@ -509,8 +528,8 @@ The embedding Java application **must**:
*(inferred — §14 Q6)*.
4. Bound maximum schema size, imported bytes, and fetch rate at the
*caller* level. XMLSchema provides configurable import/include depth
- and per-read resolution limits, but these do not replace deployment-
- specific resource budgets *(inferred — §14 Q8)*.
+ per-read resolution, and structural nesting limits, but these do not
+ replace deployment-specific resource budgets *(inferred — §14 Q8)*.
5. Set `org.apache.ws.commons.schema.extension_registry` only at JVM
startup from a trusted source; do not allow untrusted actors to set
it.
@@ -529,9 +548,9 @@ defense-in-depth controls:
`FEATURE_SECURE_PROCESSING=true`: `disallow-doctype-decl=true`,
`external-general-entities=false`, and
`external-parameter-entities=false`.
-3. Supplement XMLSchema's import/include depth and per-read resolution
- limits with caller-boundary budgets for total imported bytes and fetch
- rate per top-level parse.
+3. Supplement XMLSchema's import/include depth, per-read resolution, and
+ structural nesting limits with caller-boundary budgets for total imported
+ bytes and fetch rate per top-level parse.
4. Use connect/read timeouts for import fetches and fail closed on
timeout or policy-check errors.
5. Log import-resolution decisions (requested URI, normalized target,
@@ -817,6 +836,7 @@ source comments. The project website is
| `README.txt` | "lightweight schema object model that can be used to
manipulate and generate XML schema representations" | §1, §2 intended use |
| `RELEASE-NOTE.txt` (2.3.0) | Java 17 minimum, Java 7 dropped | §5
environment |
|
`xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaCollection.java`
line 361 | `org.apache.ws.commons.schema.extension_registry` system property
loaded via `Class.forName` | §5a, §6, §11 |
+|
`xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java`
| `org.apache.ws.commons.schema.maxNestingDepth` structural descent limit | §5,
§5a, §6, §8 P6 |
| `XmlSchemaCollection.java` line 713 |
`docFac.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, TRUE)` | §5a, §8 P2 |
| `XmlSchemaCollection.java` line 745 | `AccessController.doPrivileged`
wrapper for the SAX parse | §5 |
| `XmlSchema.java` line 886 |
`trFac.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, TRUE)` for serializer
| §5a, §8 P2 |
diff --git
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
index 92b366af..66418f23 100644
---
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
+++
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
@@ -69,6 +69,22 @@ public class SchemaBuilder {
private static final String[] RESERVED_ATTRIBUTES_LIST = {
"name", "type", "default", "fixed", "form", "id", "use", "ref"
};
+ /**
+ * Maximum nesting depth of the structural descent. The DOM-to-model walk
+ * recurses once per nesting level of the schema document, so a bound is
+ * required to keep a deeply nested document from overflowing the thread
+ * stack (neither FEATURE_SECURE_PROCESSING nor JDK defaults impose an
+ * element-depth limit on the XML parse itself). The counter lives on the
+ * XmlSchemaCollection, not on this builder: XmlSchemaCollection.read
+ * creates a fresh SchemaBuilder for every include/import/redefine
+ * document that resolveXmlSchema pulls in, but all those documents are
+ * built on one thread stack, so the depth must be carried across the
+ * hops rather than reset per document. Each nested document resolution
+ * counts one level toward the same bound (see resolveXmlSchema), which
+ * also bounds a chain of distinct single-hop documents.
+ */
+ private static final int MAX_NESTING_DEPTH =
+ getIntProperty("org.apache.ws.commons.schema.maxNestingDepth", 512);
XmlSchemaCollection collection;
Document currentDocument;
XmlSchema currentSchema;
@@ -115,6 +131,38 @@ public class SchemaBuilder {
}
}
+ private void enterNestedStructure() {
+ // Check before incrementing: on a breach the counter is left
+ // untouched, so the shared per-collection count stays balanced (every
+ // completed increment has a matching finally-decrement) even when the
+ // exception is caught and the collection is reused.
+ if (collection.builderNestingDepth >= MAX_NESTING_DEPTH) {
+ throw new XmlSchemaException("The schema is nested more than " +
MAX_NESTING_DEPTH
+ + " levels deep (nested
include/import/redefine document"
+ + " resolutions count toward the same
depth bound); refusing to"
+ + " build it. The limit may be
changed with the"
+ + "
org.apache.ws.commons.schema.maxNestingDepth"
+ + " system property.");
+ }
+ collection.builderNestingDepth++;
+ }
+
+ private void exitNestedStructure() {
+ collection.builderNestingDepth--;
+ }
+
+ private static int getIntProperty(String name, int defaultValue) {
+ try {
+ Integer value = Integer.getInteger(name);
+ if (value != null) {
+ return value.intValue();
+ }
+ } catch (RuntimeException e) {
+ // fall through to the default
+ }
+ return defaultValue;
+ }
+
/**
* Setup the cache to be used by the current thread of execution. Multiple
threads can use the cache, and
* each one must call this method at some point prior to attempting to
resolve the first schema, or the
@@ -275,6 +323,16 @@ public class SchemaBuilder {
*/
XmlSchemaComplexType handleComplexType(XmlSchema schema, Element
complexEl, Element schemaEl,
boolean topLevel) {
+ enterNestedStructure();
+ try {
+ return doHandleComplexType(schema, complexEl, schemaEl, topLevel);
+ } finally {
+ exitNestedStructure();
+ }
+ }
+
+ private XmlSchemaComplexType doHandleComplexType(XmlSchema schema, Element
complexEl, Element schemaEl,
+ boolean topLevel) {
XmlSchemaComplexType ct = new XmlSchemaComplexType(schema, topLevel);
@@ -381,6 +439,16 @@ public class SchemaBuilder {
* @param isGlobal
*/
XmlSchemaElement handleElement(XmlSchema schema, Element el, Element
schemaEl, boolean isGlobal) {
+ enterNestedStructure();
+ try {
+ return doHandleElement(schema, el, schemaEl, isGlobal);
+ } finally {
+ exitNestedStructure();
+ }
+ }
+
+ private XmlSchemaElement doHandleElement(XmlSchema schema, Element el,
Element schemaEl,
+ boolean isGlobal) {
XmlSchemaElement element = new XmlSchemaElement(schema, isGlobal);
@@ -587,6 +655,16 @@ public class SchemaBuilder {
*/
XmlSchemaSimpleType handleSimpleType(XmlSchema schema, Element simpleEl,
Element schemaEl,
boolean topLevel) {
+ enterNestedStructure();
+ try {
+ return doHandleSimpleType(schema, simpleEl, schemaEl, topLevel);
+ } finally {
+ exitNestedStructure();
+ }
+ }
+
+ private XmlSchemaSimpleType doHandleSimpleType(XmlSchema schema, Element
simpleEl, Element schemaEl,
+ boolean topLevel) {
XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType(schema,
topLevel);
if (simpleEl.hasAttribute("name")) {
simpleType.setName(simpleEl.getAttribute("name"));
@@ -702,9 +780,20 @@ public class SchemaBuilder {
if (collection.check(key)) {
collection.push(key);
try {
- XmlSchema readSchema = collection.read(source, validator);
- putCachedSchema(targetNamespace, schemaLocation, baseUri,
readSchema);
- return readSchema;
+ // The nested document is built by a fresh SchemaBuilder
+ // (collection.read creates one per document) but on this
+ // same thread stack, so the hop counts one level toward
+ // the shared per-collection nesting bound. Exact-key
+ // cycle detection (check/push) alone does not bound a
+ // chain of distinct schema locations.
+ enterNestedStructure();
+ try {
+ XmlSchema readSchema = collection.read(source,
validator);
+ putCachedSchema(targetNamespace, schemaLocation,
baseUri, readSchema);
+ return readSchema;
+ } finally {
+ exitNestedStructure();
+ }
} finally {
collection.pop();
}
@@ -854,6 +943,15 @@ public class SchemaBuilder {
}
private XmlSchemaAll handleAll(XmlSchema schema, Element allEl, Element
schemaEl) {
+ enterNestedStructure();
+ try {
+ return doHandleAll(schema, allEl, schemaEl);
+ } finally {
+ exitNestedStructure();
+ }
+ }
+
+ private XmlSchemaAll doHandleAll(XmlSchema schema, Element allEl, Element
schemaEl) {
XmlSchemaAll all = new XmlSchemaAll();
@@ -1100,6 +1198,15 @@ public class SchemaBuilder {
}
private XmlSchemaChoice handleChoice(XmlSchema schema, Element choiceEl,
Element schemaEl) {
+ enterNestedStructure();
+ try {
+ return doHandleChoice(schema, choiceEl, schemaEl);
+ } finally {
+ exitNestedStructure();
+ }
+ }
+
+ private XmlSchemaChoice doHandleChoice(XmlSchema schema, Element choiceEl,
Element schemaEl) {
XmlSchemaChoice choice = new XmlSchemaChoice();
if (choiceEl.hasAttribute("id")) {
@@ -1557,6 +1664,15 @@ public class SchemaBuilder {
}
private XmlSchemaSequence handleSequence(XmlSchema schema, Element
sequenceEl, Element schemaEl) {
+ enterNestedStructure();
+ try {
+ return doHandleSequence(schema, sequenceEl, schemaEl);
+ } finally {
+ exitNestedStructure();
+ }
+ }
+
+ private XmlSchemaSequence doHandleSequence(XmlSchema schema, Element
sequenceEl, Element schemaEl) {
XmlSchemaSequence sequence = new XmlSchemaSequence();
diff --git
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaCollection.java
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaCollection.java
index 0e5b32aa..dfe59fb3 100644
---
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaCollection.java
+++
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaCollection.java
@@ -198,6 +198,20 @@ public final class XmlSchemaCollection {
return !stack.contains(pKey);
}
+ /**
+ * Structural nesting depth of the schema build currently in progress on
+ * this collection. Maintained by SchemaBuilder (balanced increments and
+ * finally-decrements, so it always returns to zero when the outermost
+ * read completes or throws). It lives here rather than on SchemaBuilder
+ * because read() creates a fresh SchemaBuilder for every
+ * include/import/redefine document that SchemaBuilder.resolveXmlSchema
+ * pulls in, while all those documents are built on a single thread
+ * stack: the bound has to be carried across the nested reads, not reset
+ * per document. Package-private and accessed directly by SchemaBuilder,
+ * like {@link #baseUri}.
+ */
+ int builderNestingDepth;
+
public ExtensionRegistry getExtReg() {
return extReg;
}
diff --git a/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
b/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
new file mode 100644
index 00000000..d9323542
--- /dev/null
+++ b/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
@@ -0,0 +1,185 @@
+/**
+ * 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 tests;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.StringReader;
+import java.io.Writer;
+
+import org.apache.ws.commons.schema.XmlSchema;
+import org.apache.ws.commons.schema.XmlSchemaCollection;
+import org.apache.ws.commons.schema.XmlSchemaException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.xml.sax.InputSource;
+
+/**
+ * The structural descent of the schema build must be bounded: a deeply
+ * nested schema document must fail with the documented XmlSchemaException
+ * instead of exhausting the thread stack. The bound is shared across the
+ * documents of one read: every nested include/import/redefine resolution is
+ * built by a fresh SchemaBuilder but on the same thread stack, so the depth
+ * carries across resolveXmlSchema hops instead of resetting per document,
+ * and a chain of distinct single-hop documents is bounded too.
+ */
+public class NestingDepthLimitTest extends Assert {
+ private static final String MAX_ELEMENT_DEPTH = "jdk.xml.maxElementDepth";
+
+ private String savedMaxElementDepth;
+
+ /**
+ * Secure processing makes the JDK parser enforce its own element-depth
+ * limit (100 by default as of JDK 25), which would reject these
+ * documents before the schema build ever descends. Lift it so the bound
+ * under test is the library's own.
+ */
+ @Before
+ public void liftParserElementDepthLimit() {
+ savedMaxElementDepth = System.getProperty(MAX_ELEMENT_DEPTH);
+ System.setProperty(MAX_ELEMENT_DEPTH, "0");
+ }
+
+ @After
+ public void restoreParserElementDepthLimit() {
+ if (savedMaxElementDepth == null) {
+ System.clearProperty(MAX_ELEMENT_DEPTH);
+ } else {
+ System.setProperty(MAX_ELEMENT_DEPTH, savedMaxElementDepth);
+ }
+ }
+
+ @Test
+ public void testReasonablyNestedSchemaStillParses() throws Exception {
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ XmlSchema schema = collection.read(new
StringReader(buildNestedSchema(50)));
+ assertNotNull(schema);
+ }
+
+ @Test
+ public void testDeeplyNestedSchemaIsRejected() throws Exception {
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ try {
+ collection.read(new StringReader(buildNestedSchema(600)));
+ fail("A schema document nested 600 particle levels deep should be
rejected.");
+ } catch (XmlSchemaException expected) {
+ assertTrue(expected.getMessage().contains("nested"));
+ }
+ }
+
+ /**
+ * The counter must not reset across include hops: a chain of distinct
+ * documents, each individually trivial, must still hit the shared bound
+ * instead of adding stack frames per hop until the thread stack
+ * overflows. (Exact-key cycle detection never fires here because every
+ * schemaLocation is distinct.)
+ */
+ @Test
+ public void testIncludeChainOfDistinctDocumentsIsRejected() throws
Exception {
+ File root = createIncludeChain(newTempDir(), 600, 0);
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ try {
+ collection.read(new InputSource(root.toURI().toString()));
+ fail("An include chain of 600 distinct documents should be
rejected, not recursed.");
+ } catch (XmlSchemaException expected) {
+ assertTrue(expected.getMessage().contains("depth"));
+ }
+ }
+
+ @Test
+ public void testShallowIncludeChainWithNestedLeafStillParses() throws
Exception {
+ File root = createIncludeChain(newTempDir(), 3, 50);
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ XmlSchema schema = collection.read(new
InputSource(root.toURI().toString()));
+ assertNotNull(schema);
+ }
+
+ @Test
+ public void testCollectionIsReusableAfterRejection() throws Exception {
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ try {
+ collection.read(new StringReader(buildNestedSchema(600)));
+ fail("A schema document nested 600 particle levels deep should be
rejected.");
+ } catch (XmlSchemaException expected) {
+ // expected; the counter must have unwound to zero.
+ }
+
+ XmlSchema schema = collection.read(new
StringReader(buildNestedSchema(50, "urn:after-rejection")));
+ assertNotNull(schema);
+ }
+
+ private String buildNestedSchema(int depth) {
+ return buildNestedSchema(depth, "urn:nesting");
+ }
+
+ private String buildNestedSchema(int depth, String namespace) {
+ StringBuilder schema = new StringBuilder();
+ schema.append("<xs:schema
xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"")
+ .append(" targetNamespace=\"").append(namespace).append("\">");
+ schema.append("<xs:element name=\"root\">");
+ for (int i = 0; i < depth; i++) {
+ schema.append("<xs:complexType><xs:sequence><xs:element
name=\"e\">");
+ }
+ schema.append("<xs:complexType/>");
+ for (int i = 0; i < depth; i++) {
+ schema.append("</xs:element></xs:sequence></xs:complexType>");
+ }
+ schema.append("</xs:element></xs:schema>");
+ return schema.toString();
+ }
+
+ private File newTempDir() {
+ File dir = new File(System.getProperty("java.io.tmpdir"),
+ "xmlschema-nesting-" + System.nanoTime());
+ assertTrue(dir.mkdir());
+ return dir;
+ }
+
+ /**
+ * Writes an include chain of the given length; every document is
+ * structurally trivial, and the last one carries the given nested
+ * payload (or none).
+ */
+ private File createIncludeChain(File dir, int hops, int leafNestingDepth)
throws Exception {
+ for (int i = 0; i < hops; i++) {
+ File f = new File(dir, "hop" + i + ".xsd");
+ Writer out = new FileWriter(f);
+ try {
+ out.write("<xs:schema
xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
+ + " targetNamespace=\"urn:nesting\">");
+ if (i + 1 < hops) {
+ out.write("<xs:include schemaLocation=\"hop" + (i + 1) +
".xsd\"/>");
+ } else if (leafNestingDepth > 0) {
+ String leaf = buildNestedSchema(leafNestingDepth);
+ int start = leaf.indexOf("<xs:element");
+ out.write(leaf.substring(start,
leaf.lastIndexOf("</xs:schema>")));
+ }
+ out.write("</xs:schema>");
+ } finally {
+ out.close();
+ }
+ }
+ return new File(dir, "hop0.xsd");
+ }
+}