| 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 | |
} |