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