Skip to content

Commit df18b8f

Browse files
committedDec 24, 2024··
bug(#273): Make Jcabi Greate Again?
1 parent b37b7b1 commit df18b8f

File tree

8 files changed

+222
-316
lines changed

8 files changed

+222
-316
lines changed
 

‎pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ OF THE POSSIBILITY OF SUCH DAMAGE.
9292
<version>33.4.0-jre</version>
9393
<scope>test</scope>
9494
</dependency>
95+
<dependency>
96+
<groupId>com.yegor256</groupId>
97+
<artifactId>together</artifactId>
98+
<version>0.0.5</version>
99+
<scope>test</scope>
100+
</dependency>
95101
<dependency>
96102
<groupId>net.sf.saxon</groupId>
97103
<artifactId>Saxon-HE</artifactId>

‎src/main/java/com/jcabi/xml/ClasspathResolver.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,19 @@ final class ClasspathResolver implements LSResourceResolver {
4242

4343
@Override
4444
@SuppressWarnings("PMD.UseObjectForClearerAPI")
45-
// @checkstyle ParameterNumber (1 line)
46-
public LSInput resolveResource(final String type, final String nspace,
47-
final String pid, final String sid, final String base) {
45+
// @checkstyle ParameterNumber (10 lines)
46+
public LSInput resolveResource(
47+
final String type,
48+
final String nspace,
49+
final String pid,
50+
final String sid,
51+
final String base
52+
) {
4853
LSInput input = null;
49-
final ClassLoader loader =
50-
Thread.currentThread().getContextClassLoader();
54+
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
5155
if (sid != null && loader.getResource(sid) != null) {
5256
input = new ClasspathInput(pid, sid);
5357
}
5458
return input;
5559
}
56-
5760
}

‎src/main/java/com/jcabi/xml/SaxonDocument.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import net.sf.saxon.s9api.XdmItem;
5151
import net.sf.saxon.s9api.XdmNode;
5252
import org.w3c.dom.Node;
53+
import org.w3c.dom.ls.LSResourceResolver;
5354
import org.xml.sax.SAXParseException;
5455

5556
/**
@@ -228,7 +229,7 @@ public Node deepCopy() {
228229
}
229230

230231
@Override
231-
public Collection<SAXParseException> validate() {
232+
public Collection<SAXParseException> validate(final LSResourceResolver resolver) {
232233
throw new UnsupportedOperationException(
233234
String.format(SaxonDocument.UNSUPPORTED, "validate")
234235
);

‎src/main/java/com/jcabi/xml/StrictXML.java

+4-91
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,17 @@
3030
package com.jcabi.xml;
3131

3232
import com.jcabi.log.Logger;
33-
import java.io.IOException;
34-
import java.net.SocketException;
3533
import java.util.ArrayList;
3634
import java.util.Collection;
3735
import java.util.Iterator;
3836
import java.util.List;
39-
import java.util.concurrent.CopyOnWriteArrayList;
40-
import javax.xml.XMLConstants;
4137
import javax.xml.namespace.NamespaceContext;
42-
import javax.xml.transform.dom.DOMSource;
43-
import javax.xml.validation.SchemaFactory;
44-
import javax.xml.validation.Validator;
4538
import lombok.EqualsAndHashCode;
4639
import org.cactoos.Scalar;
4740
import org.cactoos.scalar.Sticky;
4841
import org.cactoos.scalar.Unchecked;
4942
import org.w3c.dom.Node;
5043
import org.w3c.dom.ls.LSResourceResolver;
51-
import org.xml.sax.SAXException;
5244
import org.xml.sax.SAXParseException;
5345

5446
/**
@@ -84,16 +76,7 @@ public StrictXML(final XML xml) {
8476
* @since 0.19
8577
*/
8678
public StrictXML(final XML xml, final LSResourceResolver resolver) {
87-
this(xml, StrictXML.newValidator(resolver));
88-
}
89-
90-
/**
91-
* Public ctor.
92-
* @param xml XML document
93-
* @param val Custom validator
94-
*/
95-
public StrictXML(final XML xml, final Validator val) {
96-
this(xml, () -> StrictXML.validate(xml, val));
79+
this(xml, () -> xml.validate(resolver));
9780
}
9881

9982
/**
@@ -102,7 +85,7 @@ public StrictXML(final XML xml, final Validator val) {
10285
* @param schema XSD schema
10386
*/
10487
public StrictXML(final XML xml, final XML schema) {
105-
this(xml, () -> StrictXML.check(xml, schema));
88+
this(xml, () -> xml.validate(schema));
10689
}
10790

10891
/**
@@ -193,25 +176,15 @@ public Node deepCopy() {
193176
}
194177

195178
@Override
196-
public Collection<SAXParseException> validate() {
197-
return this.origin.value().validate();
179+
public Collection<SAXParseException> validate(final LSResourceResolver resolver) {
180+
return this.origin.value().validate(resolver);
198181
}
199182

200183
@Override
201184
public Collection<SAXParseException> validate(final XML xsd) {
202185
return this.origin.value().validate(xsd);
203186
}
204187

205-
/**
206-
* Check and return list of errors.
207-
* @param xml The XML to check
208-
* @param xsd Schema to use
209-
* @return List of errors
210-
*/
211-
private static Collection<SAXParseException> check(final XML xml, final XML xsd) {
212-
return xml.validate(xsd);
213-
}
214-
215188
/**
216189
* Convert errors to lines.
217190
* @param errors The errors
@@ -273,64 +246,4 @@ private static String join(final Iterable<?> iterable, final String sep) {
273246
}
274247
return buf.toString();
275248
}
276-
277-
/**
278-
* Validate XML without external schema.
279-
* @param xml XML Document
280-
* @param validator XML Validator
281-
* @return List of validation errors
282-
*/
283-
private static Collection<SAXParseException> validate(
284-
final XML xml,
285-
final Validator validator
286-
) {
287-
final Collection<SAXParseException> errors =
288-
new CopyOnWriteArrayList<>();
289-
final int max = 3;
290-
try {
291-
validator.setErrorHandler(
292-
new XMLDocument.ValidationHandler(errors)
293-
);
294-
final DOMSource dom = new DOMSource(xml.inner());
295-
for (int retry = 1; retry <= max; ++retry) {
296-
try {
297-
validator.validate(dom);
298-
break;
299-
} catch (final SocketException ex) {
300-
Logger.error(
301-
StrictXML.class,
302-
"Try #%d of %d failed: %s: %s",
303-
retry,
304-
max,
305-
ex.getClass().getName(),
306-
ex.getMessage()
307-
);
308-
if (retry == max) {
309-
throw new IllegalStateException(ex);
310-
}
311-
}
312-
}
313-
} catch (final SAXException | IOException ex) {
314-
throw new IllegalStateException(ex);
315-
}
316-
return errors;
317-
}
318-
319-
/**
320-
* Creates a new validator.
321-
* @param resolver The resolver for resources
322-
* @return A new validator
323-
*/
324-
private static Validator newValidator(final LSResourceResolver resolver) {
325-
try {
326-
final Validator validator = SchemaFactory
327-
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
328-
.newSchema()
329-
.newValidator();
330-
validator.setResourceResolver(resolver);
331-
return validator;
332-
} catch (final SAXException ex) {
333-
throw new IllegalStateException(ex);
334-
}
335-
}
336249
}

‎src/main/java/com/jcabi/xml/XML.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.List;
3434
import javax.xml.namespace.NamespaceContext;
3535
import org.w3c.dom.Node;
36+
import org.w3c.dom.ls.LSResourceResolver;
3637
import org.xml.sax.SAXParseException;
3738

3839
/**
@@ -185,10 +186,11 @@ public interface XML {
185186

186187
/**
187188
* Validate this XML against the XSD schema inside it.
189+
* @param resolver XSD schema resolver
188190
* @return List of errors found
189191
* @since 0.31.0
190192
*/
191-
Collection<SAXParseException> validate();
193+
Collection<SAXParseException> validate(LSResourceResolver resolver);
192194

193195
/**
194196
* Validate this XML against the provided XSD schema.
@@ -197,5 +199,4 @@ public interface XML {
197199
* @since 0.31.0
198200
*/
199201
Collection<SAXParseException> validate(XML xsd);
200-
201202
}

