1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.jcabi.xml;
31
32 import com.jcabi.log.Logger;
33 import java.io.IOException;
34 import java.net.SocketException;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.concurrent.CopyOnWriteArrayList;
40 import javax.xml.XMLConstants;
41 import javax.xml.namespace.NamespaceContext;
42 import javax.xml.transform.dom.DOMSource;
43 import javax.xml.validation.SchemaFactory;
44 import javax.xml.validation.Validator;
45 import lombok.EqualsAndHashCode;
46 import org.w3c.dom.Node;
47 import org.w3c.dom.ls.LSResourceResolver;
48 import org.xml.sax.SAXException;
49 import org.xml.sax.SAXParseException;
50
51
52
53
54
55
56
57
58
59
60
61 @EqualsAndHashCode(of = "origin")
62 public final class StrictXML implements XML {
63
64
65
66
67 private final transient XML origin;
68
69
70
71
72
73 public StrictXML(final XML xml) {
74 this(xml, new ClasspathResolver());
75 }
76
77
78
79
80
81
82
83 public StrictXML(final XML xml, final LSResourceResolver resolver) {
84 this(xml, StrictXML.newValidator(resolver));
85 }
86
87
88
89
90
91
92 public StrictXML(final XML xml, final Validator val) {
93 this(xml, StrictXML.validate(xml, val));
94 }
95
96
97
98
99
100
101 public StrictXML(final XML xml, final XSD schema) {
102 this(xml, schema.validate(new DOMSource(xml.node())));
103 }
104
105
106
107
108
109
110 @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
111 private StrictXML(final XML xml,
112 final Collection<SAXParseException> errors) {
113 if (!errors.isEmpty()) {
114 Logger.warn(
115 StrictXML.class,
116 "%d XML validation error(s):\n %s\n%s",
117 errors.size(),
118 StrictXML.join(StrictXML.print(errors), "\n "),
119 xml
120 );
121 throw new IllegalArgumentException(
122 String.format(
123 "%d error(s) in XML document: %s",
124 errors.size(),
125 StrictXML.join(StrictXML.print(errors), ";")
126 )
127 );
128 }
129 this.origin = xml;
130 }
131
132 @Override
133 public String toString() {
134 return this.origin.toString();
135 }
136
137 @Override
138 public List<String> xpath(final String query) {
139 return this.origin.xpath(query);
140 }
141
142 @Override
143 public List<XML> nodes(final String query) {
144 return this.origin.nodes(query);
145 }
146
147 @Override
148 public XML registerNs(final String prefix, final Object uri) {
149 return this.origin.registerNs(prefix, uri);
150 }
151
152 @Override
153 public XML merge(final NamespaceContext context) {
154 return this.origin.merge(context);
155 }
156
157 @Override
158 public Node node() {
159 return this.origin.node();
160 }
161
162
163
164
165
166
167 private static Iterable<String> print(
168 final Collection<SAXParseException> errors) {
169 final Collection<String> lines = new ArrayList<>(errors.size());
170 for (final SAXParseException error : errors) {
171 lines.add(
172 String.format(
173 "%d:%d: %s",
174 error.getLineNumber(),
175 error.getColumnNumber(),
176 error.getMessage()
177 )
178 );
179 }
180 return lines;
181 }
182
183
184
185
186
187
188
189
190 private static String join(final Iterable<?> iterable, final String sep) {
191 final Iterator<?> iterator = iterable.iterator();
192 final Object first = iterator.next();
193 final StringBuilder buf = new StringBuilder(256);
194 if (first != null) {
195 buf.append(first);
196 }
197 while (iterator.hasNext()) {
198 buf.append(sep);
199 final Object obj = iterator.next();
200 if (obj != null) {
201 buf.append(obj);
202 }
203 }
204 return buf.toString();
205 }
206
207
208
209
210
211
212
213 private static Collection<SAXParseException> validate(
214 final XML xml,
215 final Validator validator) {
216 final Collection<SAXParseException> errors =
217 new CopyOnWriteArrayList<>();
218 final int max = 3;
219 try {
220 validator.setErrorHandler(
221 new XSDDocument.ValidationHandler(errors)
222 );
223 final DOMSource dom = new DOMSource(xml.node());
224 for (int retry = 1; retry <= max; ++retry) {
225 try {
226 validator.validate(dom);
227 break;
228 } catch (final SocketException ex) {
229 Logger.error(
230 StrictXML.class,
231 "Try #%d of %d failed: %s: %s",
232 retry,
233 max,
234 ex.getClass().getName(),
235 ex.getMessage()
236 );
237 if (retry == max) {
238 throw new IllegalStateException(ex);
239 }
240 }
241 }
242 } catch (final SAXException | IOException ex) {
243 throw new IllegalStateException(ex);
244 }
245 return errors;
246 }
247
248
249
250
251
252
253 private static Validator newValidator(final LSResourceResolver resolver) {
254 try {
255 final Validator validator = SchemaFactory
256 .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
257 .newSchema()
258 .newValidator();
259 validator.setResourceResolver(resolver);
260 return validator;
261 } catch (final SAXException ex) {
262 throw new IllegalStateException(ex);
263 }
264 }
265 }