View Javadoc

1   package org.apache.maven.report.projectinfo;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.validator.UrlValidator;
23  import org.apache.maven.doxia.sink.Sink;
24  import org.apache.maven.model.License;
25  import org.apache.maven.project.MavenProject;
26  import org.apache.maven.reporting.AbstractMavenReportRenderer;
27  import org.apache.maven.settings.Settings;
28  import org.codehaus.plexus.i18n.I18N;
29  import org.codehaus.plexus.util.StringUtils;
30  
31  import java.io.File;
32  import java.io.IOException;
33  import java.net.MalformedURLException;
34  import java.net.URL;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Locale;
38  import java.util.regex.Matcher;
39  import java.util.regex.Pattern;
40  
41  /**
42   * Generates the Project License report.
43   *
44   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton </a>
45   * @version $Id: LicenseReport.java 748487 2009-02-27 11:22:04Z vsiveton $
46   * @since 2.0
47   * @goal license
48   */
49  public class LicenseReport
50      extends AbstractProjectInfoReport
51  {
52      // ----------------------------------------------------------------------
53      // Mojo parameters
54      // ----------------------------------------------------------------------
55  
56      /**
57       * The Maven Settings.
58       *
59       * @parameter default-value="${settings}"
60       * @required
61       * @readonly
62       */
63      private Settings settings;
64  
65      /**
66       * Whether the system is currently offline.
67       *
68       * @parameter expression="${settings.offline}"
69       */
70      private boolean offline;
71  
72      // ----------------------------------------------------------------------
73      // Public methods
74      // ----------------------------------------------------------------------
75  
76      /** {@inheritDoc} */
77      public String getName( Locale locale )
78      {
79          return i18n.getString( "project-info-report", locale, "report.license.name" );
80      }
81  
82      /** {@inheritDoc} */
83      public String getDescription( Locale locale )
84      {
85          return i18n.getString( "project-info-report", locale, "report.license.description" );
86      }
87  
88      /** {@inheritDoc} */
89      public void executeReport( Locale locale )
90      {
91          LicenseRenderer r = new LicenseRenderer( getSink(), getProject(), i18n, locale, settings );
92  
93          r.render();
94      }
95  
96      /** {@inheritDoc} */
97      public boolean canGenerateReport()
98      {
99          if ( !offline )
100         {
101             return true;
102         }
103 
104         List licenses = project.getModel().getLicenses();
105         for ( Iterator i = licenses.iterator(); i.hasNext(); )
106         {
107             License license = (License) i.next();
108 
109             String url = license.getUrl();
110 
111             URL licenseUrl = null;
112             try
113             {
114                 licenseUrl = getLicenseURL( project, url );
115             }
116             catch ( MalformedURLException e )
117             {
118                 getLog().error( e.getMessage() );
119             }
120             catch ( IOException e )
121             {
122                 getLog().error( e.getMessage() );
123             }
124 
125             if ( licenseUrl != null && licenseUrl.getProtocol().equals( "file" ) )
126             {
127                 return true;
128             }
129         }
130 
131         return false;
132     }
133 
134     /** {@inheritDoc} */
135     public String getOutputName()
136     {
137         return "license";
138     }
139 
140     /**
141      * @param project not null
142      * @param url not null
143      * @return a valid URL object from the url string
144      * @throws MalformedURLException if any
145      * @throws IOException if any
146      */
147     protected static URL getLicenseURL( MavenProject project, String url )
148         throws MalformedURLException, IOException
149     {
150         URL licenseUrl = null;
151         UrlValidator urlValidator = new UrlValidator( UrlValidator.ALLOW_ALL_SCHEMES );
152         // UrlValidator does not accept file URLs because the file
153         // URLs do not contain a valid authority (no hostname).
154         // As a workaround accept license URLs that start with the
155         // file scheme.
156         if ( urlValidator.isValid( url ) || StringUtils.defaultString( url ).startsWith( "file://" ) )
157         {
158             try
159             {
160                 licenseUrl = new URL( url );
161             }
162             catch ( MalformedURLException e )
163             {
164                 throw new MalformedURLException( "The license url '" + url + "' seems to be invalid: "
165                     + e.getMessage() );
166             }
167         }
168         else
169         {
170             File licenseFile = new File( project.getBasedir(), url );
171             if ( !licenseFile.exists() )
172             {
173                 // Workaround to allow absolute path names while
174                 // staying compatible with the way it was...
175                 licenseFile = new File( url );
176             }
177             if ( !licenseFile.exists() )
178             {
179                 throw new IOException( "Maven can't find the file '" + licenseFile + "' on the system." );
180             }
181             try
182             {
183                 licenseUrl = licenseFile.toURL();
184             }
185             catch ( MalformedURLException e )
186             {
187                 throw new MalformedURLException( "The license url '" + url + "' seems to be invalid: "
188                     + e.getMessage() );
189             }
190         }
191 
192         return licenseUrl;
193     }
194 
195     // ----------------------------------------------------------------------
196     // Private
197     // ----------------------------------------------------------------------
198 
199     /**
200      * Internal renderer class
201      */
202     private static class LicenseRenderer
203         extends AbstractMavenReportRenderer
204     {
205         private MavenProject project;
206 
207         private Settings settings;
208 
209         private I18N i18n;
210 
211         private Locale locale;
212 
213         LicenseRenderer( Sink sink, MavenProject project, I18N i18n, Locale locale, Settings settings )
214         {
215             super( sink );
216 
217             this.project = project;
218 
219             this.settings = settings;
220 
221             this.i18n = i18n;
222 
223             this.locale = locale;
224         }
225 
226         /** {@inheritDoc} */
227         public String getTitle()
228         {
229             return i18n.getString( "project-info-report", locale, "report.license.title" );
230         }
231 
232         /** {@inheritDoc} */
233         public void renderBody()
234         {
235             List licenses = project.getModel().getLicenses();
236 
237             if ( licenses.isEmpty() )
238             {
239                 startSection( getTitle() );
240 
241                 paragraph( i18n.getString( "project-info-report", locale, "report.license.nolicense" ) );
242 
243                 endSection();
244 
245                 return;
246             }
247 
248             // Overview
249             startSection( i18n.getString( "project-info-report", locale, "report.license.overview.title" ) );
250 
251             paragraph( i18n.getString( "project-info-report", locale, "report.license.overview.intro" ) );
252 
253             endSection();
254 
255             // License
256             startSection( i18n.getString( "project-info-report", locale, "report.license.title" ) );
257 
258             for ( Iterator i = licenses.iterator(); i.hasNext(); )
259             {
260                 License license = (License) i.next();
261 
262                 String name = license.getName();
263                 String url = license.getUrl();
264                 String comments = license.getComments();
265 
266                 startSection( name );
267 
268                 if ( !StringUtils.isEmpty( comments ) )
269                 {
270                     paragraph( comments );
271                 }
272 
273                 if ( url != null )
274                 {
275                     URL licenseUrl = null;
276                     try
277                     {
278                         licenseUrl = getLicenseURL( project, url );
279                     }
280                     catch ( MalformedURLException e )
281                     {
282                         // I18N message
283                         paragraph( e.getMessage() );
284                     }
285                     catch ( IOException e )
286                     {
287                         // I18N message
288                         paragraph( e.getMessage() );
289                     }
290 
291                     if ( licenseUrl != null )
292                     {
293                         String licenseContent = null;
294                         try
295                         {
296                             // All licenses are supposed in English...
297                             licenseContent = ProjectInfoReportUtils.getInputStream( licenseUrl, settings );
298                         }
299                         catch ( IOException e )
300                         {
301                             paragraph( "Can't read the url [" + licenseUrl + "] : " + e.getMessage() );
302                         }
303 
304                         if ( licenseContent != null )
305                         {
306                             // TODO: we should check for a text/html mime type instead, and possibly use a html parser to do this a bit more cleanly/reliably.
307                             String licenseContentLC = licenseContent.toLowerCase( Locale.ENGLISH );
308                             int bodyStart = licenseContentLC.indexOf( "<body" );
309                             int bodyEnd = licenseContentLC.indexOf( "</body>" );
310                             if ( ( licenseContentLC.startsWith( "<!doctype html" )
311                                 || licenseContentLC.startsWith( "<html>" ) ) && bodyStart >= 0 && bodyEnd >= 0 )
312                             {
313                                 bodyStart = licenseContentLC.indexOf( ">", bodyStart ) + 1;
314                                 String body = licenseContent.substring( bodyStart, bodyEnd );
315 
316                                 link( licenseUrl.toExternalForm(), "[Original text]" );
317                                 paragraph( "Copy of the license follows." );
318 
319                                 body = replaceRelativeLinks( body, baseURL( licenseUrl ).toExternalForm() );
320                                 sink.rawText( body );
321                             }
322                             else
323                             {
324                                 verbatimText( licenseContent );
325                             }
326                         }
327                     }
328                 }
329 
330                 endSection();
331             }
332 
333             endSection();
334         }
335 
336         private static URL baseURL( URL aUrl )
337         {
338             String urlTxt = aUrl.toExternalForm();
339             int lastSlash = urlTxt.lastIndexOf( '/' );
340             if ( lastSlash > -1 )
341             {
342                 try
343                 {
344                     return new URL( urlTxt.substring( 0, lastSlash + 1 ) );
345                 }
346                 catch ( MalformedURLException e )
347                 {
348                     throw new AssertionError( e );
349                 }
350             }
351 
352             return aUrl;
353         }
354 
355         private static String replaceRelativeLinks( String html, String baseURL )
356         {
357             String url = baseURL;
358             if ( !url.endsWith( "/" ) )
359             {
360                 url += "/";
361             }
362 
363             String serverURL = url.substring( 0, url.indexOf( '/', url.indexOf( "//" ) + 2 ) );
364 
365             String content = replaceParts( html, url, serverURL, "[aA]", "[hH][rR][eE][fF]" );
366             content = replaceParts( content, url, serverURL, "[iI][mM][gG]", "[sS][rR][cC]" );
367             return content;
368         }
369 
370         private static String replaceParts( String html, String baseURL, String serverURL, String tagPattern,
371                                             String attributePattern )
372         {
373             Pattern anchor = Pattern.compile(
374                 "(<\\s*" + tagPattern + "\\s+[^>]*" + attributePattern + "\\s*=\\s*\")([^\"]*)\"([^>]*>)" );
375             StringBuffer sb = new StringBuffer( html );
376 
377             int indx = 0;
378             boolean done = false;
379             while ( !done )
380             {
381                 Matcher mAnchor = anchor.matcher( sb );
382                 if ( mAnchor.find( indx ) )
383                 {
384                     indx = mAnchor.end( 3 );
385 
386                     if ( mAnchor.group( 2 ).startsWith( "#" ) )
387                     {
388                         // relative link - don't want to alter this one!
389                     }
390                     if ( mAnchor.group( 2 ).startsWith( "/" ) )
391                     {
392                         // root link
393                         sb.insert( mAnchor.start( 2 ), serverURL );
394                         indx += serverURL.length();
395                     }
396                     else if ( mAnchor.group( 2 ).indexOf( ':' ) < 0 )
397                     {
398                         // relative link
399                         sb.insert( mAnchor.start( 2 ), baseURL );
400                         indx += baseURL.length();
401                     }
402                 }
403                 else
404                 {
405                     done = true;
406                 }
407             }
408             return sb.toString();
409         }
410     }
411 }