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