Coverage Report - com.jcabi.xml.StrictXML
 
Classes in this File Line Coverage Branch Coverage Complexity
StrictXML
75%
44/58
38%
10/26
2.214
 
 1  
 /**
 2  
  * Copyright (c) 2012-2017, 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 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  
  * Strict {@link XML} that fails if encapsulated XML document
 52  
  * doesn't validate against externally provided XSD schema or internally
 53  
  * specified schema locations.
 54  
  *
 55  
  * <p>Objects of this class are immutable and thread-safe.
 56  
  *
 57  
  * @author Yegor Bugayenko (yegor@teamed.io)
 58  
  * @version $Id: 102f566ef917932d04b9ac84dde9c906364f7a52 $
 59  
  * @since 0.7
 60  
  */
 61  0
 @EqualsAndHashCode(of = "origin")
 62  
 public final class StrictXML implements XML {
 63  
 
 64  
     /**
 65  
      * Original XML document.
 66  
      */
 67  
     private final transient XML origin;
 68  
 
 69  
     /**
 70  
      * Public ctor.
 71  
      * @param xml XML document
 72  
      */
 73  
     public StrictXML(final XML xml) {
 74  5
         this(xml, StrictXML.newValidator());
 75  2
     }
 76  
 
 77  
     /**
 78  
      * Public ctor.
 79  
      * @param xml XML document
 80  
      * @param val Custom validator
 81  
      */
 82  
     public StrictXML(final XML xml, final Validator val) {
 83  6
         this(xml, StrictXML.validate(xml, val));
 84  3
     }
 85  
 
 86  
     /**
 87  
      * Public ctor.
 88  
      * @param xml XML document
 89  
      * @param schema XSD schema
 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  
      * Private ctor.
 97  
      * @param xml XML Document
 98  
      * @param errors XML Document errors
 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  
      * Convert errors to lines.
 153  
      * @param errors The errors
 154  
      * @return List of messages to print
 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  
      * Joins many objects' string representations with the given separator
 174  
      * string. The separator will not be appended to the beginning or the end.
 175  
      * @param iterable Iterable of objects.
 176  
      * @param sep Separator string.
 177  
      * @return Joined string.
 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  
         // @checkstyle MagicNumber (1 line)
 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  
      * Validate XML without external schema.
 199  
      * @param xml XML Document
 200  
      * @param validator XML Validator
 201  
      * @return List of validation errors
 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  
      * Creates a new validator.
 242  
      * @return A new validator
 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  
 }