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

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

commit 5a19b706463174ec055c5605d469443426c14166
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Aug 26 10:05:29 2026 +0100

    Add max nesting depth
---
 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 | 158 +++++++++++++++++++++
 5 files changed, 337 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..407dafc9
--- /dev/null
+++ b/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
@@ -0,0 +1,158 @@
+/**
+ * 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.Assert;
+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 {
+    @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");
+    }
+}

Reply via email to