View Javadoc

1   /**
2    * Copyright 2010-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.codehaus.mojo.license;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Comparator;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.SortedMap;
29  import java.util.SortedSet;
30  import java.util.TreeSet;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import org.apache.commons.collections.CollectionUtils;
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.factory.ArtifactFactory;
38  import org.apache.maven.artifact.repository.ArtifactRepository;
39  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
40  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
41  import org.apache.maven.artifact.resolver.ArtifactResolver;
42  import org.apache.maven.model.License;
43  import org.apache.maven.project.MavenProject;
44  import org.apache.maven.project.MavenProjectBuilder;
45  import org.apache.maven.project.MavenProjectHelper;
46  import org.codehaus.mojo.license.model.LicenseMap;
47  import org.codehaus.plexus.logging.AbstractLogEnabled;
48  import org.codehaus.plexus.logging.Logger;
49  
50  /**
51   * Default implementation of the third party tool.
52   *
53   * @author <a href="mailto:tchemit@codelutin.com">Tony Chemit</a>
54   * @version $Id: DefaultThirdPartyTool.java 14410 2011-08-10 20:54:51Z tchemit $
55   * @plexus.component role="org.codehaus.mojo.license.ThirdPartyTool" role-hint="default"
56   */
57  public class DefaultThirdPartyTool extends AbstractLogEnabled implements ThirdPartyTool {
58      public static final String DESCRIPTOR_CLASSIFIER = "third-party";
59  
60      public static final String DESCRIPTOR_TYPE = "properties";
61  
62      // ----------------------------------------------------------------------
63      // Components
64      // ----------------------------------------------------------------------
65  
66      /**
67       * The component that is used to resolve additional artifacts required.
68       *
69       * @plexus.requirement
70       */
71      private ArtifactResolver artifactResolver;
72  
73      /**
74       * The component used for creating artifact instances.
75       *
76       * @plexus.requirement
77       */
78      private ArtifactFactory artifactFactory;
79  
80      /**
81       * Project builder.
82       *
83       * @plexus.requirement
84       */
85      private MavenProjectBuilder mavenProjectBuilder;
86  
87      /**
88       * Maven ProjectHelper.
89       *
90       * @plexus.requirement
91       */
92      private MavenProjectHelper projectHelper;
93  
94      /**
95       * Maven project comparator.
96       */
97      private final Comparator<MavenProject> projectComparator = MojoHelper.newMavenProjectComparator();
98  
99      /**
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 }