1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.report.projectinfo;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.net.MalformedURLException;
24 import java.net.URL;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import org.apache.commons.validator.routines.UrlValidator;
31 import org.apache.maven.doxia.sink.Sink;
32 import org.apache.maven.doxia.util.DoxiaUtils;
33 import org.apache.maven.model.License;
34 import org.apache.maven.plugins.annotations.Mojo;
35 import org.apache.maven.plugins.annotations.Parameter;
36 import org.apache.maven.project.MavenProject;
37 import org.apache.maven.reporting.MavenReportException;
38 import org.apache.maven.settings.Settings;
39 import org.codehaus.plexus.i18n.I18N;
40 import org.codehaus.plexus.util.StringUtils;
41
42
43
44
45
46
47
48 @Mojo(name = "licenses")
49 public class LicensesReport extends AbstractProjectInfoReport {
50
51
52
53
54
55
56
57 @Parameter(property = "settings.offline")
58 private boolean offline;
59
60
61
62
63
64
65
66
67 @Parameter(defaultValue = "false")
68 private boolean linkOnly;
69
70
71
72
73
74
75 @Parameter
76 private String licenseFileEncoding;
77
78
79
80
81
82 @Override
83 public boolean canGenerateReport() throws MavenReportException {
84 boolean result = super.canGenerateReport();
85 if (result && skipEmptyReport) {
86 result = !isEmpty(getProject().getModel().getLicenses());
87 }
88
89 if (!result) {
90 return false;
91 }
92
93 if (!offline) {
94 return true;
95 }
96
97 for (License license : project.getModel().getLicenses()) {
98 String url = license.getUrl();
99
100 URL licenseUrl = null;
101 try {
102 licenseUrl = getLicenseURL(project, url);
103 } catch (IOException e) {
104 getLog().error(e.getMessage());
105 }
106
107 if (licenseUrl != null && licenseUrl.getProtocol().equals("file")) {
108 return true;
109 }
110
111 if (licenseUrl != null
112 && (licenseUrl.getProtocol().equals("http")
113 || licenseUrl.getProtocol().equals("https"))) {
114 linkOnly = true;
115 return true;
116 }
117 }
118
119 return false;
120 }
121
122 @Override
123 public void executeReport(Locale locale) {
124 LicensesRenderer r = new LicensesRenderer(
125 getSink(), getProject(), getI18N(locale), locale, settings, linkOnly, licenseFileEncoding);
126
127 r.render();
128 }
129
130
131
132
133 public String getOutputName() {
134 return "licenses";
135 }
136
137 @Override
138 protected String getI18Nsection() {
139 return "licenses";
140 }
141
142
143
144
145
146
147
148 protected static URL getLicenseURL(MavenProject project, String url) throws IOException {
149 URL licenseUrl;
150 UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_ALL_SCHEMES);
151
152
153
154
155 if (urlValidator.isValid(url) || StringUtils.defaultString(url).startsWith("file://")) {
156 try {
157 licenseUrl = new URL(url);
158 } catch (MalformedURLException e) {
159 throw new MalformedURLException("The license url '" + url + "' seems to be invalid: " + e.getMessage());
160 }
161 } else {
162 File licenseFile = new File(project.getBasedir(), url);
163 if (!licenseFile.exists()) {
164
165
166 licenseFile = new File(url);
167 }
168 if (!licenseFile.exists()) {
169 throw new IOException("Maven can't find the file '" + licenseFile + "' on the system.");
170 }
171 try {
172 licenseUrl = licenseFile.toURI().toURL();
173 } catch (MalformedURLException e) {
174 throw new MalformedURLException("The license url '" + url + "' seems to be invalid: " + e.getMessage());
175 }
176 }
177
178 return licenseUrl;
179 }
180
181
182
183
184
185
186
187
188 private static class LicensesRenderer extends AbstractProjectInfoRenderer {
189 private final MavenProject project;
190
191 private final Settings settings;
192
193 private final boolean linkOnly;
194
195 private final String licenseFileEncoding;
196
197 LicensesRenderer(
198 Sink sink,
199 MavenProject project,
200 I18N i18n,
201 Locale locale,
202 Settings settings,
203 boolean linkOnly,
204 String licenseFileEncoding) {
205 super(sink, i18n, locale);
206
207 this.project = project;
208
209 this.settings = settings;
210
211 this.linkOnly = linkOnly;
212
213 this.licenseFileEncoding = licenseFileEncoding;
214 }
215
216 @Override
217 protected String getI18Nsection() {
218 return "licenses";
219 }
220
221 @Override
222 protected void renderBody() {
223 List<License> licenses = project.getModel().getLicenses();
224
225 if (licenses.isEmpty()) {
226 startSection(getTitle());
227
228 paragraph(getI18nString("nolicense"));
229
230 endSection();
231
232 return;
233 }
234
235
236 startSection(getI18nString("overview.title"));
237
238 paragraph(getI18nString("overview.intro"));
239
240 endSection();
241
242
243 startSection(getI18nString("title"));
244
245 if (licenses.size() > 1) {
246
247 paragraph(getI18nString("multiple"));
248
249 if (!linkOnly) {
250
251 sink.list();
252 for (License license : licenses) {
253 String name = license.getName();
254 if (name == null || name.isEmpty()) {
255 name = getI18nString("unnamed");
256 }
257
258 sink.listItem();
259 link("#" + DoxiaUtils.encodeId(name), name);
260 sink.listItem_();
261 }
262 sink.list_();
263 }
264 }
265
266 for (License license : licenses) {
267 String name = license.getName();
268 if (name == null || name.isEmpty()) {
269 name = getI18nString("unnamed");
270 }
271
272 String url = license.getUrl();
273 String comments = license.getComments();
274
275 startSection(name);
276
277 if (!(comments == null || comments.isEmpty())) {
278 paragraph(comments);
279 }
280
281 if (url != null) {
282 try {
283 URL licenseUrl = getLicenseURL(project, url);
284
285 if (linkOnly) {
286 link(licenseUrl.toExternalForm(), licenseUrl.toExternalForm());
287 } else {
288 renderLicenseContent(licenseUrl);
289 }
290 } catch (IOException e) {
291
292 paragraph(e.getMessage());
293 }
294 }
295
296 endSection();
297 }
298
299 endSection();
300 }
301
302
303
304
305
306
307 private void renderLicenseContent(URL licenseUrl) {
308 try {
309
310 String licenseContent = ProjectInfoReportUtils.getContent(licenseUrl, settings, licenseFileEncoding);
311
312
313
314 String licenseContentLC = licenseContent.toLowerCase(Locale.ENGLISH);
315 int bodyStart = licenseContentLC.indexOf("<body");
316 int bodyEnd = licenseContentLC.indexOf("</body>");
317
318 if ((licenseContentLC.contains("<!doctype html") || licenseContentLC.contains("<html>"))
319 && ((bodyStart >= 0) && (bodyEnd > bodyStart))) {
320 bodyStart = licenseContentLC.indexOf('>', bodyStart) + 1;
321 String body = licenseContent.substring(bodyStart, bodyEnd);
322
323 link(licenseUrl.toExternalForm(), getI18nString("originalText"));
324 paragraph(getI18nString("copy"));
325
326 body = replaceRelativeLinks(body, baseURL(licenseUrl).toExternalForm());
327 sink.rawText(body);
328 } else {
329 verbatimText(licenseContent);
330 }
331 } catch (IOException e) {
332 paragraph("Can't read the url [" + licenseUrl + "] : " + e.getMessage());
333 }
334 }
335
336 private static URL baseURL(URL aUrl) {
337 String urlTxt = aUrl.toExternalForm();
338 int lastSlash = urlTxt.lastIndexOf('/');
339 if (lastSlash > -1) {
340 try {
341 return new URL(urlTxt.substring(0, lastSlash + 1));
342 } catch (MalformedURLException e) {
343 throw new AssertionError(e);
344 }
345 }
346
347 return aUrl;
348 }
349
350 private static String replaceRelativeLinks(String html, String baseURL) {
351 String url = baseURL;
352 if (!url.endsWith("/")) {
353 url += "/";
354 }
355
356 String serverURL = url.substring(0, url.indexOf('/', url.indexOf("//") + 2));
357
358 String content = replaceParts(html, url, serverURL, "[aA]", "[hH][rR][eE][fF]");
359 content = replaceParts(content, url, serverURL, "[iI][mM][gG]", "[sS][rR][cC]");
360 return content;
361 }
362
363 private static String replaceParts(
364 String html, String baseURL, String serverURL, String tagPattern, String attributePattern) {
365 Pattern anchor = Pattern.compile(
366 "(<\\s*" + tagPattern + "\\s+[^>]*" + attributePattern + "\\s*=\\s*\")([^\"]*)\"([^>]*>)");
367 StringBuilder sb = new StringBuilder(html);
368
369 int indx = 0;
370 boolean done = false;
371 while (!done) {
372 Matcher mAnchor = anchor.matcher(sb);
373 if (mAnchor.find(indx)) {
374 indx = mAnchor.end(3);
375
376 if (mAnchor.group(2).startsWith("#")) {
377
378 }
379 if (mAnchor.group(2).startsWith("/")) {
380
381 sb.insert(mAnchor.start(2), serverURL);
382 indx += serverURL.length();
383 } else if (mAnchor.group(2).indexOf(':') < 0) {
384
385 sb.insert(mAnchor.start(2), baseURL);
386 indx += baseURL.length();
387 }
388 } else {
389 done = true;
390 }
391 }
392 return sb.toString();
393 }
394 }
395 }