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