org.codehaus.mojo.license.DefaultThirdPartyTool.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.mojo.license.DefaultThirdPartyTool.java

Source

/**
 * Copyright 2010-2013 The Kuali Foundation
 *
 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 *
 * 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.
 */
package org.codehaus.mojo.license;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
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;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.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.MavenProjectBuilder;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.mojo.license.model.LicenseMap;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.logging.Logger;

/**
 * Default implementation of the third party tool.
 *
 * @author <a href="mailto:tchemit@codelutin.com">Tony Chemit</a>
 * @version $Id: DefaultThirdPartyTool.java 14410 2011-08-10 20:54:51Z tchemit $
 * @plexus.component role="org.codehaus.mojo.license.ThirdPartyTool" role-hint="default"
 */
public class DefaultThirdPartyTool extends AbstractLogEnabled implements ThirdPartyTool {

    public static final String DESCRIPTOR_CLASSIFIER = "third-party";

    public static final String DESCRIPTOR_TYPE = "properties";

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

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

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

    /**
     * Project builder.
     *
     * @plexus.requirement
     */
    private MavenProjectBuilder mavenProjectBuilder;

    /**
     * Maven ProjectHelper.
     *
     * @plexus.requirement
     */
    private MavenProjectHelper projectHelper;

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

    /**
     * {@inheritDoc}
     */
    @Override
    public void attachThirdPartyDescriptor(MavenProject project, File file) {

        projectHelper.attachArtifact(project, DESCRIPTOR_TYPE, DESCRIPTOR_CLASSIFIER, file);
    }

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

        Logger log = getLogger();

