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