Coverage Report - com.jcabi.matchers.NoBrokenLinks
 
Classes in this File Line Coverage Branch Coverage Complexity
NoBrokenLinks
75%
31/41
50%
12/24
2.5
 
 1  
 /**
 2  
  * Copyright (c) 2011-2014, 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.matchers;
 31  
 
 32  
 import com.jcabi.http.Response;
 33  
 import com.jcabi.http.response.XmlResponse;
 34  
 import com.jcabi.log.Logger;
 35  
 import java.io.IOException;
 36  
 import java.net.HttpURLConnection;
 37  
 import java.net.MalformedURLException;
 38  
 import java.net.URI;
 39  
 import java.net.URL;
 40  
 import java.util.Collection;
 41  
 import java.util.LinkedList;
 42  
 import lombok.EqualsAndHashCode;
 43  
 import lombok.ToString;
 44  
 import org.hamcrest.BaseMatcher;
 45  
 import org.hamcrest.Description;
 46  
 
 47  
 /**
 48  
  * Finds broken links in HTML.
 49  
  *
 50  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 51  
  * @version $Id$
 52  
  * @since 0.3.4
 53  
  */
 54  0
 @ToString
 55  0
 @EqualsAndHashCode(callSuper = false, of = "home")
 56  
 public final class NoBrokenLinks extends BaseMatcher<Response> {
 57  
 
 58  
     /**
 59  
      * Home page.
 60  
      */
 61  
     private final transient URI home;
 62  
 
 63  
     /**
 64  
      * List of broken links.
 65  
      */
 66  4
     private final transient Collection<URI> broken = new LinkedList<URI>();
 67  
 
 68  
     /**
 69  
      * Public ctor.
 70  
      * @param uri Home page URI, for relative links
 71  
      */
 72  
     public NoBrokenLinks(final URI uri) {
 73  4
         super();
 74  4
         this.home = uri;
 75  4
     }
 76  
 
 77  
     @Override
 78  
     public boolean matches(final Object item) {
 79  4
         this.check(Response.class.cast(item));
 80  3
         return this.broken.isEmpty();
 81  
     }
 82  
 
 83  
     @Override
 84  
     public void describeTo(final Description description) {
 85  0
         description.appendText(
 86  
             Logger.format(
 87  
                 "%d broken link(s) found: %[list]s",
 88  
                 this.broken.size(), this.broken
 89  
             )
 90  
         );
 91  0
     }
 92  
 
 93  
     /**
 94  
      * Check for validness.
 95  
      * @param response Response to check
 96  
      */
 97  
     private void check(final Response response) {
 98  4
         final Collection<String> links = new XmlResponse(response).xml().xpath(
 99  
             new StringBuilder("//head/link/@href")
 100  
                 .append(" | //body//a/@href")
 101  
                 .append(" | //body//img/@src")
 102  
                 .append(" | //xhtml:img/@src")
 103  
                 .append(" | //xhtml:a/@href")
 104  
                 .append(" | //xhtml:link/@href")
 105  
                 .toString()
 106  
         );
 107  3
         Logger.debug(
 108  
             this, "#assertThat(): %d links found: %[list]s",
 109  
             links.size(), links
 110  
         );
 111  3
         this.broken.clear();
 112  3
         for (final String link : links) {
 113  
             final URI uri;
 114  4
             if (link.isEmpty() || link.charAt(0) != '/') {
 115  2
                 uri = URI.create(link);
 116  
             } else {
 117  2
                 uri = this.home.resolve(link);
 118  
             }
 119  4
             if (!uri.isAbsolute() || !NoBrokenLinks.isValid(uri)) {
 120  2
                 this.broken.add(uri);
 121  
             }
 122  4
         }
 123  3
     }
 124  
 
 125  
     /**
 126  
      * Check whether the URI is valid and returns code 200.
 127  
      * @param uri The URI to check
 128  
      * @return TRUE if it's valid
 129  
      */
 130  
     private static boolean isValid(final URI uri) {
 131  3
         boolean valid = false;
 132  
         try {
 133  3
             final int code = NoBrokenLinks.http(uri.toURL());
 134  3
             if (code < HttpURLConnection.HTTP_BAD_REQUEST) {
 135  2
                 valid = true;
 136  
             } else {
 137  1
                 Logger.warn(
 138  
                     NoBrokenLinks.class,
 139  
                     "#isValid('%s'): not valid since response code is %d",
 140  
                     uri, code
 141  
                 );
 142  
             }
 143  0
         } catch (final MalformedURLException ex) {
 144  0
             Logger.warn(
 145  
                 NoBrokenLinks.class,
 146  
                 "#isValid('%s'): invalid URL: %s",
 147  
                 uri, ex.getMessage()
 148  
             );
 149  3
         }
 150  3
         return valid;
 151  
     }
 152  
 
 153  
     /**
 154  
      * Get HTTP response code from this URL.
 155  
      * @param url The URL to get
 156  
      * @return HTTP response code
 157  
      */
 158  
     private static int http(final URL url) {
 159  3
         int code = HttpURLConnection.HTTP_BAD_REQUEST;
 160  
         try {
 161  3
             final HttpURLConnection conn =
 162  
                 HttpURLConnection.class.cast(url.openConnection());
 163  
             try {
 164  3
                 code = conn.getResponseCode();
 165  0
             } catch (final IOException ex) {
 166  0
                 Logger.warn(
 167  
                     NoBrokenLinks.class,
 168  
                     "#http('%s'): can't get response code: %s",
 169  
                     url, ex.getMessage()
 170  
                 );
 171  
             } finally {
 172  3
                 conn.disconnect();
 173  3
             }
 174  0
         } catch (final IOException ex) {
 175  0
             Logger.warn(
 176  
                 NoBrokenLinks.class,
 177  
                 "#http('%s'): can't open connection: %s",
 178  
                 url, ex.getMessage()
 179  
             );
 180  3
         }
 181  3
         return code;
 182  
     }
 183  
 
 184  
 }