Package org.apache.maven.verifier

Source Code of org.apache.maven.verifier.DependencyVerifier$DefaultWagonFactory

package org.apache.maven.verifier;

/* ====================================================================
*   Licensed to the Apache Software Foundation (ASF) under one or more
*   contributor license agreements.  See the NOTICE file distributed with
*   this work for additional information regarding copyright ownership.
*   The ASF licenses this file to You under the Apache License, Version 2.0
*   (the "License"); you may not use this file except in compliance with
*   the License.  You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
* ====================================================================
*/

import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.AbstractMavenComponent;
import org.apache.maven.MavenConstants;
import org.apache.maven.jelly.MavenJellyContext;
import org.apache.maven.project.Project;
import org.apache.maven.repository.Artifact;
import org.apache.maven.util.BootstrapDownloadMeter;
import org.apache.maven.util.ConsoleDownloadMeter;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.authorization.AuthorizationException;
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.providers.ssh.jsch.SftpWagon;
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;

/**
* Make sure that everything that is required for the project to build
* successfully is present. We will start by looking at the dependencies
* and make sure they are all here before trying to compile.
*
* @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
* @author <a href="mailto:vmassol@apache.org">Vincent Massol</a>
*
*
* @todo Separate out the local settings verifier because this only needs to be run
*       once a session, but is currently being run during project verification so
*       this is a big waste in the reactor for example.
*/
public class DependencyVerifier
    extends AbstractMavenComponent
{
    /** LOGGER for debug output */
    private static final Log LOGGER = LogFactory.getLog( DependencyVerifier.class );

    /** List of failed deps. */
    private List failedDependencies;

    /** Local Repository verifier. */
    private LocalSettingsVerifier localRepositoryVerifier;

    private static Set resolvedArtifacts = new HashSet();

    private ProxyInfo proxyInfo = null;

    private TransferListener listener = null;

    /**
     * Default ctor.
     * @param project the project to verify
     */
    public DependencyVerifier( Project project )
    {
        super( project );
        failedDependencies = new ArrayList();
        localRepositoryVerifier = new LocalSettingsVerifier( project );
        MavenJellyContext context = getProject().getContext();

        if ( context.getProxyHost() != null )
        {
            proxyInfo = new ProxyInfo();
            proxyInfo.setHost( context.getProxyHost() );
            try
            {
                proxyInfo.setPort( Integer.valueOf( context.getProxyPort() ).intValue() );
            }
            catch ( NumberFormatException e )
            {
                LOGGER.warn( "Ignoring invalid proxy port: '" + context.getProxyPort() + "'" );
            }
            proxyInfo.setUserName( context.getProxyUserName() );
            proxyInfo.setPassword( context.getProxyPassword() );
            proxyInfo.setNonProxyHosts( (String) context.getVariable( MavenConstants.PROXY_NONPROXYHOSTS ) );
            proxyInfo.setNtlmHost( (String) context.getVariable( MavenConstants.PROXY_NTLM_HOST ) );
            proxyInfo.setNtlmDomain( (String) context.getVariable( MavenConstants.PROXY_NTLM_DOMAIN ) );
        }
        String meterType = (String) context.getVariable( MavenConstants.DOWNLOAD_METER );
        if ( meterType == null )
        {
            meterType = "console";
        }

        if ( "bootstrap".equals( meterType ) )
        {
            listener = new BootstrapDownloadMeter();
        }
        else if ( "console".equals( meterType ) )
        {
            listener = new ConsoleDownloadMeter();
        }
    }

    /**
     * Execute the verification process.
     *
     * @throws RepoConfigException If an error occurs while verifying basic maven settings.
     * @throws UnsatisfiedDependencyException If there are unsatisfied dependencies.
     * @throws ChecksumVerificationException if the download checksum doesn't match the calculated
     */
    public void verify()
        throws RepoConfigException, UnsatisfiedDependencyException, ChecksumVerificationException
    {
        localRepositoryVerifier.verifyLocalRepository();
        satisfyDependencies();
    }

    /**
     * Clear the failed dependencies. Required when reusing the
     * project verifier.
     */
    private void clearFailedDependencies()
    {
        failedDependencies.clear();
    }

    /**
     * Check to see that all dependencies are present and if they are
     * not then download them.
     *
     * @throws UnsatisfiedDependencyException If there are unsatisfied dependencies.
     * @throws ChecksumVerificationException If checksums don't match.
     */
    private void satisfyDependencies()
        throws UnsatisfiedDependencyException, ChecksumVerificationException
    {
        // Is the remote repository enabled?
        boolean remoteRepoEnabled = getProject().getContext().getRemoteRepositoryEnabled().booleanValue();

        // Is the user online?
        boolean online = getProject().getContext().getOnline().booleanValue();

        if ( !remoteRepoEnabled )
        {
            LOGGER.warn( getMessage( "remote.repository.disabled.warning" ) );
        }

        clearFailedDependencies();

        for ( Iterator i = getProject().getArtifacts().iterator(); i.hasNext(); )
        {
            Artifact artifact = (Artifact) i.next();

            String path = artifact.getUrlPath();
            if ( resolvedArtifacts.contains( path ) )
            {
                if ( LOGGER.isDebugEnabled() )
                    LOGGER.debug( "(previously resolved: " + path + ")" );
                continue;
            }
            resolvedArtifacts.add( path );

            // The artifact plain doesn't exist so chalk it up as a failed dependency.
            if ( !artifact.exists() )
            {
                if ( LOGGER.isDebugEnabled() )
                    LOGGER.debug( "Artifact [" + path + "] not found in local repository" );
                failedDependencies.add( artifact );
            }
            else if ( artifact.isSnapshot() && !Artifact.OVERRIDE_PATH.equals( artifact.getOverrideType() ) )
            {
                // The artifact exists but we need to take into account the user
                // being online and whether the artifact is a snapshot. If the user
                // is online then snapshots are added to the list of failed dependencies
                // so that a newer version can be retrieved if one exists. We make
                // an exception when the user is working offline and let them
                // take their chances with a strong warning that they could possibly
                // be using an out-of-date artifact. We don't want to cripple users
                // when working offline.
                if ( online )
                {
                    failedDependencies.add( artifact );
                }
                else
                {
                    LOGGER.warn( getMessage( "offline.snapshot.warning", artifact.getName() ) );
                }
            }
        }

        // If we have any failed dependencies then we will attempt to download
        // them for the user if the remote repository is enabled.
        if ( !failedDependencies.isEmpty() && remoteRepoEnabled && online )
        {
            getDependencies();
        }

        // If we still have failed dependencies after we have tried to
        // satisfy all dependencies then we have a problem. There might
        // also be a problem if the use of the remote repository has
        // been disabled and dependencies just aren't present. In any
        // case we have a problem.
        if ( !failedDependencies.isEmpty() )
        {
            throw new UnsatisfiedDependencyException( createUnsatisfiedDependenciesMessage() );
        }
    }

    /**
     * Create a message for the user stating the dependencies that are unsatisfied.
     *
     * @return The unsatisfied dependency message.
     */
    private String createUnsatisfiedDependenciesMessage()
    {
        StringBuffer message = new StringBuffer();

        if ( failedDependencies.size() == 1 )
        {
            message.append( getMessage( "single.unsatisfied.dependency.error" ) );
        }
        else
        {
            message.append( getMessage( "multiple.unsatisfied.dependency.error" ) );
        }

        message.append( "\n" );

        for ( Iterator i = failedDependencies.iterator(); i.hasNext(); )
        {
            Artifact artifact = (Artifact) i.next();
            message.append( "- " + artifact.getDescription() );

            String overrideType = artifact.getOverrideType();
            if ( overrideType != Artifact.OVERRIDE_NONE )
            {
                if ( Artifact.OVERRIDE_VERSION.equals( overrideType ) )
                {
                    message.append( "; version override doesn't exist: " + artifact.getDependency().getVersion() );
                }
                else if ( Artifact.OVERRIDE_PATH.equals( overrideType ) )
                {
                    message.append( "; path override doesn't exist: " + artifact.getPath() );
                }
            }

            String url = artifact.getDependency().getUrl();
            if ( StringUtils.isNotEmpty( url ) )
            {
                // FIXME: internationalize
                message.append( " (" ).append( "try downloading from " ).append( url ).append( ")" );
            }
            message.append( "\n" );
        }

        return message.toString();
    }

    /**
     *  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
    {
        if ( LOGGER.isDebugEnabled() )
            LOGGER.debug( "Getting failed dependencies: " + failedDependencies );

        // if there are failed dependencies try to indicate for which project:
        if ( failedDependencies.size() > 0 )
        {
            LOGGER.info( getMessage( "satisfy.project.message", getProject().getName() ) );
        }

        Artifact artifact;
        Iterator i = failedDependencies.iterator();
        while ( i.hasNext() )
        {
            artifact = (Artifact) i.next();

            // before we try to download a missing dependency we have to verify
            // that the dependency is not of the type Artifact.OVERRIDE_PATH,
            // in which case it can not be downloaded. Just skip this iteration.
            // Since the dependency won't get removed from the failedDependencies list
            // an error message will be created.
            String overrideType = artifact.getOverrideType();
            if ( Artifact.OVERRIDE_PATH.equals( overrideType ) )
            {
                continue;
            }

            // The directory structure for the project this dependency belongs to
            // may not exists so attempt to create the project directory structure
            // before attempting to download the dependency.
            File directory = artifact.getFile().getParentFile();

            if ( !directory.exists() )
            {
                directory.mkdirs();
            }

            if ( getRemoteArtifact( artifact ) )
            {
                // The dependency has been successfully downloaded so lets remove
                // it from the failed dependency list.
                i.remove();
            }
            else
            {
                if ( artifact.exists() )
                {
                    // The snapshot jar locally exists and not in remote repository
                    //LOGGER.info( getMessage( "not.existing.artifact.in.repo", artifact.getUrlPath() ) );
                    i.remove();
                }
                //                else
                //                {
                //                    LOGGER.error( getMessage( "failed.download.warning", artifact.getName() ) );
                //                }
            }
        }
    }

    /**
     * Retrieve a <code>remoteFile</code> from the maven remote repositories
     * 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;

        int count = 0;

        Iterator i = getProject().getContext().getMavenRepoRemote().iterator();
        while(i.hasNext())
        {
            String remoteRepo = (String) i.next();

            if ( artifact.isSnapshot() && artifact.exists() )
            {
                LOGGER.info( getMessage( "update.message" ) + " " + artifact.getDescription() + " from " + remoteRepo );
            }
            else
            {
                LOGGER
                    .info( getMessage( "download.message" ) + " " + artifact.getDescription() + " from " + remoteRepo );
            }
            //LOGGER.info( "Searching in repository : " + remoteRepo );

            Repository repository = new Repository( "repo" + count++, remoteRepo.trim() );

            final Wagon wagon = new DefaultWagonFactory().getWagon( repository.getProtocol() );

            if ( listener != null )
            {
                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();
            boolean downloaded = false;

            try
            {
                wagon.connect( repository, proxyInfo );

                boolean firstRun = true;
                boolean retry = true;

                // 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 )
                {
                    // reset the retry flag.
                    retry = false;

                    downloaded = wagon.getIfNewer( remotePath, temp, destination.lastModified() );
                    if ( !downloaded && firstRun )
                    {
                        LOGGER.info( getMessage( "skip.download.message" ) );
                    }

                    if ( downloaded )
                    {
                        // 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 )
                            {
                                LOGGER.warn( "*** CHECKSUM FAILED - " + e.getMessage() + " - RETRYING" );
                                retry = true;
                            }
                            else
                            {
                                throw new ChecksumVerificationException( e.getMessage(), e.getCause() );
                            }
                        }
                        catch ( ResourceDoesNotExistException md5TryException )
                        {
                            LOGGER.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 )
            {
                // Multiple repositories may exist, and if the file is not found
                // in just one of them, it's no problem, and we don't want to
                // even print out an error.
                // if it's not found at all, artifactFound will be false, and the
                // build _will_ break, and the user will get an error message
                LOGGER.debug( "File not found on one of the repos", e );
            }
            catch ( Exception e )
            {
                // If there are additional remote repos, then ignore exception
                // as artifact may be found in another remote repo. If there
                // are no more remote repos to check and the artifact wasn't found in
                // a previous remote repo, then artifactFound is false indicating
                // that the artifact could not be found in any of the remote repos
                //
                // arguably, we need to give the user better control (another command-
                // line switch perhaps) of what to do in this case? Maven already has
                // a command-line switch to work in offline mode, but what about when
                // one of two or more remote repos is unavailable? There may be multiple
                // remote repos for redundancy, in which case you probably want the build
                // to continue. There may however be multiple remote repos because some
                // artifacts are on one, and some are on another. In this case, you may
                // want the build to break.
                //
                // print a warning, in any case, so user catches on to mistyped
                // hostnames, or other snafus
                // FIXME: localize this message
                LOGGER.warn( "Error retrieving artifact from [" + repository.getUrl() + "]: " + e );
                LOGGER.debug( "Error details", e );
            }
            finally
            {
                try
                {
                    wagon.disconnect();
                }
                catch ( ConnectionException e )
                {
                    LOGGER.debug( "Error disconnecting wagon", e );
                }
            }

            if ( !temp.exists() && downloaded )
            {
                LOGGER.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 ) && downloaded )
            {
                try
                {
                    FileUtils.copyFile( temp, destination );
                    temp.delete();
                }
                catch ( IOException e )
                {
                    LOGGER.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;
    }

    /**
     * Creates Wagons. Replace it with a IoC container?
     */
    private static class DefaultWagonFactory
    {

        private final Map map = new HashMap();

        public DefaultWagonFactory()
        {
            map.put( "http", HttpWagon.class );
            map.put( "https", HttpWagon.class );
            map.put( "sftp", SftpWagon.class );
            map.put( "file", FileWagon.class );
        }

        public final Wagon getWagon( final String protocol )
        {
            // TODO: don't initialise the wagons all the time - use a session
            Wagon ret;
            final Class aClass = (Class) map.get( protocol );
            if ( aClass == null )
            {
                LOGGER.info( "Unknown protocol: `" + protocol + "'. Trying file wagon" );
                ret = new FileWagon();
            }
            else
            {
                try
                {
                    ret = (Wagon) aClass.newInstance();
                }
                catch ( final Exception e )
                {
                    throw new RuntimeException( e );
                }
            }

            return ret;
        }
    }

    // ----------------------------------------------------------------------
    // V E R I F I C A T I O N
    // ----------------------------------------------------------------------

    /**
     * Rules for verifying the checksum.
     *
     * We attempt to download artifacts and their accompanying md5 checksum
     * files.
     */
    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 );
        }
    }

}
TOP

Related Classes of org.apache.maven.verifier.DependencyVerifier$DefaultWagonFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.