Author: ltheussl Date: Fri May 19 14:55:30 2006 New Revision: 407919 URL: http://svn.apache.org/viewvc?rev=407919&view=rev Log: PR: MAVEN-121, MAVEN-1080, MAVEN-1648 Download md5 sums of artifacts and verify checksums.
Modified: maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/ChecksumVerificationException.java maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/DependencyVerifier.java Modified: maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/ChecksumVerificationException.java URL: http://svn.apache.org/viewvc/maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/ChecksumVerificationException.java?rev=407919&r1=407918&r2=407919&view=diff ============================================================================== --- maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/ChecksumVerificationException.java (original) +++ maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/ChecksumVerificationException.java Fri May 19 14:55:30 2006 @@ -36,4 +36,16 @@ { super( message ); } + + /** + * Constructs a ChecksumVerificationException with the specified detail message + * and the causing Throwable. + * @param message Detailed message. + * @param cause The Throwable. + */ + public ChecksumVerificationException( String message, Throwable cause ) + { + super( message, cause ); + } + } Modified: maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/DependencyVerifier.java URL: http://svn.apache.org/viewvc/maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/DependencyVerifier.java?rev=407919&r1=407918&r2=407919&view=diff ============================================================================== --- maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/DependencyVerifier.java (original) +++ maven/maven-1/core/trunk/src/java/org/apache/maven/verifier/DependencyVerifier.java Fri May 19 14:55:30 2006 @@ -18,11 +18,13 @@ */ import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.security.NoSuchAlgorithmException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -35,12 +37,16 @@ import org.apache.maven.util.ConsoleDownloadMeter; import org.apache.maven.wagon.ConnectionException; import org.apache.maven.wagon.ResourceDoesNotExistException; +import org.apache.maven.wagon.authorization.AuthorizationException; import org.apache.maven.wagon.Wagon; +import org.apache.maven.wagon.TransferFailedException; import org.apache.maven.wagon.events.TransferListener; +import org.apache.maven.wagon.observers.ChecksumObserver; import org.apache.maven.wagon.providers.file.FileWagon; import org.apache.maven.wagon.providers.http.HttpWagon; import org.apache.maven.wagon.proxy.ProxyInfo; import org.apache.maven.wagon.repository.Repository; +import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.StringUtils; /** @@ -92,11 +98,9 @@ throws RepoConfigException, UnsatisfiedDependencyException, ChecksumVerificationException - { localRepositoryVerifier.verifyLocalRepository(); satisfyDependencies(); - //verifyDependencies(); } /** @@ -116,9 +120,10 @@ * not then download them. * * @throws UnsatisfiedDependencyException If there are unsatisfied dependencies. + * @throws ChecksumVerificationException If checksums don't match. */ private void satisfyDependencies() - throws UnsatisfiedDependencyException + throws UnsatisfiedDependencyException, ChecksumVerificationException { // Is the remote repository enabled? boolean remoteRepoEnabled = getProject().getContext().getRemoteRepositoryEnabled().booleanValue(); @@ -256,8 +261,10 @@ /** * Try and retrieve the dependencies from the remote repository in * order to satisfy the dependencies of the project. + * @throws ChecksumVerificationException If checksums don't match. */ private void getDependencies() + throws ChecksumVerificationException { log.debug("Getting failed dependencies: " + failedDependencies); for ( Iterator i = failedDependencies.iterator(); i.hasNext();) @@ -316,8 +323,10 @@ * and store it at <code>localFile</code> * @param artifact the artifact to retrieve from the repositories. * @return true if the retrieval succeeds, false otherwise. + * @throws ChecksumVerificationException If checksums don't match. */ private boolean getRemoteArtifact( Artifact artifact ) + throws ChecksumVerificationException { boolean artifactFound = false; @@ -384,19 +393,120 @@ wagon.addTransferListener( listener ); } + ChecksumObserver md5ChecksumObserver = null; + ChecksumObserver sha1ChecksumObserver = null; + try + { + md5ChecksumObserver = new ChecksumObserver( "MD5" ); + wagon.addTransferListener( md5ChecksumObserver ); + + sha1ChecksumObserver = new ChecksumObserver( "SHA-1" ); + wagon.addTransferListener( sha1ChecksumObserver ); + } + catch ( NoSuchAlgorithmException e ) + { + throw new ChecksumVerificationException( "Unable to add checksum methods: " + e.getMessage(), e ); + } + + File destination = artifact.getFile(); + String remotePath = artifact.getUrlPath(); + File temp = new File( destination + ".tmp" ); + temp.deleteOnExit(); + try { wagon.connect( repository, proxyInfo ); - wagon.getIfNewer( artifact.getUrlPath(), artifact.getFile(), artifact.getFile().lastModified() ); - // Artifact was found, continue checking additional remote repos (if any) - // in case there is a newer version (i.e. snapshots) in another repo - artifactFound = true; + boolean firstRun = true; + boolean retry = true; - if ( !artifact.isSnapshot() ) + // this will run at most twice. The first time, the firstRun flag is turned off, and if the retry flag + // is set on the first run, it will be turned off and not re-set on the second try. This is because the + // only way the retry flag can be set is if ( firstRun == true ). + while ( firstRun || retry ) { - break; + // reset the retry flag. + retry = false; + + wagon.getIfNewer( remotePath, temp, destination.lastModified() ); + + // keep the checksum files from showing up on the download monitor... + if ( listener != null ) + { + wagon.removeTransferListener( listener ); + } + + // try to verify the MD5 checksum for this file. + try + { + verifyChecksum( md5ChecksumObserver, destination, temp, remotePath, ".md5", wagon ); + } + catch ( ChecksumVerificationException e ) + { + // if we catch a ChecksumVerificationException, it means the transfer/read succeeded, but the checksum + // doesn't match. This could be a problem with the server (ibiblio HTTP-200 error page), so we'll + // try this up to two times. On the second try, we'll handle it as a bona-fide error, based on the + // repository's checksum checking policy. + if ( firstRun ) + { + log.warn( "*** CHECKSUM FAILED - " + e.getMessage() + " - RETRYING" ); + retry = true; + } + else + { + throw new ChecksumVerificationException( e.getMessage(), e.getCause() ); + } + } + catch ( ResourceDoesNotExistException md5TryException ) + { + log.debug( "MD5 not found, trying SHA1", md5TryException ); + + // if this IS NOT a ChecksumVerificationException, it was a problem with transfer/read of the checksum + // file...we'll try again with the SHA-1 checksum. + try + { + verifyChecksum( sha1ChecksumObserver, destination, temp, remotePath, ".sha1", wagon ); + } + catch ( ChecksumVerificationException e ) + { + // if we also fail to verify based on the SHA-1 checksum, and the checksum transfer/read + // succeeded, then we need to determine whether to retry or handle it as a failure. + if ( firstRun ) + { + retry = true; + } + else + { + throw new ChecksumVerificationException( e.getMessage(), e.getCause() ); + } + } + catch ( ResourceDoesNotExistException sha1TryException ) + { + // this was a failed transfer, and we don't want to retry. + throw new ChecksumVerificationException( "Error retrieving checksum file for " + remotePath, sha1TryException ); + } + } + + + // Artifact was found, continue checking additional remote repos (if any) + // in case there is a newer version (i.e. snapshots) in another repo + artifactFound = true; + + if ( !artifact.isSnapshot() ) + { + break; + } + + // reinstate the download monitor... + if ( listener != null ) + { + wagon.addTransferListener( listener ); + } + + // unset the firstRun flag, so we don't get caught in an infinite loop... + firstRun = false; } + } catch ( ResourceDoesNotExistException e ) { @@ -441,6 +551,39 @@ log.debug( "Error disconnecting wagon", e ); } } + + if ( !temp.exists() ) + { + log.debug( "Downloaded file does not exist: " + temp ); + artifactFound = false; + } + + // The temporary file is named destination + ".tmp" and is done this way to ensure + // that the temporary file is in the same file system as the destination because the + // File.renameTo operation doesn't really work across file systems. + // So we will attempt to do a File.renameTo for efficiency and atomicity, if this fails + // then we will use a brute force copy and delete the temporary file. + + if ( !temp.renameTo( destination ) ) + { + try + { + FileUtils.copyFile( temp, destination ); + temp.delete(); + } + catch ( IOException e ) + { + log.debug( "Error copying temporary file to the final destination: " + e.getMessage() ); + artifactFound = false; + } + } + + // don't try another repo if artifact has been found + if ( artifactFound ) + { + break; + } + } return artifactFound; @@ -449,6 +592,58 @@ // ---------------------------------------------------------------------- // V E R I F I C A T I O N // ---------------------------------------------------------------------- + + private void verifyChecksum( ChecksumObserver checksumObserver, File destination, File tempDestination, String remotePath, + String checksumFileExtension, Wagon wagon ) + throws ResourceDoesNotExistException, TransferFailedException, AuthorizationException, ChecksumVerificationException + { + try + { + // grab it first, because it's about to change... + String actualChecksum = checksumObserver.getActualChecksum(); + + File tempChecksumFile = new File( tempDestination + checksumFileExtension + ".tmp" ); + tempChecksumFile.deleteOnExit(); + wagon.get( remotePath + checksumFileExtension, tempChecksumFile ); + + String expectedChecksum = FileUtils.fileRead( tempChecksumFile ); + + // remove whitespaces at the end + expectedChecksum = expectedChecksum.trim(); + + // check for 'MD5 (name) = CHECKSUM' + if ( expectedChecksum.startsWith( "MD5" ) ) + { + int lastSpacePos = expectedChecksum.lastIndexOf( ' ' ); + expectedChecksum = expectedChecksum.substring( lastSpacePos + 1 ); + } + else + { + // remove everything after the first space (if available) + int spacePos = expectedChecksum.indexOf( ' ' ); + + if ( spacePos != -1 ) + { + expectedChecksum = expectedChecksum.substring( 0, spacePos ); + } + } + if ( expectedChecksum.equals( actualChecksum ) ) + { + File checksumFile = new File( destination + checksumFileExtension ); + if ( checksumFile.exists() ) checksumFile.delete(); + FileUtils.copyFile( tempChecksumFile, checksumFile ); + } + else + { + throw new ChecksumVerificationException( "Checksum failed on download: local = '" + actualChecksum + + "'; remote = '" + expectedChecksum + "'" ); + } + } + catch ( IOException e ) + { + throw new ChecksumVerificationException( "Invalid checksum file", e ); + } + } }