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.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 | |
|
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | 0 | @ToString |
55 | 0 | @EqualsAndHashCode(callSuper = false, of = "home") |
56 | |
public final class NoBrokenLinks extends BaseMatcher<Response> { |
57 | |
|
58 | |
|
59 | |
|
60 | |
|
61 | |
private final transient URI home; |
62 | |
|
63 | |
|
64 | |
|
65 | |
|
66 | 4 | private final transient Collection<URI> broken = new LinkedList<URI>(); |
67 | |
|
68 | |
|
69 | |
|
70 | |
|
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 | |
|
95 | |
|
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 | |
|
127 | |
|
128 | |
|
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 | |
|
155 | |
|
156 | |
|
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 | |
} |