View Javadoc
1   /*
2    * Copyright (c) 2012-2024, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.xml;
31  
32  import java.io.ByteArrayInputStream;
33  import java.nio.charset.StandardCharsets;
34  import java.security.SecureRandom;
35  import java.util.Collection;
36  import java.util.Random;
37  import java.util.concurrent.Callable;
38  import java.util.concurrent.ExecutorService;
39  import java.util.concurrent.Executors;
40  import java.util.concurrent.TimeUnit;
41  import javax.xml.transform.dom.DOMSource;
42  import javax.xml.transform.stream.StreamSource;
43  import org.apache.commons.lang3.RandomStringUtils;
44  import org.apache.commons.lang3.StringUtils;
45  import org.hamcrest.MatcherAssert;
46  import org.hamcrest.Matchers;
47  import org.junit.jupiter.api.Test;
48  import org.xml.sax.SAXParseException;
49  
50  /**
51   * Test case for {@link XSDDocument}.
52   * @since 0.1
53   * @checkstyle AbbreviationAsWordInNameCheck (5 lines)
54   */
55  final class XSDDocumentTest {
56  
57      @Test
58      void validatesXml() {
59          final XSD xsd = new XSDDocument(
60              new ByteArrayInputStream(
61                  StringUtils.join(
62                      "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' >",
63                      "<xs:element name='test'/>",
64                      " </xs:schema>"
65                  ).getBytes()
66              )
67          );
68          MatcherAssert.assertThat(
69              xsd.validate(new DOMSource(new XMLDocument("<test/>").node())),
70              Matchers.empty()
71          );
72          MatcherAssert.assertThat(
73              xsd.validate(
74                  new DOMSource(new XMLDocument("<test></test>").node())
75              ),
76              Matchers.empty()
77          );
78      }
79  
80      @Test
81      void detectsSchemaViolations() {
82          final String xsd = StringUtils.join(
83              "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
84              "<xs:element name='first'/></xs:schema>"
85          );
86          final Collection<SAXParseException> errors =
87              new XSDDocument(xsd).validate(
88                  new StreamSource(
89                      new ByteArrayInputStream("<second/>".getBytes())
90                  )
91              );
92          MatcherAssert.assertThat(
93              errors,
94              Matchers.iterableWithSize(1)
95          );
96          MatcherAssert.assertThat(
97              errors.iterator().next().getLineNumber(),
98              Matchers.greaterThan(0)
99          );
100     }
101 
102     @Test
103     @SuppressWarnings({
104         "PMD.AvoidInstantiatingObjectsInLoops",
105         "PMD.InsufficientStringBufferDeclaration"
106         })
107     void validatesComplexXml() throws Exception {
108         final int loopp = 5;
109         final int size = 10_000;
110         final int loop = 100;
111         final int random = 10;
112         final String xsd = StringUtils.join(
113             "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'  >",
114             "<xs:element name='root'>",
115             "<xs:complexType><xs:sequence>",
116             "<xs:element name='a' type='xs:string' maxOccurs='unbounded' />",
117             "</xs:sequence></xs:complexType>",
118             "</xs:element></xs:schema>"
119         );
120         final StringBuilder text = new StringBuilder(size)
121             .append("<root>");
122         for (int idx = 0; idx < loop; ++idx) {
123             text.append("\n<a>\t&lt;&gt;&amp;&quot;&#09;&#x0A;")
124                 .append(RandomStringUtils.randomAlphanumeric(random))
125                 .append("</a>\n\r \t    ");
126         }
127         text.append("</root>");
128         for (int idx = 0; idx < loopp; ++idx) {
129             MatcherAssert.assertThat(
130                 new XSDDocument(xsd).validate(
131                     new StreamSource(
132                         new ByteArrayInputStream(
133                             text.toString().getBytes(StandardCharsets.UTF_8)
134                         )
135                     )
136                 ),
137                 Matchers.empty()
138             );
139         }
140     }
141 
142     @Test
143     void validatesLongXml() throws Exception {
144         final XSD xsd = new XSDDocument(
145             this.getClass().getResource("sample.xsd")
146         );
147         MatcherAssert.assertThat(
148             xsd.validate(
149                 new DOMSource(
150                     new XMLDocument(
151                         StringUtils.join(
152                             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
153                             "<payment><id>333</id>",
154                             "<date>1-Jan-2013</date>",
155                             "<debit>test-1</debit>",
156                             "<credit>test-2</credit>",
157                             "</payment>"
158                         )
159                     ).node()
160                 )
161             ),
162             Matchers.empty()
163         );
164     }
165 
166     @Test
167     void validatesMultipleXmlsInThreads() throws Exception {
168         final int random = 100;
169         final int loop = 10;
170         final int timeout = 30;
171         final Random rand = new SecureRandom();
172         final XSD xsd = new XSDDocument(
173             StringUtils.join(
174                 "<xs:schema xmlns:xs ='http://www.w3.org/2001/XMLSchema' >",
175                 "<xs:element name='r'><xs:complexType>",
176                 "<xs:sequence>",
177                 "<xs:element name='x' type='xs:integer'",
178                 " minOccurs='0' maxOccurs='unbounded'/>",
179                 "</xs:sequence></xs:complexType></xs:element>",
180                 "</xs:schema>"
181             )
182         );
183         // @checkstyle AnonInnerLengthCheck (50 lines)
184         final Callable<Void> callable = new Callable<Void>() {
185             @Override
186             public Void call() throws Exception {
187                 final int cnt = rand.nextInt(random);
188                 MatcherAssert.assertThat(
189                     xsd.validate(
190                         new DOMSource(
191                             new XMLDocument(
192                                 StringUtils.join(
193                                     "<r>",
194                                     StringUtils.repeat("<x>hey</x>", cnt),
195                                     "</r>"
196                                 )
197                             ).node()
198                         )
199                     ),
200                     Matchers.hasSize(cnt << 1)
201                 );
202                 return null;
203             }
204         };
205         final ExecutorService service = Executors.newFixedThreadPool(5);
206         for (int count = 0; count < loop; count += 1) {
207             service.submit(callable);
208         }
209         service.shutdown();
210         MatcherAssert.assertThat(
211             service.awaitTermination(timeout, TimeUnit.SECONDS),
212             Matchers.is(true)
213         );
214         service.shutdownNow();
215     }
216 
217 }