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 }