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