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.xml.sax.SAXException; |
48 | |
import org.xml.sax.SAXParseException; |
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
|
61 | 0 | @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 | 5 | this(xml, StrictXML.newValidator()); |
75 | 2 | } |
76 | |
|
77 | |
|
78 | |
|
79 | |
|
80 | |
|
81 | |
|
82 | |
public StrictXML(final XML xml, final Validator val) { |
83 | 6 | this(xml, StrictXML.validate(xml, val)); |
84 | 3 | } |
85 | |
|
86 | |
|
87 | |
|
88 | |
|
89 | |
|
90 | |
|
91 | |
public StrictXML(final XML xml, final XSD schema) { |
92 | 52 | this(xml, schema.validate(new DOMSource(xml.node()))); |
93 | 1 | } |
94 | |
|
95 | |
|
96 | |
|
97 | |
|
98 | |
|
99 | |
|
100 | |
private StrictXML(final XML xml, final Collection<SAXParseException> errors) |
101 | 58 | { |
102 | 58 | if (!errors.isEmpty()) { |
103 | 54 | Logger.warn( |
104 | |
StrictXML.class, |
105 | |
"%d XML validation error(s):\n %s\n%s", |
106 | |
errors.size(), |
107 | |
StrictXML.join(StrictXML.print(errors), "\n "), |
108 | |
xml |
109 | |
); |
110 | 54 | throw new IllegalArgumentException( |
111 | |
String.format( |
112 | |
"%d error(s) in XML document: %s", |
113 | |
errors.size(), |
114 | |
StrictXML.join(StrictXML.print(errors), ";") |
115 | |
) |
116 | |
); |
117 | |
} |
118 | 4 | this.origin = xml; |
119 | 4 | } |
120 | |
|
121 | |
@Override |
122 | |
public String toString() { |
123 | 0 | return this.origin.toString(); |
124 | |
} |
125 | |
|
126 | |
@Override |
127 | |
public List<String> xpath(final String query) { |
128 | 0 | return this.origin.xpath(query); |
129 | |
} |
130 | |
|
131 | |
@Override |
132 | |
public List<XML> nodes(final String query) { |
133 | 0 | return this.origin.nodes(query); |
134 | |
} |
135 | |
|
136 | |
@Override |
137 | |
public XML registerNs(final String prefix, final Object uri) { |
138 | 0 | return this.origin.registerNs(prefix, uri); |
139 | |
} |
140 | |
|
141 | |
@Override |
142 | |
public XML merge(final NamespaceContext context) { |
143 | 0 | return this.origin.merge(context); |
144 | |
} |
145 | |
|
146 | |
@Override |
147 | |
public Node node() { |
148 | 0 | return this.origin.node(); |
149 | |
} |
150 | |
|
151 | |
|
152 | |
|
153 | |
|
154 | |
|
155 | |
|
156 | |
private static Iterable<String> print( |
157 | |
final Collection<SAXParseException> errors) { |
158 | 108 | final Collection<String> lines = new ArrayList<String>(errors.size()); |
159 | 108 | for (final SAXParseException error : errors) { |
160 | 220 | lines.add( |
161 | |
String.format( |
162 | |
"%d:%d: %s", |
163 | |
error.getLineNumber(), |
164 | |
error.getColumnNumber(), |
165 | |
error.getMessage() |
166 | |
) |
167 | |
); |
168 | 220 | } |
169 | 108 | return lines; |
170 | |
} |
171 | |
|
172 | |
|
173 | |
|
174 | |
|
175 | |
|
176 | |
|
177 | |
|
178 | |
|
179 | |
private static String join(final Iterable<?> iterable, final String sep) { |
180 | 108 | final Iterator<?> iterator = iterable.iterator(); |
181 | 108 | final Object first = iterator.next(); |
182 | |
|
183 | 108 | final StringBuilder buf = new StringBuilder(256); |
184 | 108 | if (first != null) { |
185 | 108 | buf.append(first); |
186 | |
} |
187 | 220 | while (iterator.hasNext()) { |
188 | 112 | buf.append(sep); |
189 | 112 | final Object obj = iterator.next(); |
190 | 112 | if (obj != null) { |
191 | 112 | buf.append(obj); |
192 | |
} |
193 | 112 | } |
194 | 108 | return buf.toString(); |
195 | |
} |
196 | |
|
197 | |
|
198 | |
|
199 | |
|
200 | |
|
201 | |
|
202 | |
|
203 | |
private static Collection<SAXParseException> validate( |
204 | |
final XML xml, |
205 | |
final Validator validator) { |
206 | 6 | final Collection<SAXParseException> errors = |
207 | |
new CopyOnWriteArrayList<SAXParseException>(); |
208 | 6 | final int max = 3; |
209 | |
try { |
210 | 6 | validator.setErrorHandler( |
211 | |
new XSDDocument.ValidationHandler(errors) |
212 | |
); |
213 | 6 | final DOMSource dom = new DOMSource(xml.node()); |
214 | 8 | for (int retry = 1; retry <= max; ++retry) { |
215 | |
try { |
216 | 8 | validator.validate(dom); |
217 | 6 | break; |
218 | 2 | } catch (final SocketException ex) { |
219 | 2 | Logger.error( |
220 | |
StrictXML.class, |
221 | |
"Try #%d of %d failed: %s: %s", |
222 | |
retry, |
223 | |
max, |
224 | |
ex.getClass().getName(), |
225 | |
ex.getMessage() |
226 | |
); |
227 | 2 | if (retry == max) { |
228 | 0 | throw new IllegalStateException(ex); |
229 | |
} |
230 | |
} |
231 | |
} |
232 | 0 | } catch (final SAXException ex) { |
233 | 0 | throw new IllegalStateException(ex); |
234 | 0 | } catch (final IOException ex) { |
235 | 0 | throw new IllegalStateException(ex); |
236 | 6 | } |
237 | 6 | return errors; |
238 | |
} |
239 | |
|
240 | |
|
241 | |
|
242 | |
|
243 | |
|
244 | |
private static Validator newValidator() { |
245 | |
try { |
246 | 5 | final Validator validator = SchemaFactory |
247 | |
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) |
248 | |
.newSchema() |
249 | |
.newValidator(); |
250 | 5 | validator.setResourceResolver(new ClasspathResolver()); |
251 | 5 | return validator; |
252 | 0 | } catch (final SAXException ex) { |
253 | 0 | throw new IllegalStateException(ex); |
254 | |
} |
255 | |
} |
256 | |
} |