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