All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.license.api.DefaultThirdPartyTool Maven / Gradle / Ivy

There is a newer version: 2.4.0
Show newest version
package org.codehaus.mojo.license.api;

/*
 * #%L
 * License Maven Plugin
 * %%
 * Copyright (C) 2011 CodeLutin, Codehaus, Tony Chemit
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.License;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.mojo.license.model.LicenseMap;
import org.codehaus.mojo.license.utils.FileUtil;
import org.codehaus.mojo.license.utils.MojoHelper;
import org.codehaus.mojo.license.utils.SortedProperties;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.logging.Logger;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Default implementation of the third party tool.
 *
 * @author Tony Chemit
 * @version $Id: DefaultThirdPartyTool.java 19147 2013-12-29 14:08:39Z tchemit $
 */
@Component( role = ThirdPartyTool.class, hint = "default" )
public class DefaultThirdPartyTool
    extends AbstractLogEnabled
    implements ThirdPartyTool
{
    /**
     * Classifier of the third-parties descriptor attached to a maven module.
     */
    public static final String DESCRIPTOR_CLASSIFIER = "third-party";

    /**
     * Type of the the third-parties descriptor attached to a maven module.
     */
    public static final String DESCRIPTOR_TYPE = "properties";

    /**
     * Pattern of a GAV plus a type.
     */
    private static final Pattern GAV_PLUS_TYPE_PATTERN = Pattern.compile( "(.+)--(.+)--(.+)--(.+)" );

    /**
     * Pattern of a GAV plus a type plus a classifier.
     */
    private static final Pattern GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN =
        Pattern.compile( "(.+)--(.+)--(.+)--(.+)--(.+)" );

    static final String LICENSE_DB_TYPE = "license.properties";

    // ----------------------------------------------------------------------
    // Components
    // ----------------------------------------------------------------------

    /**
     * The component that is used to resolve additional artifacts required.
     */
    @Requirement
    private ArtifactResolver artifactResolver;

    /**
     * The component used for creating artifact instances.
     */
    @Requirement
    private ArtifactFactory artifactFactory;

    /**
     * Maven ProjectHelper.
     */
    @Requirement
    private MavenProjectHelper projectHelper;

    /**
     * freeMarker helper.
     */
    private FreeMarkerHelper freeMarkerHelper = FreeMarkerHelper.newDefaultHelper();

    /**
     * Maven project comparator.
     */
    private final Comparator projectComparator = MojoHelper.newMavenProjectComparator();

    private boolean verbose;

    /**
     * {@inheritDoc}
     */
    public boolean isVerbose()
    {
        return verbose;
    }

    /**
     * {@inheritDoc}
     */
    public void setVerbose( boolean verbose )
    {
        this.verbose = verbose;
    }

    /**
     * {@inheritDoc}
     */
    public void attachThirdPartyDescriptor( MavenProject project, File file )
    {
        projectHelper.attachArtifact( project, DESCRIPTOR_TYPE, DESCRIPTOR_CLASSIFIER, file );
    }

    /**
     * {@inheritDoc}
     */
    public SortedSet getProjectsWithNoLicense( LicenseMap licenseMap, boolean doLog )
    {

        Logger log = getLogger();

        // get unsafe dependencies (says with no license)
        SortedSet unsafeDependencies = licenseMap.get( LicenseMap.UNKNOWN_LICENSE_MESSAGE );

        if ( doLog )
        {
            if ( CollectionUtils.isEmpty( unsafeDependencies ) )
            {
                log.debug( "There is no dependency with no license from poms." );
            }
            else
            {
                log.debug( "There is " + unsafeDependencies.size() + " dependencies with no license from poms : " );
                for ( MavenProject dep : unsafeDependencies )
                {

                    // no license found for the dependency
                    log.debug( " - " + MojoHelper.getArtifactId( dep.getArtifact() ) );
                }
            }
        }

        return unsafeDependencies;
    }

    /**
     * {@inheritDoc}
     */
    public SortedProperties loadThirdPartyDescriptorsForUnsafeMapping( Set topLevelDependencies,
                                                                       String encoding,
                                                                       Collection projects,
                                                                       SortedSet unsafeDependencies,
                                                                       LicenseMap licenseMap,
                                                                       ArtifactRepository localRepository,
                                                                       List remoteRepositories )
        throws ThirdPartyToolException, IOException
    {

        SortedProperties result = new SortedProperties( encoding );
        Map unsafeProjects = new HashMap();
        for ( MavenProject unsafeDependency : unsafeDependencies )
        {
            String id = MojoHelper.getArtifactId( unsafeDependency.getArtifact() );
            unsafeProjects.put( id, unsafeDependency );
        }

        for ( MavenProject mavenProject : projects )
        {

            if ( CollectionUtils.isEmpty( unsafeDependencies ) )
            {

                // no more unsafe dependencies to find
                break;
            }

            File thirdPartyDescriptor = resolvThirdPartyDescriptor( mavenProject, localRepository, remoteRepositories );

            if ( thirdPartyDescriptor != null && thirdPartyDescriptor.exists() && thirdPartyDescriptor.length() > 0 )
            {

                if ( getLogger().isInfoEnabled() )
                {
                    getLogger().info( "Detects third party descriptor " + thirdPartyDescriptor );
                }

                // there is a third party file detected form the given dependency
                SortedProperties unsafeMappings = new SortedProperties( encoding );

                if ( thirdPartyDescriptor.exists() )
                {

                    getLogger().info( "Load missing file " + thirdPartyDescriptor );

                    // load the missing file
                    unsafeMappings.load( thirdPartyDescriptor );
                }
                resolveUnsafe( unsafeDependencies, licenseMap, unsafeProjects, unsafeMappings, result );
            }

        }
        try
        {
            loadGlobalLicenses( topLevelDependencies, localRepository, remoteRepositories, unsafeDependencies,
                                licenseMap, unsafeProjects, result );
        }
        catch ( ArtifactNotFoundException e )
        {
            throw new ThirdPartyToolException( "Failed to load global licenses", e );
        }
        catch ( ArtifactResolutionException e )
        {
            throw new ThirdPartyToolException( "Failed to load global licenses", e );
        }
        return result;
    }

    private void resolveUnsafe( SortedSet unsafeDependencies, LicenseMap licenseMap,
                                Map unsafeProjects, SortedProperties unsafeMappings,
                                SortedProperties result )
    {
        for ( String id : unsafeProjects.keySet() )
        {

            if ( unsafeMappings.containsKey( id ) )
            {

                String license = (String) unsafeMappings.get( id );
                if ( StringUtils.isEmpty( license ) )
                {

                    // empty license means not fill, skip it
                    continue;
                }

                // found a resolved unsafe dependency in the missing third party file
                MavenProject resolvedProject = unsafeProjects.get( id );
                unsafeDependencies.remove( resolvedProject );

                // push back to
                result.put( id, license.trim() );

                addLicense( licenseMap, resolvedProject, license );
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public File resolvThirdPartyDescriptor( MavenProject project, ArtifactRepository localRepository,
                                            List repositories )
        throws ThirdPartyToolException
    {
        if ( project == null )
        {
            throw new IllegalArgumentException( "The parameter 'project' can not be null" );
        }
        if ( localRepository == null )
        {
            throw new IllegalArgumentException( "The parameter 'localRepository' can not be null" );
        }
        if ( repositories == null )
        {
            throw new IllegalArgumentException( "The parameter 'remoteArtifactRepositories' can not be null" );
        }

        try
        {
            return resolveThirdPartyDescriptor( project, localRepository, repositories );
        }
        catch ( ArtifactNotFoundException e )
        {
            getLogger().debug( "ArtifactNotFoundException: Unable to locate third party descriptor: " + e );
            return null;
        }
        catch ( ArtifactResolutionException e )
        {
            throw new ThirdPartyToolException(
                "ArtifactResolutionException: Unable to locate third party descriptor: " + e.getMessage(), e );
        }
        catch ( IOException e )
        {
            throw new ThirdPartyToolException(
                "IOException: Unable to locate third party descriptor: " + e.getMessage(), e );
        }
    }

    /**
     * {@inheritDoc}
     */
    public void addLicense( LicenseMap licenseMap, MavenProject project, String licenseName )
    {
        License license = new License();
        license.setName( licenseName.trim() );
        license.setUrl( licenseName.trim() );
        addLicense( licenseMap, project, license );
    }

    /**
     * {@inheritDoc}
     */
    public void addLicense( LicenseMap licenseMap, MavenProject project, License license )
    {
        addLicense( licenseMap, project, Arrays.asList( license ) );
    }

    /**
     * {@inheritDoc}
     */
    public void addLicense( LicenseMap licenseMap, MavenProject project, List licenses )
    {

        if ( Artifact.SCOPE_SYSTEM.equals( project.getArtifact().getScope() ) )
        {

            // do NOT treat system dependency
            return;
        }

        if ( CollectionUtils.isEmpty( licenses ) )
        {

            // no license found for the dependency
            licenseMap.put( LicenseMap.UNKNOWN_LICENSE_MESSAGE, project );
            return;
        }

        for ( Object o : licenses )
        {
            String id = MojoHelper.getArtifactId( project.getArtifact() );
            if ( o == null )
            {
                getLogger().warn( "could not acquire the license for " + id );
                continue;
            }
            License license = (License) o;
            String licenseKey = license.getName();

            // tchemit 2010-08-29 Ano #816 Check if the License object is well formed

            if ( StringUtils.isEmpty( license.getName() ) )
            {
                getLogger().warn( "No license name defined for " + id );
                licenseKey = license.getUrl();
            }

            if ( StringUtils.isEmpty( licenseKey ) )
            {
                getLogger().warn( "No license url defined for " + id );
                licenseKey = LicenseMap.UNKNOWN_LICENSE_MESSAGE;
            }
            licenseMap.put( licenseKey, project );
        }
    }

    /**
     * {@inheritDoc}
     */
    public void mergeLicenses( LicenseMap licenseMap, String mainLicense, Set licenses )
    {

        if ( licenses.isEmpty() )
        {

            // nothing to merge, is this can really happen ?
            return;
        }

        SortedSet mainSet = licenseMap.get( mainLicense );
        if ( mainSet == null )
        {
            if ( isVerbose() )
            {
                getLogger().warn( "No license [" + mainLicense + "] found, will create it." );
            }
            mainSet = new TreeSet( projectComparator );
            licenseMap.put( mainLicense, mainSet );
        }
        for ( String license : licenses )
        {
            SortedSet set = licenseMap.get( license );
            if ( set == null )
            {
                if ( isVerbose() )
                {
                    getLogger().warn( "No license [" + license + "] found, skip the merge to [" + mainLicense + "]" );
                }
                continue;
            }
            if ( isVerbose() )
            {
                getLogger().info(
                    "Merge license [" + license + "] to [" + mainLicense + "] (" + set.size() + " dependencies)." );
            }
            mainSet.addAll( set );
            set.clear();
            licenseMap.remove( license );
        }
    }

    /**
     * {@inheritDoc}
     */
    public SortedProperties loadUnsafeMapping( LicenseMap licenseMap, SortedMap artifactCache,
                                               String encoding, File missingFile )
        throws IOException
    {
        SortedSet unsafeDependencies = getProjectsWithNoLicense( licenseMap, false );

        SortedProperties unsafeMappings = new SortedProperties( encoding );

        if ( missingFile.exists() )
        {
            // there is some unsafe dependencies

            getLogger().info( "Load missing file " + missingFile );

            // load the missing file
            unsafeMappings.load( missingFile );
        }

        // get from the missing file, all unknown dependencies
        List unknownDependenciesId = new ArrayList();

        // coming from maven-license-plugin, we used the full g/a/v/c/t. Now we remove classifier and type
        // since GAV is good enough to qualify a license of any artifact of it...
        Map migrateKeys = migrateMissingFileKeys( unsafeMappings.keySet() );

        for ( Object o : migrateKeys.keySet() )
        {
            String id = (String) o;
            String migratedId = migrateKeys.get( id );

            MavenProject project = artifactCache.get( migratedId );
            if ( project == null )
            {
                // now we are sure this is a unknown dependency
                unknownDependenciesId.add( id );
            }
            else
            {
                if ( !id.equals( migratedId ) )
                {

                    // migrates id to migratedId
                    getLogger().info( "Migrates [" + id + "] to [" + migratedId + "] in the missing file." );
                    Object value = unsafeMappings.get( id );
                    unsafeMappings.remove( id );
                    unsafeMappings.put( migratedId, value );
                }
            }
        }

        if ( !unknownDependenciesId.isEmpty() )
        {

            // there is some unknown dependencies in the missing file, remove them
            for ( String id : unknownDependenciesId )
            {
                getLogger().warn(
                    "dependency [" + id + "] does not exist in project, remove it from the missing file." );
                unsafeMappings.remove( id );
            }

            unknownDependenciesId.clear();
        }

        // push back loaded dependencies
        for ( Object o : unsafeMappings.keySet() )
        {
            String id = (String) o;

            MavenProject project = artifactCache.get( id );
            if ( project == null )
            {
                getLogger().warn( "dependency [" + id + "] does not exist in project." );
                continue;
            }

            String license = (String) unsafeMappings.get( id );
            if ( StringUtils.isEmpty( license ) )
            {

                // empty license means not fill, skip it
                continue;
            }

            // add license in map
            addLicense( licenseMap, project, license );

            // remove unknown license
            unsafeDependencies.remove( project );
        }

        if ( unsafeDependencies.isEmpty() )
        {

            // no more unknown license in map
            licenseMap.remove( LicenseMap.UNKNOWN_LICENSE_MESSAGE );
        }
        else
        {

            // add a "with no value license" for missing dependencies
            for ( MavenProject project : unsafeDependencies )
            {
                String id = MojoHelper.getArtifactId( project.getArtifact() );
                if ( getLogger().isDebugEnabled() )
                {
                    getLogger().debug( "dependency [" + id + "] has no license, add it in the missing file." );
                }
                unsafeMappings.setProperty( id, "" );
            }
        }
        return unsafeMappings;
    }

    /**
     * {@inheritDoc}
     */
    public void writeThirdPartyFile( LicenseMap licenseMap, File thirdPartyFile, boolean verbose, String encoding,
                                     String lineFormat )
        throws IOException
    {

        Logger log = getLogger();

        Map properties = new HashMap();
        properties.put( "licenseMap", licenseMap.entrySet() );
        properties.put( "dependencyMap", licenseMap.toDependencyMap().entrySet() );
        String content = freeMarkerHelper.renderTemplate( lineFormat, properties );

        log.info( "Writing third-party file to " + thirdPartyFile );
        if ( verbose )
        {
            log.info( content );
        }

        FileUtil.writeString( thirdPartyFile, content, encoding );

    }

    /**
     * {@inheritDoc}
     */
    public void writeBundleThirdPartyFile( File thirdPartyFile, File outputDirectory, String bundleThirdPartyPath )
        throws IOException
    {

        // creates the bundled license file
        File bundleTarget = FileUtil.getFile( outputDirectory, bundleThirdPartyPath );
        getLogger().info( "Writing bundled third-party file to " + bundleTarget );
        FileUtil.copyFile( thirdPartyFile, bundleTarget );
    }

    public void loadGlobalLicenses( Set dependencies, ArtifactRepository localRepository,
                                    List repositories, SortedSet unsafeDependencies,
                                    LicenseMap licenseMap, Map unsafeProjects,
                                    SortedProperties result )
        throws IOException, ArtifactNotFoundException, ArtifactResolutionException
    {
        for ( Artifact dep : dependencies )
        {
            if ( LICENSE_DB_TYPE.equals( dep.getType() ) )
            {
                loadOneGlobalSet( unsafeDependencies, licenseMap, unsafeProjects, dep, localRepository, repositories,
                                  result );
            }
        }
    }

    private void loadOneGlobalSet( SortedSet unsafeDependencies, LicenseMap licenseMap,
                                   Map unsafeProjects, Artifact dep,
                                   ArtifactRepository localRepository, List repositories,
                                   SortedProperties result )
        throws IOException, ArtifactNotFoundException, ArtifactResolutionException
    {
        artifactResolver.resolve( dep, repositories, localRepository );
        File propFile = dep.getFile();
        getLogger().info(
            String.format( "Loading global license map from %s: %s", dep.toString(), propFile.getAbsolutePath() ) );
        SortedProperties props = new SortedProperties( "utf-8" );
        InputStream propStream = null;

        try
        {
            propStream = new FileInputStream( propFile );
            props.load( propStream );
        }
        finally
        {
            IOUtils.closeQuietly( propStream );
        }

        for ( Object keyObj : props.keySet() )
        {
            String key = (String) keyObj;
            String val = (String) props.get( key );
            result.put( key, val );
        }

        resolveUnsafe( unsafeDependencies, licenseMap, unsafeProjects, props, result );
    }

    // ----------------------------------------------------------------------
    // Private methods
    // ----------------------------------------------------------------------

    /**
     * @param project         not null
     * @param localRepository not null
     * @param repositories    not null
     * @return the resolved site descriptor
     * @throws IOException                 if any
     * @throws ArtifactResolutionException if any
     * @throws ArtifactNotFoundException   if any
     */
    private File resolveThirdPartyDescriptor( MavenProject project, ArtifactRepository localRepository,
                                              List repositories )
        throws IOException, ArtifactResolutionException, ArtifactNotFoundException
    {
        File result;

        // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1?
        Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(),
                                                                          project.getVersion(), DESCRIPTOR_TYPE,
                                                                          DESCRIPTOR_CLASSIFIER );
        try
        {
            artifactResolver.resolve( artifact, repositories, localRepository );

            result = artifact.getFile();

            // we use zero length files to avoid re-resolution (see below)
            if ( result.length() == 0 )
            {
                getLogger().debug( "Skipped third party descriptor" );
            }
        }
        catch ( ArtifactNotFoundException e )
        {
            getLogger().debug( "Unable to locate third party files descriptor : " + e );

            // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote
            // repository, because the parent was already released (and snapshots are updated automatically if changed)
            result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );

            FileUtil.createNewFile( result );
        }

        return result;
    }

    private Map migrateMissingFileKeys( Set missingFileKeys )
    {
        Map migrateKeys = new HashMap();
        for ( Object object : missingFileKeys )
        {
            String id = (String) object;
            Matcher matcher;

            String newId = id;
            matcher = GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN.matcher( id );
            if ( matcher.matches() )
            {
                newId = matcher.group( 1 ) + "--" + matcher.group( 2 ) + "--" + matcher.group( 3 );

            }
            else
            {
                matcher = GAV_PLUS_TYPE_PATTERN.matcher( id );
                if ( matcher.matches() )
                {
                    newId = matcher.group( 1 ) + "--" + matcher.group( 2 ) + "--" + matcher.group( 3 );

                }
            }
            migrateKeys.put( id, newId );
        }
        return migrateKeys;
    }
}