diff --git a/pom.xml b/pom.xml index 25889532c..56ec6cd13 100644 --- a/pom.xml +++ b/pom.xml @@ -294,6 +294,11 @@ 2.3.20 + + org.apache.httpcomponents + httpclient + + log4j log4j diff --git a/src/it/download-licenses-force/invoker.properties b/src/it/download-licenses-force/invoker.properties index 2d950418f..fa44d6e30 100644 --- a/src/it/download-licenses-force/invoker.properties +++ b/src/it/download-licenses-force/invoker.properties @@ -1 +1 @@ -invoker.goals = clean license:download-licenses -Dlicense.forceDownload=true -Dlicense.errorRemedy=xmlOutput +invoker.goals = clean license:download-licenses -Dlicense.forceDownload=true -Dlicense.errorRemedy=xmlOutput -Dlicense.sortByGroupIdAndArtifactId=true diff --git a/src/it/download-licenses-force/licenses.expected.xml b/src/it/download-licenses-force/licenses.expected.xml index fc69a29a5..f39c8759d 100644 --- a/src/it/download-licenses-force/licenses.expected.xml +++ b/src/it/download-licenses-force/licenses.expected.xml @@ -13,5 +13,18 @@ + + org.hibernate + hibernate-search-parent + 5.10.3.Final + + + GNU Lesser General Public License v2.1 or later + http://www.opensource.org/licenses/LGPL-2.1 + gnu lesser general public license v2.1 or later - lgpl-2.1.txt.html + See also: http://hibernate.org/license + + + diff --git a/src/it/download-licenses-force/pom.xml b/src/it/download-licenses-force/pom.xml index 4422e9c19..dd92c0d25 100644 --- a/src/it/download-licenses-force/pom.xml +++ b/src/it/download-licenses-force/pom.xml @@ -24,6 +24,18 @@ groovy-all 1.0-jsr-04 + + org.hibernate + hibernate-search-parent + pom + 5.10.3.Final + + + * + * + + + diff --git a/src/it/download-licenses-force/postbuild.groovy b/src/it/download-licenses-force/postbuild.groovy index 4b79c1207..03dc61b55 100644 --- a/src/it/download-licenses-force/postbuild.groovy +++ b/src/it/download-licenses-force/postbuild.groovy @@ -30,6 +30,10 @@ assert Files.exists(asl2) assert !asl2.text.contains('This content is fake.') assert asl2.text.contains('Version 2.0, January 2004') +final Path lgpl21 = basePath.resolve('target/generated-resources/licenses/gnu lesser general public license v2.1 or later - lgpl-2.1.txt.html') +assert Files.exists(lgpl21) +assert lgpl21.text.contains('Version 2.1, February 1999') + final Path expectedLicensesXml = basePath.resolve('licenses.expected.xml') final Path licensesXml = basePath.resolve('target/generated-resources/licenses.xml') assert expectedLicensesXml.text.equals(licensesXml.text) diff --git a/src/main/java/org/codehaus/mojo/license/AbstractDownloadLicensesMojo.java b/src/main/java/org/codehaus/mojo/license/AbstractDownloadLicensesMojo.java index ba26920fd..635b12f0e 100644 --- a/src/main/java/org/codehaus/mojo/license/AbstractDownloadLicensesMojo.java +++ b/src/main/java/org/codehaus/mojo/license/AbstractDownloadLicensesMojo.java @@ -38,6 +38,7 @@ import org.codehaus.mojo.license.model.ProjectLicenseInfo; import org.codehaus.mojo.license.utils.FileUtil; import org.codehaus.mojo.license.utils.LicenseDownloader; +import org.codehaus.mojo.license.utils.LicenseDownloader.LicenseDownloadResult; import org.codehaus.mojo.license.utils.LicenseSummaryReader; import org.codehaus.mojo.license.utils.LicenseSummaryWriter; import org.codehaus.mojo.license.utils.MojoHelper; @@ -47,7 +48,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; @@ -458,28 +459,37 @@ public void execute() // The resulting list of licenses after dependency resolution List depProjectLicenses = new ArrayList<>(); - for ( MavenProject project : dependencies ) + try ( LicenseDownloader licenseDownloader = new LicenseDownloader() ) { - Artifact artifact = project.getArtifact(); - getLog().debug( "Checking licenses for project " + artifact ); - String artifactProjectId = getArtifactProjectId( artifact ); - ProjectLicenseInfo depProject; - if ( configuredDepLicensesMap.containsKey( artifactProjectId ) ) + for ( MavenProject project : dependencies ) { - depProject = configuredDepLicensesMap.get( artifactProjectId ); - depProject.setVersion( artifact.getVersion() ); - } - else - { - depProject = createDependencyProject( project ); - } - if ( !offline ) - { - downloadLicenses( depProject ); + Artifact artifact = project.getArtifact(); + getLog().debug( "Checking licenses for project " + artifact ); + String artifactProjectId = getArtifactProjectId( artifact ); + ProjectLicenseInfo depProject; + if ( configuredDepLicensesMap.containsKey( artifactProjectId ) ) + { + depProject = configuredDepLicensesMap.get( artifactProjectId ); + depProject.setVersion( artifact.getVersion() ); + } + else + { + depProject = createDependencyProject( project ); + } + if ( !offline ) + { + downloadLicenses( licenseDownloader, depProject ); + } + depProjectLicenses.add( depProject ); } - depProjectLicenses.add( depProject ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); } + + try { if ( sortByGroupIdAndArtifactId ) @@ -867,7 +877,8 @@ private String getLicenseFileName( ProjectLicenseInfo depProject, final URL lice * @param depProject The project which generated the dependency * @throws MojoFailureException */ - private void downloadLicenses( ProjectLicenseInfo depProject ) throws MojoFailureException + private void downloadLicenses( LicenseDownloader licenseDownloader, ProjectLicenseInfo depProject ) + throws MojoFailureException { getLog().debug( "Downloading license(s) for project " + depProject ); @@ -906,9 +917,20 @@ private void downloadLicenses( ProjectLicenseInfo depProject ) throws MojoFailur { if ( !downloadedLicenseURLs.containsKey( licenseUrl ) || organizeLicensesByDependencies ) { - licenseOutputFile = LicenseDownloader.downloadLicense( licenseUrl, proxyLoginPasswordEncoded, - licenseOutputFile, getLog() ); - downloadedLicenseURLs.put( licenseUrl, licenseOutputFile ); + final LicenseDownloadResult result = + licenseDownloader.downloadLicense( licenseUrl, proxyLoginPasswordEncoded, licenseOutputFile, + getLog() ); + + if ( result.isSuccess() ) + { + licenseOutputFile = result.getFile(); + downloadedLicenseURLs.put( licenseUrl, licenseOutputFile ); + } + else + { + handleError( depProject, result.getErrorMessage() ); + } + } } @@ -918,7 +940,7 @@ private void downloadLicenses( ProjectLicenseInfo depProject ) throws MojoFailur } } - catch ( MalformedURLException e ) + catch ( URISyntaxException e ) { handleError( depProject, "POM for dependency " + depProject.toString() + " has an invalid license URL: " + licenseUrl ); diff --git a/src/main/java/org/codehaus/mojo/license/utils/LicenseDownloader.java b/src/main/java/org/codehaus/mojo/license/utils/LicenseDownloader.java index cf37477b9..971ccdb2a 100644 --- a/src/main/java/org/codehaus/mojo/license/utils/LicenseDownloader.java +++ b/src/main/java/org/codehaus/mojo/license/utils/LicenseDownloader.java @@ -27,10 +27,20 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; - +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.maven.plugin.logging.Log; /** @@ -39,7 +49,7 @@ * @author pgier * @since 1.0 */ -public class LicenseDownloader +public class LicenseDownloader implements AutoCloseable { /** @@ -47,6 +57,18 @@ public class LicenseDownloader */ public static final int DEFAULT_CONNECTION_TIMEOUT = 5000; + private final CloseableHttpClient client; + + public LicenseDownloader() + { + final RequestConfig config = RequestConfig.copy( RequestConfig.DEFAULT ) + .setConnectTimeout( DEFAULT_CONNECTION_TIMEOUT ) + .setSocketTimeout( DEFAULT_CONNECTION_TIMEOUT ) + .setConnectionRequestTimeout( DEFAULT_CONNECTION_TIMEOUT ) + .build(); + this.client = HttpClients.custom().setDefaultRequestConfig( config ).build(); + } + /** * Downloads a license file from the given {@code licenseUrlString} stores it locally and * returns the local path where the license file was stored. Note that the @@ -59,54 +81,55 @@ public class LicenseDownloader * @param log * @return the path to the file where the downloaded license file was stored * @throws IOException + * @throws URISyntaxException */ - public static File downloadLicense( String licenseUrlString, String loginPassword, File outputFile, Log log ) - throws IOException + public LicenseDownloadResult downloadLicense( String licenseUrlString, String loginPassword, File outputFile, + Log log ) + throws IOException, URISyntaxException { if ( licenseUrlString == null || licenseUrlString.length() == 0 ) { - return outputFile; + return LicenseDownloadResult.success( outputFile ); } - URLConnection connection = newConnection( licenseUrlString, loginPassword ); - - boolean redirect = false; - if ( connection instanceof HttpURLConnection ) + if ( licenseUrlString.startsWith( "file://" ) ) { - int status = ( (HttpURLConnection) connection ).getResponseCode(); - - log.debug( String.format( "Got HTTP response %d for URL '%s'", status, licenseUrlString ) ); - - redirect = HttpURLConnection.HTTP_MOVED_TEMP == status || HttpURLConnection.HTTP_MOVED_PERM == status - || HttpURLConnection.HTTP_SEE_OTHER == status; + Files.copy( Paths.get( new URI( licenseUrlString ) ), outputFile.toPath() ); + return LicenseDownloadResult.success( outputFile ); } - - if ( redirect ) + else { - // get redirect url from "location" header field - String newUrl = connection.getHeaderField( "Location" ); - - // open the new connnection again - connection = newConnection( newUrl, loginPassword ); - - if ( connection instanceof HttpURLConnection ) + try ( CloseableHttpResponse response = client.execute( new HttpGet( licenseUrlString ) ) ) { - int status = ( (HttpURLConnection) connection ).getResponseCode(); - log.debug( String.format( "Got HTTP response %d for URL '%s'", status, licenseUrlString ) ); + final StatusLine statusLine = response.getStatusLine(); + if ( statusLine.getStatusCode() != HttpStatus.SC_OK ) + { + return LicenseDownloadResult.failure( "'" + licenseUrlString + "' returned " + + statusLine.getStatusCode() + + ( statusLine.getReasonPhrase() != null ? " " + statusLine.getReasonPhrase() : "" ) ); + } + + final HttpEntity entity = response.getEntity(); + if ( entity != null ) + { + final ContentType contentType = ContentType.get( entity ); + File updatedFile = + updateFileExtension( outputFile, contentType != null ? contentType.getMimeType() : null ); + + try ( InputStream in = entity.getContent(); + FileOutputStream fos = new FileOutputStream( updatedFile ) ) + { + copyStream( in, fos ); + } + return LicenseDownloadResult.success( updatedFile ); + + } + else + { + return LicenseDownloadResult.failure( "'" + licenseUrlString + "' returned no body." ); + } } - } - - try ( InputStream licenseInputStream = connection.getInputStream() ) - { - File updatedFile = updateFileExtension( outputFile, connection.getContentType() ); - try ( FileOutputStream fos = new FileOutputStream( updatedFile ) ) - { - copyStream( licenseInputStream, fos ); - } - return updatedFile; - } - } /** @@ -127,24 +150,6 @@ private static void copyStream( InputStream inStream, OutputStream outStream ) } } - private static URLConnection newConnection( String url, String loginPassword ) - throws IOException - { - - URL licenseUrl = new URL( url ); - URLConnection connection = licenseUrl.openConnection(); - - if ( loginPassword != null ) - { - connection.setRequestProperty( "Proxy-Authorization", "Basic " + loginPassword.trim() ); - } - connection.setConnectTimeout( DEFAULT_CONNECTION_TIMEOUT ); - connection.setReadTimeout( DEFAULT_CONNECTION_TIMEOUT ); - - return connection; - - } - private static File updateFileExtension( File outputFile, String mimeType ) { final String realExtension = getFileExtension( mimeType ); @@ -185,4 +190,55 @@ private static String getFileExtension( String mimeType ) return null; } + @Override + public void close() + throws Exception + { + client.close(); + } + + /** + * A result of a license download operation. + * + * @since 1.18 + */ + public static class LicenseDownloadResult + { + public static LicenseDownloadResult success( File file ) + { + return new LicenseDownloadResult( file, null ); + } + + public static LicenseDownloadResult failure( String errorMessage ) + { + return new LicenseDownloadResult( null, errorMessage ); + } + + private LicenseDownloadResult( File file, String errorMessage ) + { + super(); + this.file = file; + this.errorMessage = errorMessage; + } + + private final File file; + + private final String errorMessage; + + public File getFile() + { + return file; + } + + public String getErrorMessage() + { + return errorMessage; + } + + public boolean isSuccess() + { + return errorMessage == null; + } + } + }