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