org.codehaus.mojo.license.api.DefaultThirdPartyTool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of license-maven-plugin Show documentation
Show all versions of license-maven-plugin Show documentation
Maven plugin to download and collect license files from project dependencies.
The 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 javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.License;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.mojo.license.LicenseMojoUtils;
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.mojo.license.utils.UrlRequester;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.artifact.DefaultArtifactType;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.transfer.ArtifactNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of the third party tool.
*
* @author Tony Chemit
* @version $Id$
*/
@Named
@Singleton
public class DefaultThirdPartyTool implements ThirdPartyTool {
private static final Logger LOG = LoggerFactory.getLogger(DefaultThirdPartyTool.class);
/**
* Classifier of the third-parties descriptor attached to a maven module.
*/
private static final String DESCRIPTOR_CLASSIFIER = "third-party";
/**
* Type of the the third-parties descriptor attached to a maven module.
*/
private 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("(.+)--(.+)--(.+)--(.+)--(.+)");
public static final String LICENSE_DB_TYPE = "license.properties";
// ----------------------------------------------------------------------
// Components
// ----------------------------------------------------------------------
/**
* Maven Artifact Resolver repoSystem
*/
private final RepositorySystem aetherRepoSystem;
private final Provider mavenSessionProvider;
/**
* Maven ProjectHelper.
*/
private final MavenProjectHelper projectHelper;
/**
* freeMarker helper.
*/
private final FreeMarkerHelper freeMarkerHelper = FreeMarkerHelper.newDefaultHelper();
/**
* Maven project comparator.
*/
private final Comparator projectComparator = MojoHelper.newMavenProjectComparator();
private boolean verbose;
@Inject
DefaultThirdPartyTool(
RepositorySystem aetherRepoSystem,
Provider mavenSessionProvider,
MavenProjectHelper projectHelper) {
this.aetherRepoSystem = aetherRepoSystem;
this.mavenSessionProvider = mavenSessionProvider;
this.projectHelper = projectHelper;
}
/**
* {@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) {
// get unsafe dependencies (says with no license)
SortedSet unsafeDependencies = licenseMap.get(LicenseMap.UNKNOWN_LICENSE_MESSAGE);
if (CollectionUtils.isEmpty(unsafeDependencies)) {
LOG.debug("There is no dependency with no license from poms.");
} else {
if (LOG.isDebugEnabled()) {
boolean plural = unsafeDependencies.size() > 1;
String message = String.format(
"There %s %d %s with no license from poms :",
plural ? "are" : "is", unsafeDependencies.size(), plural ? "dependencies" : "dependency");
LOG.debug(message);
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,
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, remoteRepositories);
if (thirdPartyDescriptor != null && thirdPartyDescriptor.exists() && thirdPartyDescriptor.length() > 0) {
LOG.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()) {
LOG.info("Load missing file {}", thirdPartyDescriptor);
// load the missing file
unsafeMappings.load(thirdPartyDescriptor);
}
resolveUnsafe(unsafeDependencies, licenseMap, unsafeProjects, unsafeMappings, result);
}
}
try {
loadGlobalLicenses(
topLevelDependencies, remoteRepositories, unsafeDependencies, licenseMap, unsafeProjects, result);
} catch (ArtifactNotFoundException | 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, List remoteRepositories)
throws ThirdPartyToolException {
if (project == null) {
throw new IllegalArgumentException("The parameter 'project' can not be null");
}
if (remoteRepositories == null) {
throw new IllegalArgumentException("The parameter 'remoteRepositories' can not be null");
}
try {
return resolveThirdPartyDescriptor(project, remoteRepositories);
} catch (ArtifactResolutionException e) {
throw new ThirdPartyToolException(
"ArtifactResolutionException: Unable to locate third party descriptor: " + e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
public void addLicense(LicenseMap licenseMap, MavenProject project, String... licenseNames) {
List licenses = new ArrayList<>();
for (String licenseName : licenseNames) {
License license = new License();
license.setName(licenseName.trim());
license.setUrl(licenseName.trim());
licenses.add(license);
}
addLicense(licenseMap, project, licenses);
}
/**
* {@inheritDoc}
*/
public void addLicense(LicenseMap licenseMap, MavenProject project, License license) {
addLicense(licenseMap, project, Collections.singletonList(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) {
LOG.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())) {
LOG.warn("The license for {} has no name (but exist)", id);
licenseKey = license.getUrl();
}
if (StringUtils.isEmpty(licenseKey)) {
LOG.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()) {
LOG.warn("No license [{}] found, will create it.", mainLicense);
}
mainSet = new TreeSet<>(projectComparator);
licenseMap.put(mainLicense, mainSet);
}
for (String license : licenses) {
SortedSet set = licenseMap.get(license);
if (set == null) {
if (isVerbose()) {
LOG.warn("No license [{}] found, skip the merge to [{}]", license, mainLicense);
}
continue;
}
if (isVerbose()) {
LOG.info("Merge license [{}] to [{}] ({} dependencies).", license, mainLicense, set.size());
}
mainSet.addAll(set);
set.clear();
licenseMap.remove(license);
}
}
/**
* {@inheritDoc}
*/
public SortedProperties loadUnsafeMapping(
LicenseMap licenseMap,
SortedMap artifactCache,
String encoding,
File missingFile,
String missingFileUrl)
throws IOException {
Map snapshots = new HashMap<>();
synchronized (artifactCache) {
// find snapshot dependencies
for (Map.Entry entry : artifactCache.entrySet()) {
MavenProject mavenProject = entry.getValue();
if (mavenProject.getVersion().endsWith(Artifact.SNAPSHOT_VERSION)) {
snapshots.put(entry.getKey(), mavenProject);
}
}
}
for (Map.Entry snapshot : snapshots.entrySet()) {
// remove invalid entries, which contain timestamps in key
artifactCache.remove(snapshot.getKey());
// put corrected keys/entries into artifact cache
MavenProject mavenProject = snapshot.getValue();
String id = MojoHelper.getArtifactId(mavenProject.getArtifact());
artifactCache.put(id, mavenProject);
}
SortedSet unsafeDependencies = getProjectsWithNoLicense(licenseMap);
SortedProperties unsafeMappings = new SortedProperties(encoding);
if (missingFile.exists()) {
// there is some unsafe dependencies
LOG.info("Load missingFile {}", missingFile);
// load the missing file
unsafeMappings.load(missingFile);
}
if (UrlRequester.isStringUrl(missingFileUrl)) {
String httpRequestResult = UrlRequester.getFromUrl(missingFileUrl);
unsafeMappings.load(new ByteArrayInputStream(httpRequestResult.getBytes()));
}
// 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
LOG.info("Migrates [{}] to [{}] in the missing file.", id, migratedId);
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) {
LOG.warn("dependency [{}] does not exist in project, remove it from the missing file.", id);
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) {
LOG.warn("dependency [{}] does not exist in project.", id);
continue;
}
String license = (String) unsafeMappings.get(id);
String[] licenses = StringUtils.split(license, '|');
if (ArrayUtils.isEmpty(licenses)) {
// empty license means not fill, skip it
continue;
}
// add license in map
addLicense(licenseMap, project, licenses);
// 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());
LOG.debug("dependency [{}] has no license, add it in the missing file.", id);
unsafeMappings.setProperty(id, "");
}
}
return unsafeMappings;
}
/**
* {@inheritDoc}
*/
public void overrideLicenses(
LicenseMap licenseMap, SortedMap artifactCache, String encoding, String overrideUrl)
throws IOException {
if (LicenseMojoUtils.isValid(overrideUrl)) {
final SortedProperties overrideMappings = new SortedProperties(encoding);
try (Reader reader = new StringReader(UrlRequester.getFromUrl(overrideUrl, encoding))) {
overrideMappings.load(reader);
}
boolean isExternalUrl = UrlRequester.isExternalUrl(overrideUrl);
for (Object o : overrideMappings.keySet()) {
String id = (String) o;
MavenProject project = artifactCache.get(id);
if (project == null) {
// Log at warn for local override files, but at debug for remote (presumably shared) override files.
if (isExternalUrl) {
LOG.debug("dependency [{}] does not exist in project.", id);
} else {
LOG.warn("dependency [{}] does not exist in project.", id);
}
continue;
}
String license = (String) overrideMappings.get(id);
String[] licenses = StringUtils.split(license, '|');
if (ArrayUtils.isEmpty(licenses)) {
// empty license means not fill, skip it
continue;
}
// remove project only removes first occurrence of project from license -> project[] map.
List removedFrom = licenseMap.removeProject(project);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Overriding license(s) for dependency [{}] with [{}], overriden license(s): [{}]",
id,
"(" + StringUtils.join(licenses, ") (") + ")",
"(" + StringUtils.join(removedFrom.toArray(), ") (") + ")");
}
// add licenses to map
addLicense(licenseMap, project, licenses);
}
}
}
/**
* {@inheritDoc}
*/
public void writeThirdPartyFile(
LicenseMap licenseMap, File thirdPartyFile, boolean verbose, String encoding, String lineFormat)
throws IOException {
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.printString(thirdPartyFile, content, encoding);
}
/**
* {@inheritDoc}
*/
public void writeBundleThirdPartyFile(File thirdPartyFile, File outputDirectory, String bundleThirdPartyPath)
throws IOException {
// creates the bundled license file
File bundleTarget = new File(outputDirectory, bundleThirdPartyPath);
LOG.info("Writing bundled third-party file to {}", bundleTarget);
FileUtil.copyFile(thirdPartyFile, bundleTarget);
}
private void loadGlobalLicenses(
Set dependencies,
List remoteRepositories,
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, remoteRepositories, result);
}
}
}
private void loadOneGlobalSet(
SortedSet unsafeDependencies,
LicenseMap licenseMap,
Map unsafeProjects,
Artifact dep,
List remoteRepositories,
SortedProperties result)
throws IOException, ArtifactResolutionException {
File propFile = resolveArtifact(
dep.getGroupId(),
dep.getArtifactId(),
dep.getVersion(),
dep.getType(),
dep.getClassifier(),
remoteRepositories);
LOG.info("Loading global license map from {}: {}", dep.toString(), propFile.getAbsolutePath());
SortedProperties props = new SortedProperties("utf-8");
try (InputStream propStream = Files.newInputStream(propFile.toPath())) {
props.load(propStream);
} catch (IOException e) {
throw new IOException("Unable to load " + propFile.getAbsolutePath(), e);
}
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 remoteRepositories not null
* @return the resolved third party descriptor
* @throws ArtifactResolutionException if any
*/
private File resolveThirdPartyDescriptor(MavenProject project, List remoteRepositories)
throws ArtifactResolutionException {
File result;
try {
result = resolveArtifact(
project.getGroupId(),
project.getArtifactId(),
project.getVersion(),
DESCRIPTOR_TYPE,
DESCRIPTOR_CLASSIFIER,
remoteRepositories);
// we use zero length files to avoid re-resolution (see below)
if (result.length() == 0) {
LOG.debug("Skipped third party descriptor");
}
} catch (ArtifactResolutionException e) {
if (e.getCause() instanceof ArtifactNotFoundException) {
ArtifactNotFoundException artifactNotFoundException = (ArtifactNotFoundException) e.getCause();
LOG.debug("Unable to locate third party files descriptor", artifactNotFoundException);
org.eclipse.aether.artifact.Artifact artifact;
if (artifactNotFoundException.getArtifact() == null) {
artifact = new DefaultArtifact(
project.getGroupId(),
project.getArtifactId(),
DESCRIPTOR_CLASSIFIER,
null,
project.getVersion(),
new DefaultArtifactType(DESCRIPTOR_TYPE));
} else {
artifact = artifactNotFoundException.getArtifact();
}
/*
* 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)
*/
RepositorySystemSession aetherSession =
mavenSessionProvider.get().getRepositorySession();
result = new File(
aetherSession.getLocalRepository().getBasedir(),
aetherSession.getLocalRepositoryManager().getPathForLocalArtifact(artifact));
} else {
throw e;
}
}
return result;
}
public File resolveMissingLicensesDescriptor(
String groupId, String artifactId, String version, List remoteRepositories)
throws IOException, ArtifactResolutionException, ArtifactNotFoundException {
return resolveArtifact(
groupId, artifactId, version, DESCRIPTOR_TYPE, DESCRIPTOR_CLASSIFIER, remoteRepositories);
}
private File resolveArtifact(
String groupId,
String artifactId,
String version,
String type,
String classifier,
List remoteRepositories)
throws ArtifactResolutionException {
org.eclipse.aether.artifact.Artifact artifact2 =
new DefaultArtifact(groupId, artifactId, classifier, null, version, new DefaultArtifactType(type));
ArtifactRequest artifactRequest =
new ArtifactRequest().setArtifact(artifact2).setRepositories(remoteRepositories);
ArtifactResult result =
aetherRepoSystem.resolveArtifact(mavenSessionProvider.get().getRepositorySession(), artifactRequest);
return result.getArtifact().getFile();
}
private Map migrateMissingFileKeys(Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy