001 /* 002 * #%L 003 * License Maven Plugin 004 * 005 * $Id: DefaultThirdPartyTool.java 14410 2011-08-10 20:54:51Z tchemit $ 006 * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/DefaultThirdPartyTool.java $ 007 * %% 008 * Copyright (C) 2011 CodeLutin, Codehaus, Tony Chemit 009 * %% 010 * This program is free software: you can redistribute it and/or modify 011 * it under the terms of the GNU Lesser General Public License as 012 * published by the Free Software Foundation, either version 3 of the 013 * License, or (at your option) any later version. 014 * 015 * This program is distributed in the hope that it will be useful, 016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 018 * GNU General Lesser Public License for more details. 019 * 020 * You should have received a copy of the GNU General Lesser Public 021 * License along with this program. If not, see 022 * <http://www.gnu.org/licenses/lgpl-3.0.html>. 023 * #L% 024 */ 025 package org.codehaus.mojo.license; 026 027 import java.io.File; 028 import java.io.IOException; 029 import java.util.ArrayList; 030 import java.util.Arrays; 031 import java.util.Collection; 032 import java.util.Comparator; 033 import java.util.HashMap; 034 import java.util.List; 035 import java.util.Map; 036 import java.util.Set; 037 import java.util.SortedMap; 038 import java.util.SortedSet; 039 import java.util.TreeSet; 040 import java.util.regex.Matcher; 041 import java.util.regex.Pattern; 042 043 import org.apache.commons.collections.CollectionUtils; 044 import org.apache.commons.lang.StringUtils; 045 import org.apache.maven.artifact.Artifact; 046 import org.apache.maven.artifact.factory.ArtifactFactory; 047 import org.apache.maven.artifact.repository.ArtifactRepository; 048 import org.apache.maven.artifact.resolver.ArtifactNotFoundException; 049 import org.apache.maven.artifact.resolver.ArtifactResolutionException; 050 import org.apache.maven.artifact.resolver.ArtifactResolver; 051 import org.apache.maven.model.License; 052 import org.apache.maven.project.MavenProject; 053 import org.apache.maven.project.MavenProjectBuilder; 054 import org.apache.maven.project.MavenProjectHelper; 055 import org.codehaus.mojo.license.model.LicenseMap; 056 import org.codehaus.plexus.logging.AbstractLogEnabled; 057 import org.codehaus.plexus.logging.Logger; 058 059 /** 060 * Default implementation of the third party tool. 061 * 062 * @author <a href="mailto:tchemit@codelutin.com">Tony Chemit</a> 063 * @version $Id: DefaultThirdPartyTool.java 14410 2011-08-10 20:54:51Z tchemit $ 064 * @plexus.component role="org.codehaus.mojo.license.ThirdPartyTool" role-hint="default" 065 */ 066 public class DefaultThirdPartyTool extends AbstractLogEnabled implements ThirdPartyTool { 067 public static final String DESCRIPTOR_CLASSIFIER = "third-party"; 068 069 public static final String DESCRIPTOR_TYPE = "properties"; 070 071 // ---------------------------------------------------------------------- 072 // Components 073 // ---------------------------------------------------------------------- 074 075 /** 076 * The component that is used to resolve additional artifacts required. 077 * 078 * @plexus.requirement 079 */ 080 private ArtifactResolver artifactResolver; 081 082 /** 083 * The component used for creating artifact instances. 084 * 085 * @plexus.requirement 086 */ 087 private ArtifactFactory artifactFactory; 088 089 /** 090 * Project builder. 091 * 092 * @plexus.requirement 093 */ 094 private MavenProjectBuilder mavenProjectBuilder; 095 096 /** 097 * Maven ProjectHelper. 098 * 099 * @plexus.requirement 100 */ 101 private MavenProjectHelper projectHelper; 102 103 /** 104 * Maven project comparator. 105 */ 106 private final Comparator<MavenProject> projectComparator = MojoHelper.newMavenProjectComparator(); 107 108 /** 109 * {@inheritDoc} 110 */ 111 @Override 112 public void attachThirdPartyDescriptor(MavenProject project, File file) { 113 114 projectHelper.attachArtifact(project, DESCRIPTOR_TYPE, DESCRIPTOR_CLASSIFIER, file); 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 @Override 121 public SortedSet<MavenProject> getProjectsWithNoLicense(LicenseMap licenseMap, boolean doLog) { 122 123 Logger log = getLogger(); 124 125 // get unsafe dependencies (says with no license) 126 SortedSet<MavenProject> unsafeDependencies = licenseMap.get(LicenseMap.getUnknownLicenseMessage()); 127 128 if (doLog) { 129 if (CollectionUtils.isEmpty(unsafeDependencies)) { 130 log.debug("There is no dependency with no license from poms."); 131 } else { 132 log.debug("There is " + unsafeDependencies.size() + " dependencies with no license from poms : "); 133 for (MavenProject dep : unsafeDependencies) { 134 135 // no license found for the dependency 136 log.debug(" - " + MojoHelper.getArtifactId(dep.getArtifact())); 137 } 138 } 139 } 140 141 return unsafeDependencies; 142 } 143 144 /** 145 * {@inheritDoc} 146 */ 147 @Override 148 public SortedProperties loadThirdPartyDescriptorsForUnsafeMapping(String encoding, 149 Collection<MavenProject> projects, SortedSet<MavenProject> unsafeDependencies, LicenseMap licenseMap, 150 ArtifactRepository localRepository, List<ArtifactRepository> remoteRepositories) 151 throws ThirdPartyToolException, IOException { 152 153 SortedProperties result = new SortedProperties(encoding); 154 Map<String, MavenProject> unsafeProjects = new HashMap<String, MavenProject>(); 155 for (MavenProject unsafeDependency : unsafeDependencies) { 156 String id = MojoHelper.getArtifactId(unsafeDependency.getArtifact()); 157 unsafeProjects.put(id, unsafeDependency); 158 } 159 160 for (MavenProject mavenProject : projects) { 161 162 if (CollectionUtils.isEmpty(unsafeDependencies)) { 163 164 // no more unsafe dependencies to find 165 break; 166 } 167 168 File thirdPartyDescriptor = resolvThirdPartyDescriptor(mavenProject, localRepository, remoteRepositories); 169 170 if (thirdPartyDescriptor != null && thirdPartyDescriptor.exists() && thirdPartyDescriptor.length() > 0) { 171 172 if (getLogger().isInfoEnabled()) { 173 getLogger().info("Detects third party descriptor " + thirdPartyDescriptor); 174 } 175 176 // there is a third party file detected form the given dependency 177 SortedProperties unsafeMappings = new SortedProperties(encoding); 178 179 if (thirdPartyDescriptor.exists()) { 180 181 getLogger().debug("Load missing file " + thirdPartyDescriptor); 182 183 // load the missing file 184 unsafeMappings.load(thirdPartyDescriptor); 185 } 186 187 for (String id : unsafeProjects.keySet()) { 188 189 if (unsafeMappings.containsKey(id)) { 190 191 String license = (String) unsafeMappings.get(id); 192 if (StringUtils.isEmpty(license)) { 193 194 // empty license means not fill, skip it 195 continue; 196 } 197 198 // found a resolved unsafe dependency in the missing third party file 199 MavenProject resolvedProject = unsafeProjects.get(id); 200 unsafeDependencies.remove(resolvedProject); 201 202 // push back to 203 result.put(id, license.trim()); 204 205 addLicense(licenseMap, resolvedProject, license); 206 } 207 } 208 } 209 } 210 return result; 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override 217 public File resolvThirdPartyDescriptor(MavenProject project, ArtifactRepository localRepository, 218 List<ArtifactRepository> repositories) throws ThirdPartyToolException { 219 if (project == null) { 220 throw new IllegalArgumentException("The parameter 'project' can not be null"); 221 } 222 if (localRepository == null) { 223 throw new IllegalArgumentException("The parameter 'localRepository' can not be null"); 224 } 225 if (repositories == null) { 226 throw new IllegalArgumentException("The parameter 'remoteArtifactRepositories' can not be null"); 227 } 228 229 try { 230 return resolveThirdPartyDescriptor(project, localRepository, repositories); 231 } catch (ArtifactNotFoundException e) { 232 getLogger().debug("ArtifactNotFoundException: Unable to locate third party descriptor: " + e); 233 return null; 234 } catch (ArtifactResolutionException e) { 235 throw new ThirdPartyToolException("ArtifactResolutionException: Unable to locate third party descriptor: " 236 + e.getMessage(), e); 237 } catch (IOException e) { 238 throw new ThirdPartyToolException( 239 "IOException: Unable to locate third party descriptor: " + e.getMessage(), e); 240 } 241 } 242 243 /** 244 * {@inheritDoc} 245 */ 246 @Override 247 public void addLicense(LicenseMap licenseMap, MavenProject project, String licenseName) { 248 License license = new License(); 249 license.setName(licenseName.trim()); 250 license.setUrl(licenseName.trim()); 251 addLicense(licenseMap, project, license); 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override 258 public void addLicense(LicenseMap licenseMap, MavenProject project, License license) { 259 addLicense(licenseMap, project, Arrays.asList(license)); 260 } 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override 266 public void addLicense(LicenseMap licenseMap, MavenProject project, List<?> licenses) { 267 268 if (Artifact.SCOPE_SYSTEM.equals(project.getArtifact().getScope())) { 269 270 // do NOT treate system dependency 271 return; 272 } 273 274 if (CollectionUtils.isEmpty(licenses)) { 275 276 // no license found for the dependency 277 licenseMap.put(LicenseMap.getUnknownLicenseMessage(), project); 278 return; 279 } 280 281 for (Object o : licenses) { 282 String id = MojoHelper.getArtifactId(project.getArtifact()); 283 if (o == null) { 284 getLogger().warn("could not acquire the license for " + id); 285 continue; 286 } 287 License license = (License) o; 288 String licenseKey = license.getName(); 289 290 // tchemit 2010-08-29 Ano #816 Check if the License object is well formed 291 292 if (StringUtils.isEmpty(license.getName())) { 293 getLogger().debug("No license name defined for " + id); 294 licenseKey = license.getUrl(); 295 } 296 297 if (StringUtils.isEmpty(licenseKey)) { 298 getLogger().debug("No license url defined for " + id); 299 licenseKey = LicenseMap.getUnknownLicenseMessage(); 300 } 301 licenseMap.put(licenseKey, project); 302 } 303 } 304 305 /** 306 * {@inheritDoc} 307 */ 308 @Override 309 public void mergeLicenses(LicenseMap licenseMap, String... licenses) { 310 if (licenses.length == 0) { 311 return; 312 } 313 314 String mainLicense = licenses[0].trim(); 315 SortedSet<MavenProject> mainSet = licenseMap.get(mainLicense); 316 if (mainSet == null) { 317 getLogger().debug("No license [" + mainLicense + "] found, will create it."); 318 mainSet = new TreeSet<MavenProject>(projectComparator); 319 licenseMap.put(mainLicense, mainSet); 320 } 321 int size = licenses.length; 322 for (int i = 1; i < size; i++) { 323 String license = licenses[i].trim(); 324 SortedSet<MavenProject> set = licenseMap.get(license); 325 if (set == null) { 326 getLogger().debug("No license [" + license + "] found, skip this merge."); 327 continue; 328 } 329 getLogger().debug("Merge license [" + license + "] (" + set.size() + " depedencies)."); 330 mainSet.addAll(set); 331 set.clear(); 332 licenseMap.remove(license); 333 } 334 } 335 336 /** 337 * {@inheritDoc} 338 */ 339 @Override 340 public SortedProperties loadUnsafeMapping(LicenseMap licenseMap, SortedMap<String, MavenProject> artifactCache, 341 String encoding, File missingFile) throws IOException { 342 SortedSet<MavenProject> unsafeDependencies = getProjectsWithNoLicense(licenseMap, false); 343 344 SortedProperties unsafeMappings = new SortedProperties(encoding); 345 346 if (missingFile.exists()) { 347 // there is some unsafe dependencies 348 349 getLogger().debug("Load missing file " + missingFile); 350 351 // load the missing file 352 unsafeMappings.load(missingFile); 353 } 354 355 // get from the missing file, all unknown dependencies 356 List<String> unknownDependenciesId = new ArrayList<String>(); 357 358 // coming from maven-licen-plugin, we used in id type and classifier, now we remove 359 // these informations since GAV is good enough to qualify a license of any artifact of it... 360 Map<String, String> migrateKeys = migrateMissingFileKeys(unsafeMappings.keySet()); 361 362 for (Object o : migrateKeys.keySet()) { 363 String id = (String) o; 364 String migratedId = migrateKeys.get(id); 365 366 MavenProject project = artifactCache.get(migratedId); 367 if (project == null) { 368 // now we are sure this is a unknown dependency 369 unknownDependenciesId.add(id); 370 } else { 371 if (!id.equals(migratedId)) { 372 373 // migrates id to migratedId 374 getLogger().info("Migrates [" + id + "] to [" + migratedId + "] in the missing file."); 375 Object value = unsafeMappings.get(id); 376 unsafeMappings.remove(id); 377 unsafeMappings.put(migratedId, value); 378 } 379 } 380 } 381 382 if (!unknownDependenciesId.isEmpty()) { 383 384 // there is some unknown dependencies in the missing file, remove them 385 for (String id : unknownDependenciesId) { 386 getLogger().warn("dependency [" + id + "] does not exist in project, remove it from the missing file."); 387 unsafeMappings.remove(id); 388 } 389 390 unknownDependenciesId.clear(); 391 } 392 393 // push back loaded dependencies 394 for (Object o : unsafeMappings.keySet()) { 395 String id = (String) o; 396 397 MavenProject project = artifactCache.get(id); 398 if (project == null) { 399 getLogger().warn("dependency [" + id + "] does not exist in project."); 400 continue; 401 } 402 403 String license = (String) unsafeMappings.get(id); 404 if (StringUtils.isEmpty(license)) { 405 406 // empty license means not fill, skip it 407 continue; 408 } 409 410 // add license in map 411 addLicense(licenseMap, project, license); 412 413 // remove unknown license 414 unsafeDependencies.remove(project); 415 } 416 417 if (unsafeDependencies.isEmpty()) { 418 419 // no more unknown license in map 420 licenseMap.remove(LicenseMap.getUnknownLicenseMessage()); 421 } else { 422 423 // add a "with no value license" for missing dependencies 424 for (MavenProject project : unsafeDependencies) { 425 String id = MojoHelper.getArtifactId(project.getArtifact()); 426 if (getLogger().isDebugEnabled()) { 427 getLogger().debug("dependency [" + id + "] has no license, add it in the missing file."); 428 } 429 unsafeMappings.setProperty(id, ""); 430 } 431 } 432 return unsafeMappings; 433 } 434 435 // ---------------------------------------------------------------------- 436 // Private methods 437 // ---------------------------------------------------------------------- 438 439 /** 440 * @param project 441 * not null 442 * @param localRepository 443 * not null 444 * @param repositories 445 * not null 446 * @return the resolved site descriptor 447 * @throws IOException 448 * if any 449 * @throws ArtifactResolutionException 450 * if any 451 * @throws ArtifactNotFoundException 452 * if any 453 */ 454 private File resolveThirdPartyDescriptor(MavenProject project, ArtifactRepository localRepository, 455 List<ArtifactRepository> repositories) throws IOException, ArtifactResolutionException, 456 ArtifactNotFoundException { 457 File result; 458 459 // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? 460 Artifact artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(), 461 project.getVersion(), DESCRIPTOR_TYPE, DESCRIPTOR_CLASSIFIER); 462 try { 463 artifactResolver.resolve(artifact, repositories, localRepository); 464 465 result = artifact.getFile(); 466 467 // we use zero length files to avoid re-resolution (see below) 468 if (result.length() == 0) { 469 getLogger().debug("Skipped third party descriptor"); 470 } 471 } catch (ArtifactNotFoundException e) { 472 getLogger().debug("Unable to locate third party files descriptor : " + e); 473 474 // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote 475 // repository, because the parent was already released (and snapshots are updated automatically if changed) 476 result = new File(localRepository.getBasedir(), localRepository.pathOf(artifact)); 477 478 FileUtil.createNewFile(result); 479 } 480 481 return result; 482 } 483 484 private final Pattern GAV_PLUS_TYPE_PATTERN = Pattern.compile("(.+)--(.+)--(.+)--(.+)"); 485 486 private final Pattern GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN = Pattern.compile("(.+)--(.+)--(.+)--(.+)--(.+)"); 487 488 private Map<String, String> migrateMissingFileKeys(Set<Object> missingFileKeys) { 489 Map<String, String> migrateKeys = new HashMap<String, String>(); 490 for (Object object : missingFileKeys) { 491 String id = (String) object; 492 Matcher matcher; 493 494 String newId = id; 495 matcher = GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN.matcher(id); 496 if (matcher.matches()) { 497 newId = matcher.group(1) + "--" + matcher.group(2) + "--" + matcher.group(3); 498 499 } else { 500 matcher = GAV_PLUS_TYPE_PATTERN.matcher(id); 501 if (matcher.matches()) { 502 newId = matcher.group(1) + "--" + matcher.group(2) + "--" + matcher.group(3); 503 504 } 505 } 506 migrateKeys.put(id, newId); 507 } 508 return migrateKeys; 509 } 510 }