        // get unsafe dependencies (says with no license)
        SortedSet<MavenProject> unsafeDependencies = licenseMap.get(LicenseMap.getUnknownLicenseMessage());

        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}
     */
    @Override
    public SortedProperties loadThirdPartyDescriptorsForUnsafeMapping(String encoding,
            Collection<MavenProject> projects, SortedSet<MavenProject> unsafeDependencies, LicenseMap licenseMap,
            ArtifactRepository localRepository, List<ArtifactRepository> remoteRepositories)
            throws ThirdPartyToolException, IOException {

        SortedProperties result = new SortedProperties(encoding);
        Map<String, MavenProject> unsafeProjects = new HashMap<String, MavenProject>();
        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().debug("Load missing file " + thirdPartyDescriptor);

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

                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);
                    }
                }
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public File resolvThirdPartyDescriptor(MavenProject project, ArtifactRepository localRepository,
            List<ArtifactRepository> 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}
     */
    @Override
    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}
     */
    @Override
    public void addLicense(LicenseMap licenseMap, MavenProject project, License license) {
        addLicense(licenseMap, project, Arrays.asList(license));
    }

    /**
     * This does some cleanup, and basic safety checks on the license objects passed in<br>
     * 1 - Ignore system scoped dependencies<br>
     * 2 - If there are no licenses declared, use "Unknown License"<br>
     * 3 - If there is no name declared, but there is a url, use the url as a key<br>
     * 4 - If there is no name or url, use "Unknown License" as a key<br>
     */
    @Override
    public void addLicense(LicenseMap licenseMap, MavenProject project, List<?> licenses) {

        if (Artifact.SCOPE_SYSTEM.equals(project.getArtifact().getScope())) {
            // Do not deal with system scope dependencies
            return;
        }

        if (CollectionUtils.isEmpty(licenses)) {
            // No natively declared license was expressed in the pom for this dependency
            // Really funky use of "put"
            // This adds the dependency to the list of dependencies with an unknown license
            licenseMap.put(LicenseMap.getUnknownLicenseMessage(), project);
            return;
        }

        for (Object o : licenses) {
            String id = MojoHelper.getArtifactId(project.getArtifact());
            if (o == null) {
                getLogger().warn("could not acquire a 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().debug("No license name defined for " + id);
                licenseKey = license.getUrl();
            }

            if (StringUtils.isEmpty(licenseKey)) {
                getLogger().debug("No license url defined for " + id);
                licenseKey = LicenseMap.getUnknownLicenseMessage();
            }

            // Really funky use of "put"
            // This adds the dependency to the list of dependencies with this license
            licenseMap.put(licenseKey, project);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void mergeLicenses(LicenseMap licenseMap, String... licenses) {
        if (licenses.length == 0) {
            return;
        }

        String mainLicense = licenses[0].trim();
        SortedSet<MavenProject> mainSet = licenseMap.get(mainLicense);
        if (mainSet == null) {
            getLogger().debug("No license [" + mainLicense + "] found, will create it.");
            mainSet = new TreeSet<MavenProject>(projectComparator);
            licenseMap.put(mainLicense, mainSet);
        }
        int size = licenses.length;
        for (int i = 1; i < size; i++) {
            String license = licenses[i].trim();
            SortedSet<MavenProject> set = licenseMap.get(license);
            if (set == null) {
                getLogger().debug("No license [" + license + "] found, skip this merge.");
                continue;
            }
            getLogger().debug("Merge license [" + license + "] (" + set.size() + " depedencies).");
            mainSet.addAll(set);
            set.clear();
            licenseMap.remove(license);
        }
    }

    protected Properties getProperties(String encoding, File file) throws IOException {
        if (!file.exists()) {
            return new Properties();
        }
        // Load our properties file that maps Maven GAV's to a license
        SortedProperties properties = new SortedProperties(encoding);
        getLogger().debug("Loading " + file);
        properties.load(file);
        return properties;
    }

    protected void handleStuff(Properties customMappings, SortedMap<String, MavenProject> artifactCache) {
        // Store any custom mappings that are not used by this project
        List<String> unusedDependencies = new ArrayList<String>();

        // If the custom mapping file contains GAV entries with type+classifier, remove type+classifier
        // A simple GAV is enough to figure out appropriate licensing
        Map<String, String> migrateKeys = migrateCustomMappingKeys(customMappings.stringPropertyNames());
        for (String id : migrateKeys.keySet()) {
            String migratedId = migrateKeys.get(id);

            MavenProject project = artifactCache.get(migratedId);
            if (project == null) {
                // Now we are sure this is an unused dependency
                // Add this GAV as one that we don't care about for this project
                unusedDependencies.add(id);
            } else {
                if (!id.equals(migratedId)) {

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

        if (!unusedDependencies.isEmpty()) {
            // there are some unused dependencies in the custom mappings file, remove them
            for (String id : unusedDependencies) {
                getLogger().debug("dependency [" + id + "] does not exist in this project");
                // Remove it from the custom mappings file since we don't care about it for this project
                customMappings.remove(id);
            }
        }

    }

    protected void handleUnsafeDependencies(Set<MavenProject> deps, Properties mappings, LicenseMap licenseMap) {
        Iterator<MavenProject> itr = deps.iterator();
        while (itr.hasNext()) {
            MavenProject dep = itr.next();
            String license = getLicense(dep, mappings);
            if (!StringUtils.isBlank(license)) {
                addLicense(licenseMap, dep, license);
                itr.remove();
            } else {
                getLogger().debug(MojoHelper.getArtifactName(dep) + " " + license);
            }
        }
    }

    protected String getLicense(MavenProject d, Properties mappings) {
        String groupId = d.getGroupId();
        String artifactId = d.getArtifactId();
        String version = d.getVersion();

        // Exact match
        String id1 = groupId + "--" + artifactId + "--" + version;
        // Match on groupId + artifactId
        String id2 = groupId + "--" + artifactId;
        // Match on groupId
        String id3 = groupId;

        String value1 = mappings.getProperty(id1);
        String value2 = mappings.getProperty(id2);
        String value3 = mappings.getProperty(id3);

        // Return the license, starting with the most specific, progressing to the least specific
        if (!StringUtils.isBlank(value1)) {
            return value1;
        } else if (!StringUtils.isBlank(value2)) {
            return value2;
        } else if (!StringUtils.isBlank(value3)) {
            return value3;
        } else {
            return null;
        }
    }

    /**
     *
     */
    @Override
    public SortedProperties loadUnsafeMapping(LicenseMap licenseMap, SortedMap<String, MavenProject> artifactCache,
            String encoding, File missingFile) throws IOException {

        // This is the list of dependencies with no declared license info in their pom's
        SortedSet<MavenProject> unsafeDependencies = licenseMap.get(LicenseMap.getUnknownLicenseMessage());

        // Load our properties file that maps Maven GAV's to a license
        Properties customMappings = getProperties(encoding, missingFile);

        // Update licenseMap with info from customMappings
        handleUnsafeDependencies(unsafeDependencies, customMappings, licenseMap);

        // Return customMappings
        return new SortedProperties(customMappings);
    }

    // ----------------------------------------------------------------------
    // 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<ArtifactRepository> 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 final Pattern GAV_PLUS_TYPE_PATTERN = Pattern.compile("(.+)--(.+)--(.+)--(.+)");

    private final Pattern GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN = Pattern.compile("(.+)--(.+)--(.+)--(.+)--(.+)");

    private Map<String, String> migrateCustomMappingKeys(Set<String> customMappingKeys) {
        Map<String, String> migrateKeys = new HashMap<String, String>();
        for (String id : customMappingKeys) {
            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;
    }
}