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