‎src/main/java/com/jcabi/xml/XMLDocument.java

+38-30
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
import javax.xml.transform.dom.DOMSource;
6262
import javax.xml.transform.stream.StreamResult;
6363
import javax.xml.transform.stream.StreamSource;
64-
import javax.xml.validation.Schema;
6564
import javax.xml.validation.SchemaFactory;
6665
import javax.xml.validation.Validator;
6766
import javax.xml.xpath.XPath;
@@ -72,6 +71,7 @@
7271
import org.w3c.dom.Document;
7372
import org.w3c.dom.Node;
7473
import org.w3c.dom.NodeList;
74+
import org.w3c.dom.ls.LSResourceResolver;
7575
import org.xml.sax.ErrorHandler;
7676
import org.xml.sax.SAXException;
7777
import org.xml.sax.SAXParseException;
@@ -425,45 +425,51 @@ public XML merge(final NamespaceContext ctx) {
425425

426426
@Override
427427
public Collection<SAXParseException> validate(final XML xsd) {
428-
final Schema schema;
429-
try {
430-
schema = SchemaFactory
431-
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
432-
.newSchema(new StreamSource(new StringReader(xsd.toString())));
433-
} catch (final SAXException ex) {
434-
throw new IllegalStateException(
435-
String.format("Failed to create XSD schema from %s", xsd),
436-
ex
437-
);
428+
synchronized (xsd) {
429+
final Validator validator;
430+
try {
431+
validator = SchemaFactory
432+
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
433+
.newSchema(new StreamSource(new StringReader(xsd.toString())))
434+
.newValidator();
435+
} catch (final SAXException ex) {
436+
throw new IllegalStateException(
437+
String.format("Failed to create XSD schema from %s", xsd),
438+
ex
439+
);
440+
}
441+
return this.validate(validator);
438442
}
439-
return this.validate(schema);
440443
}
441444

442445
@Override
443-
public Collection<SAXParseException> validate() {
444-
final Schema schema;
445-
try {
446-
schema = SchemaFactory
447-
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
448-
.newSchema();
449-
} catch (final SAXException ex) {
450-
throw new IllegalStateException(
451-
"Failed to create XSD schema",
452-
ex
453-
);
446+
public Collection<SAXParseException> validate(final LSResourceResolver resolver) {
447+
synchronized (resolver) {
448+
final Validator validator;
449+
try {
450+
validator = SchemaFactory
451+
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
452+
.newSchema()
453+
.newValidator();
454+
validator.setResourceResolver(resolver);
455+
} catch (final SAXException ex) {
456+
throw new IllegalStateException(
457+
"Failed to create XSD schema",
458+
ex
459+
);
460+
}
461+
return this.validate(validator);
454462
}
455-
return this.validate(schema);
456463
}
457464

458465
/**
459-
* Validate against schema.
460-
* @param schema The XSD schema
466+
* Validate through validator.
467+
* @param validator Validator
461468
* @return List of errors
462469
*/
463-
public Collection<SAXParseException> validate(final Schema schema) {
470+
private Collection<SAXParseException> validate(final Validator validator) {
464471
final Collection<SAXParseException> errors =
465472
new CopyOnWriteArrayList<>();
466-
final Validator validator = schema.newValidator();
467473
validator.setErrorHandler(new XMLDocument.ValidationHandler(errors));
468474
try {
469475
validator.validate(new DOMSource(this.cache));
@@ -473,7 +479,7 @@ public Collection<SAXParseException> validate(final Schema schema) {
473479
if (Logger.isDebugEnabled(this)) {
474480
Logger.debug(
475481
this, "%s detected %d error(s)",
476-
schema.getClass().getName(), errors.size()
482+
validator.getClass().getName(), errors.size()
477483
);
478484
}
479485
return errors;
@@ -536,7 +542,9 @@ private <T> T fetch(final String query, final Class<T> type) throws XPathExpress
536542
)
537543
);
538544
}
539-
return (T) xpath.evaluate(query, this.cache, qname);
545+
synchronized (this.cache) {
546+
return (T) xpath.evaluate(query, this.cache, qname);
547+
}
540548
}
541549

542550
/**

‎src/test/java/com/jcabi/xml/StrictXMLTest.java

+80-128
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,20 @@
2929
*/
3030
package com.jcabi.xml;
3131

32-
import com.google.common.collect.Iterables;
3332
import com.yegor256.OnlineMeans;
33+
import com.yegor256.Together;
3434
import com.yegor256.WeAreOnline;
3535
import java.io.IOException;
3636
import java.net.InetAddress;
37-
import java.net.SocketException;
38-
import java.security.SecureRandom;
39-
import java.util.Collections;
40-
import java.util.Random;
41-
import java.util.concurrent.Callable;
42-
import java.util.concurrent.CountDownLatch;
43-
import java.util.concurrent.ExecutorService;
44-
import java.util.concurrent.Executors;
45-
import java.util.concurrent.TimeUnit;
46-
import java.util.concurrent.atomic.AtomicInteger;
47-
import javax.xml.transform.Source;
48-
import javax.xml.validation.Validator;
4937
import org.apache.commons.lang3.StringUtils;
5038
import org.hamcrest.MatcherAssert;
5139
import org.hamcrest.Matchers;
5240
import org.junit.jupiter.api.Assertions;
5341
import org.junit.jupiter.api.Assumptions;
5442
import org.junit.jupiter.api.BeforeEach;
55-
import org.junit.jupiter.api.Disabled;
43+
import org.junit.jupiter.api.RepeatedTest;
5644
import org.junit.jupiter.api.Test;
5745
import org.junit.jupiter.api.extension.ExtendWith;
58-
import org.mockito.ArgumentMatchers;
59-
import org.mockito.Mockito;
60-
import org.mockito.stubbing.Answer;
6146

6247
/**
6348
* Test case for {@link StrictXML}.
@@ -68,7 +53,6 @@
6853
*/
6954
@SuppressWarnings({ "PMD.TooManyMethods", "PMD.AvoidDuplicateLiterals"})
7055
final class StrictXMLTest {
71-
7256
@BeforeEach
7357
void weAreOnline() throws IOException {
7458
Assumptions.assumeTrue(
@@ -78,15 +62,18 @@ void weAreOnline() throws IOException {
7862

7963
@Test
8064
void passesValidXmlThrough() {
81-
new StrictXML(
82-
new XMLDocument("<root>passesValidXmlThrough</root>"),
83-
new XMLDocument(
84-
StringUtils.join(
85-
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
86-
"<xs:element name='root' type='xs:string'/>",
87-
"</xs:schema>"
65+
Assertions.assertDoesNotThrow(
66+
new StrictXML(
67+
new XMLDocument("<root>RootXML</root>"),
68+
new XMLDocument(
69+
StringUtils.join(
70+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
71+
"<xs:element name='root' type='xs:string'/>",
72+
"</xs:schema>"
73+
)
8874
)
89-
)
75+
)::inner,
76+
"XML should be validated without errors"
9077
);
9178
}
9279

@@ -95,11 +82,12 @@ void rejectsInvalidXmlThrough() {
9582
Assertions.assertThrows(
9683
IllegalArgumentException.class,
9784
new StrictXML(
98-
new XMLDocument("<root>not an integer</root>"),
85+
new XMLDocument("<root>string</root>"),
9986
new XMLDocument(
10087
StringUtils.join(
101-
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' >",
102-
"<xs:element name='root' type='xs:integer'/></xs:schema>"
88+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
89+
"<xs:element name='root' type='xs:integer'/>",
90+
"</xs:schema>"
10391
)
10492
)
10593
)::inner,
@@ -111,10 +99,13 @@ void rejectsInvalidXmlThrough() {
11199
@ExtendWith(WeAreOnline.class)
112100
@OnlineMeans(url = "http://maven.apache.org")
113101
void passesValidXmlUsingXsiSchemaLocation() throws Exception {
114-
new StrictXML(
115-
new XMLDocument(
116-
this.getClass().getResource("xsi-schemalocation-valid.xml")
117-
)
102+
Assertions.assertDoesNotThrow(
103+
new StrictXML(
104+
new XMLDocument(
105+
this.getClass().getResource("xsi-schemalocation-valid.xml")
106+
)
107+
)::inner,
108+
"XML with path to schema should be validated without errors"
118109
);
119110
}
120111

@@ -142,121 +133,82 @@ void printsInnerXmlToString() {
142133
new XMLDocument(xml),
143134
new XMLDocument(
144135
StringUtils.join(
145-
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' >",
146-
"<xs:element name='root' type='xs:string'/></xs:schema>"
136+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
137+
"<xs:element name='root' type='xs:string'/>",
138+
"</xs:schema>"
147139
)
148140
)
149141
).toString(),
150142
Matchers.containsString(xml)
151143
);
152144
}
153145

154-
@Test
155-
@Disabled
156-
void validatesMultipleXmlsInThreads() throws Exception {
157-
final XML xsd = new XMLDocument(
158-
StringUtils.join(
159-
"<xs:schema xmlns:xs ='http://www.w3.org/2001/XMLSchema' >",
160-
"<xs:element name='r'><xs:complexType><xs:sequence>",
161-
"<xs:element name='x' maxOccurs='unbounded'><xs:simpleType>",
162-
"<xs:restriction base='xs:integer'>",
163-
"<xs:maxInclusive value='100'/></xs:restriction>",
164-
"</xs:simpleType></xs:element>",
165-
"</xs:sequence></xs:complexType></xs:element></xs:schema>"
146+
@RepeatedTest(60)
147+
void doesNotFailOnFetchingInMultipleThreadsFromTheSameDocument() {
148+
final XML xml = new StrictXML(
149+
new XMLDocument("<root>RootXML</root>"),
150+
new XMLDocument(
151+
StringUtils.join(
152+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
153+
"<xs:element name='root' type='xs:string'/>",
154+
"</xs:schema>"
155+
)
166156
)
167157
);
168-
final Random rnd = new SecureRandom();
169-
final XML xml = new XMLDocument(
170-
StringUtils.join(
171-
Iterables.concat(
172-
Collections.singleton("<r>"),
173-
Iterables.transform(
174-
Collections.nCopies(10, 0),
175-
pos -> String.format(
176-
"<x>%d</x>", rnd.nextInt(100)
177-
)
178-
),
179-
Collections.singleton("<x>101</x></r>")
180-
),
181-
" "
182-
)
158+
Assertions.assertDoesNotThrow(
159+
new Together<>(
160+
thread -> xml.nodes("/root")
161+
)::asList,
162+
"StrictXML must not fail on fetching in multiple threads from the same document"
183163
);
184-
final AtomicInteger done = new AtomicInteger();
185-
final int threads = Runtime.getRuntime().availableProcessors() * 10;
186-
final CountDownLatch latch = new CountDownLatch(threads);
187-
final Callable<Void> callable = () -> {
188-
try {
189-
new StrictXML(xml, xsd);
190-
} catch (final IllegalArgumentException ex) {
191-
done.incrementAndGet();
192-
} finally {
193-
latch.countDown();
194-
}
195-
return null;
196-
};
197-
final ExecutorService service = Executors.newFixedThreadPool(5);
198-
try {
199-
for (int count = 0; count < threads; count += 1) {
200-
service.submit(callable);
201-
}
202-
latch.await(1L, TimeUnit.SECONDS);
203-
MatcherAssert.assertThat(done.get(), Matchers.equalTo(threads));
204-
} finally {
205-
service.shutdown();
206-
MatcherAssert.assertThat(
207-
service.awaitTermination(10L, TimeUnit.SECONDS),
208-
Matchers.is(true)
209-
);
210-
service.shutdownNow();
211-
}
212164
}
213165

214-
@Test
215-
@Disabled
216-
void passesValidXmlWithNetworkProblems() throws Exception {
217-
final Validator validator = Mockito.mock(Validator.class);
218-
final AtomicInteger counter = new AtomicInteger(0);
219-
// @checkstyle IllegalThrowsCheck (5 lines)
220-
Mockito.doAnswer(
221-
(Answer<Void>) invocation -> {
222-
final int attempt = counter.incrementAndGet();
223-
if (attempt == 1 || attempt == 2) {
224-
throw new SocketException(
225-
String.format("Attempt #%s failed", attempt)
166+
@RepeatedTest(60)
167+
void doesNotFailOnFetchingInMultipleThreadsFromDifferentDocuments() {
168+
Assertions.assertDoesNotThrow(
169+
new Together<>(
170+
thread -> {
171+
final XML xml = new StrictXML(
172+
new XMLDocument("<root>RootXML</root>"),
173+
new XMLDocument(
174+
StringUtils.join(
175+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
176+
"<xs:element name='root' type='xs:string'/>",
177+
"</xs:schema>"
178+
)
179+
)
226180
);
181+
return xml.nodes("/root");
227182
}
228-
return null;
229-
}
230-
).when(validator).validate(ArgumentMatchers.any(Source.class));
231-
new StrictXML(
232-
new XMLDocument(
233-
"<root>passesValidXmlWithNetworkProblems</root>"
234-
),
235-
validator
183+
)::asList,
184+
"StrictXML must not fail on fetching in multiple threads from different document"
236185
);
237186
}
238187

239188
@Test
240189
void lookupXsdsFromClasspath() {
241-
new StrictXML(
242-
new XMLDocument(
243-
StringUtils.join(
244-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
245-
"<payment xmlns=\"http://jcabi.com/schema/xml\" ",
246-
"xmlns:xsi=\"",
247-
"http://www.w3.org/2001/XMLSchema-instance",
248-
"\" ",
249-
"xsi:schemaLocation=\"",
250-
"http://jcabi.com/schema/xml ",
251-
"com/jcabi/xml/sample-namespaces.xsd",
252-
"\">",
253-
"<id>333</id>",
254-
"<date>1-Jan-2013</date>",
255-
"<debit>test-1</debit>",
256-
"<credit>test-2</credit>",
257-
"</payment>"
190+
Assertions.assertDoesNotThrow(
191+
new StrictXML(
192+
new XMLDocument(
193+
StringUtils.join(
194+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
195+
"<payment xmlns=\"http://jcabi.com/schema/xml\" ",
196+
"xmlns:xsi=\"",
197+
"http://www.w3.org/2001/XMLSchema-instance",
198+
"\" ",
199+
"xsi:schemaLocation=\"",
200+
"http://jcabi.com/schema/xml ",
201+
"com/jcabi/xml/sample-namespaces.xsd",
202+
"\">",
203+
"<id>333</id>",
204+
"<date>1-Jan-2013</date>",
205+
"<debit>test-1</debit>",
206+
"<credit>test-2</credit>",
207+
"</payment>"
208+
)
258209
)
259-
)
210+
)::inner,
211+
"StrictXML should not have failed on validation via classpath"
260212
);
261213
}
262214

‎src/test/java/com/jcabi/xml/XMLDocumentTest.java

+80-58
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
*/
3030
package com.jcabi.xml;
3131

32-
import com.google.common.collect.Iterables;
3332
import com.jcabi.matchers.XhtmlMatchers;
33+
import com.yegor256.Together;
3434
import java.io.ByteArrayInputStream;
3535
import java.io.File;
3636
import java.io.IOException;
@@ -44,7 +44,6 @@
4444
import java.util.List;
4545
import java.util.Random;
4646
import java.util.concurrent.Callable;
47-
import java.util.concurrent.CountDownLatch;
4847
import java.util.concurrent.ExecutorService;
4948
import java.util.concurrent.Executors;
5049
import java.util.concurrent.TimeUnit;
@@ -61,6 +60,7 @@
6160
import org.hamcrest.MatcherAssert;
6261
import org.hamcrest.Matchers;
6362
import org.hamcrest.core.IsEqual;
63+
import org.junit.jupiter.api.Assertions;
6464
import org.junit.jupiter.api.Disabled;
6565
import org.junit.jupiter.api.RepeatedTest;
6666
import org.junit.jupiter.api.Test;
@@ -78,6 +78,16 @@
7878
*/
7979
@SuppressWarnings({"PMD.TooManyMethods", "PMD.DoNotUseThreads"})
8080
final class XMLDocumentTest {
81+
/**
82+
* Root XSD.
83+
*/
84+
private static final XML XSD = new XMLDocument(
85+
StringUtils.join(
86+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
87+
"<xs:element name='root' type='xs:string'/>",
88+
"</xs:schema>"
89+
)
90+
);
8191

8292
@Test
8393
void findsDocumentNodesWithXpath() {
@@ -122,6 +132,74 @@ void findWithXpathListEqualsToJavaUtilList() {
122132
);
123133
}
124134

135+
@RepeatedTest(60)
136+
void doesNotFailOnFetchingInMultipleThreadsFromTheSameDocument() {
137+
final XML xml = new XMLDocument("<a></a>");
138+
Assertions.assertDoesNotThrow(
139+
new Together<>(
140+
thread -> xml.nodes("/a")
141+
)::asList,
142+
"XMLDocument must not fail on fetching in multiple threads from the same document"
143+
);
144+
}
145+
146+
@RepeatedTest(60)
147+
void doesNotFailOnFetchingInMultipleThreadsFromDifferentDocuments() {
148+
Assertions.assertDoesNotThrow(
149+
new Together<>(
150+
thread -> {
151+
final XML xmir = new XMLDocument("<a></a>");
152+
return xmir.nodes("/a");
153+
}
154+
)::asList,
155+
"XMLDocument must not fail on fetching in multiple threads from different documents"
156+
);
157+
}
158+
159+
@RepeatedTest(60)
160+
void doesNotFailOnXsdValidationInMultipleThreadsWithTheSameDocumentAndXsd() {
161+
final XML xml = new XMLDocument("<root>passesValidXmlThrough</root>");
162+
Assertions.assertDoesNotThrow(
163+
new Together<>(
164+
thread -> xml.validate(XMLDocumentTest.XSD)
165+
)::asList,
166+
"XMLDocument should not fail on validation in multiple threads with the same document and XSD"
167+
);
168+
}
169+
170+
@RepeatedTest(60)
171+
void doesNotFailOnXsdValidationInMultipleThreadsWithDifferentDocumentAndXsd() {
172+
Assertions.assertDoesNotThrow(
173+
new Together<>(
174+
thread -> {
175+
final XML xml = new XMLDocument("<root>passesValidXmlThrough</root>");
176+
final XML xsd = new XMLDocument(
177+
StringUtils.join(
178+
"<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>",
179+
"<xs:element name='root' type='xs:string'/>",
180+
"</xs:schema>"
181+
)
182+
);
183+
return xml.validate(xsd);
184+
}
185+
)::asList,
186+
"XMLDocument should not fail on validation in multiple threads with different document and XSD"
187+
);
188+
}
189+
190+
@RepeatedTest(60)
191+
void doesNotFailOnXsdValidationInMultipleThreadsWithDifferentDocumentAndTheSameXsd() {
192+
Assertions.assertDoesNotThrow(
193+
new Together<>(
194+
thread -> {
195+
final XML xml = new XMLDocument("<root>passesValidXmlThrough</root>");
196+
return xml.validate(XMLDocumentTest.XSD);
197+
}
198+
)::asList,
199+
"XMLDocument should not fail on validation in multiple threads with different document and the same XSD"
200+
);
201+
}
202+
125203
@Test
126204
void findsWithXpathAndNamespaces() {
127205
final XML doc = new XMLDocument(
@@ -370,53 +448,6 @@ void printsInMultipleThreads() throws Exception {
370448
service.shutdownNow();
371449
}
372450

373-
@Test
374-
@Disabled
375-
void takesNodeInMultipleThreads() throws Exception {
376-
final int threads = Runtime.getRuntime().availableProcessors() * 10;
377-
final XML xml = new XMLDocument(
378-
StringUtils.join(
379-
Iterables.concat(
380-
Collections.singleton("<r>"),
381-
Iterables.transform(
382-
Collections.nCopies(1000, 0),
383-
pos -> String.format("<x>%d</x>", pos)
384-
),
385-
Collections.singleton("<x>5555</x></r>")
386-
),
387-
" "
388-
)
389-
);
390-
final AtomicInteger done = new AtomicInteger();
391-
final CountDownLatch latch = new CountDownLatch(threads);
392-
final Runnable runnable = () -> {
393-
try {
394-
MatcherAssert.assertThat(
395-
new XMLDocument(xml.inner()).toString(),
396-
Matchers.containsString(">5555<")
397-
);
398-
done.incrementAndGet();
399-
} finally {
400-
latch.countDown();
401-
}
402-
};
403-
final ExecutorService service = Executors.newFixedThreadPool(threads);
404-
try {
405-
for (int thread = 0; thread < threads; ++thread) {
406-
service.submit(runnable);
407-
}
408-
latch.await(1L, TimeUnit.SECONDS);
409-
MatcherAssert.assertThat(done.get(), Matchers.equalTo(threads));
410-
} finally {
411-
service.shutdown();
412-
MatcherAssert.assertThat(
413-
service.awaitTermination(10L, TimeUnit.SECONDS),
414-
Matchers.is(true)
415-
);
416-
service.shutdownNow();
417-
}
418-
}
419-
420451
@Test
421452
void performsXpathCalculations() {
422453
final XML xml = new XMLDocument("<x><a/><a/><a/></x>");
@@ -556,14 +587,6 @@ void validatesXml() throws IOException {
556587
);
557588
}
558589

559-
@Test
560-
void validatesXmlWithoutSchema() {
561-
MatcherAssert.assertThat(
562-
new XMLDocument("<test/>").validate(),
563-
Matchers.not(Matchers.empty())
564-
);
565-
}
566-
567590
@Test
568591
void detectsSchemaViolations() {
569592
final String xsd = StringUtils.join(
@@ -772,5 +795,4 @@ private static String large() {
772795
)
773796
);
774797
}
775-
776798
}

0 commit comments

Comments
 (0)
Please sign in to comment.