View Javadoc

1   /*
2    * #%L
3    * License Maven Plugin
4    *
5    * $Id: DefaultThirdPartyTool.java 14410 2011-08-10 20:54:51Z tchemit $
6    * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/DefaultThirdPartyTool.java $
7    * %%
8    * Copyright (C) 2011 CodeLutin, Codehaus, Tony Chemit
9    * %%
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as
12   * published by the Free Software Foundation, either version 3 of the
13   * License, or (at your option) any later version.
14   *
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   *
20   * You should have received a copy of the GNU General Lesser Public
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
23   * #L%
24   */
25  package org.codehaus.mojo.license;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Comparator;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  import java.util.SortedMap;
38  import java.util.SortedSet;
39  import java.util.TreeSet;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  
43  import org.apache.commons.collections.CollectionUtils;
44  import org.apache.commons.lang.StringUtils;
45  import org.apache.maven.artifact.Artifact;
46  import org.apache.maven.artifact.factory.ArtifactFactory;
47  import org.apache.maven.artifact.repository.ArtifactRepository;
48  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
49  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
50  import org.apache.maven.artifact.resolver.ArtifactResolver;
51  import org.apache.maven.model.License;
52  import org.apache.maven.project.MavenProject;
53  import org.apache.maven.project.MavenProjectBuilder;
54  import org.apache.maven.project.MavenProjectHelper;
55  import org.codehaus.mojo.license.model.LicenseMap;
56  import org.codehaus.plexus.logging.AbstractLogEnabled;
57  import org.codehaus.plexus.logging.Logger;
58  
59  /**
60   * Default implementation of the third party tool.
61   *
62   * @author <a href="mailto:tchemit@codelutin.com">Tony Chemit</a>
63   * @version $Id: DefaultThirdPartyTool.java 14410 2011-08-10 20:54:51Z tchemit $
64   * @plexus.component role="org.codehaus.mojo.license.ThirdPartyTool" role-hint="default"
65   */
66  public class DefaultThirdPartyTool extends AbstractLogEnabled implements ThirdPartyTool {
67      public static final String DESCRIPTOR_CLASSIFIER = "third-party";
68  
69      public static final String DESCRIPTOR_TYPE = "properties";
70  
71      // ----------------------------------------------------------------------
72      // Components
73      // ----------------------------------------------------------------------
74  
75      /**
76       * The component that is used to resolve additional artifacts required.
77       *
78       * @plexus.requirement
79       */
80      private ArtifactResolver artifactResolver;
81  
82      /**
83       * The component used for creating artifact instances.
84       *
85       * @plexus.requirement
86       */
87      private ArtifactFactory artifactFactory;
88  
89      /**
90       * Project builder.
91       *
92       * @plexus.requirement
93       */
94      private MavenProjectBuilder mavenProjectBuilder;
95  
96      /**
97       * Maven ProjectHelper.
98       *
99       * @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()
387                         .debug("dependency [" + id + "] does not exist in project, remove it from the missing file.");
388                 unsafeMappings.remove(id);
389             }
390 
391             unknownDependenciesId.clear();
392         }
393 
394         // push back loaded dependencies
395         for (Object o : unsafeMappings.keySet()) {
396             String id = (String) o;
397 
398             MavenProject project = artifactCache.get(id);
399             if (project == null) {
400                 getLogger().warn("dependency [" + id + "] does not exist in project.");
401                 continue;
402             }
403 
404             String license = (String) unsafeMappings.get(id);
405             if (StringUtils.isEmpty(license)) {
406 
407                 // empty license means not fill, skip it
408                 continue;
409             }
410 
411             // add license in map
412             addLicense(licenseMap, project, license);
413 
414             // remove unknown license
415             unsafeDependencies.remove(project);
416         }
417 
418         if (unsafeDependencies.isEmpty()) {
419 
420             // no more unknown license in map
421             licenseMap.remove(LicenseMap.getUnknownLicenseMessage());
422         } else {
423 
424             // add a "with no value license" for missing dependencies
425             for (MavenProject project : unsafeDependencies) {
426                 String id = MojoHelper.getArtifactId(project.getArtifact());
427                 if (getLogger().isDebugEnabled()) {
428                     getLogger().debug("dependency [" + id + "] has no license, add it in the missing file.");
429                 }
430                 unsafeMappings.setProperty(id, "");
431             }
432         }
433         return unsafeMappings;
434     }
435 
436     // ----------------------------------------------------------------------
437     // Private methods
438     // ----------------------------------------------------------------------
439 
440     /**
441      * @param project
442      *            not null
443      * @param localRepository
444      *            not null
445      * @param repositories
446      *            not null
447      * @return the resolved site descriptor
448      * @throws IOException
449      *             if any
450      * @throws ArtifactResolutionException
451      *             if any
452      * @throws ArtifactNotFoundException
453      *             if any
454      */
455     private File resolveThirdPartyDescriptor(MavenProject project, ArtifactRepository localRepository,
456             List<ArtifactRepository> repositories) throws IOException, ArtifactResolutionException,
457             ArtifactNotFoundException {
458         File result;
459 
460         // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1?
461         Artifact artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(),
462                 project.getVersion(), DESCRIPTOR_TYPE, DESCRIPTOR_CLASSIFIER);
463         try {
464             artifactResolver.resolve(artifact, repositories, localRepository);
465 
466             result = artifact.getFile();
467 
468             // we use zero length files to avoid re-resolution (see below)
469             if (result.length() == 0) {
470                 getLogger().debug("Skipped third party descriptor");
471             }
472         } catch (ArtifactNotFoundException e) {
473             getLogger().debug("Unable to locate third party files descriptor : " + e);
474 
475             // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote
476             // repository, because the parent was already released (and snapshots are updated automatically if changed)
477             result = new File(localRepository.getBasedir(), localRepository.pathOf(artifact));
478 
479             FileUtil.createNewFile(result);
480         }
481 
482         return result;
483     }
484 
485     private final Pattern GAV_PLUS_TYPE_PATTERN = Pattern.compile("(.+)--(.+)--(.+)--(.+)");
486 
487     private final Pattern GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN = Pattern.compile("(.+)--(.+)--(.+)--(.+)--(.+)");
488 
489     private Map<String, String> migrateMissingFileKeys(Set<Object> missingFileKeys) {
490         Map<String, String> migrateKeys = new HashMap<String, String>();
491         for (Object object : missingFileKeys) {
492             String id = (String) object;
493             Matcher matcher;
494 
495             String newId = id;
496             matcher = GAV_PLUS_TYPE_AND_CLASSIFIER_PATTERN.matcher(id);
497             if (matcher.matches()) {
498                 newId = matcher.group(1) + "--" + matcher.group(2) + "--" + matcher.group(3);
499 
500             } else {
501                 matcher = GAV_PLUS_TYPE_PATTERN.matcher(id);
502                 if (matcher.matches()) {
503                     newId = matcher.group(1) + "--" + matcher.group(2) + "--" + matcher.group(3);
504 
505                 }
506             }
507             migrateKeys.put(id, newId);
508         }
509         return migrateKeys;
510     }
511 }