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 }