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.io.InputStream;
021 import java.io.OutputStream;
022 import java.util.ArrayList;
023 import java.util.Arrays;
024 import java.util.Collections;
025 import java.util.HashMap;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.SortedMap;
029 import java.util.SortedSet;
030 import java.util.TreeMap;
031
032 import org.apache.commons.collections.CollectionUtils;
033 import org.apache.commons.io.FileUtils;
034 import org.apache.commons.io.IOUtils;
035 import org.apache.commons.lang.StringUtils;
036 import org.apache.maven.plugin.MojoFailureException;
037 import org.apache.maven.plugin.logging.Log;
038 import org.apache.maven.project.MavenProject;
039 import org.apache.maven.project.ProjectBuildingException;
040 import org.codehaus.mojo.license.model.LicenseMap;
041 import org.springframework.core.io.DefaultResourceLoader;
042 import org.springframework.core.io.Resource;
043 import org.springframework.core.io.ResourceLoader;
044
045 /**
046 * Abstract mojo for all third-party mojos.
047 *
048 * @author tchemit <chemit@codelutin.com>
049 * @since 1.0
050 */
051 public abstract class AbstractAddThirdPartyMojo extends AbstractLicenseMojo {
052
053 /**
054 * Directory where to generate files.
055 *
056 * @parameter expression="${license.outputDirectory}"
057 * default-value="${project.build.directory}/generated-sources/license"
058 * @required
059 * @since 1.0
060 */
061 protected File outputDirectory;
062
063 /**
064 * File where to wirte the third-party file.
065 *
066 * @parameter expression="${license.thirdPartyFilename}" default-value="THIRD-PARTY.txt"
067 * @required
068 * @since 1.0
069 */
070 protected String thirdPartyFilename;
071
072 /**
073 * A flag to use the missing licenses file to consolidate the THID-PARTY file.
074 *
075 * @parameter expression="${license.useMissingFile}" default-value="false"
076 * @since 1.0
077 */
078 protected boolean useMissingFile;
079
080 /**
081 * The file where to fill the license for dependencies with unknown license.
082 *
083 * @parameter expression="${license.missingFile}" default-value="src/license/THIRD-PARTY.properties"
084 * @since 1.0
085 */
086 protected File missingFile;
087
088 /**
089 * Location of a properties file mapping artifacts that are published with no license to the license that should be
090 * used for them. This supports classpath notation and any other type of URL Spring 3.1 resource loading can
091 * understand.
092 *
093 * @parameter expression="${license.artifactLicenseMapping}" default-value="THIRD-PARTY.properties"
094 * @since 1.0
095 */
096 protected String artifactLicenseMapping;
097
098 /**
099 * To merge licenses in final file.
100 * <p/>
101 * Each entry represents a merge (first license is main license to keep), licenses are separated by {@code |}.
102 * <p/>
103 * Example :
104 * <p/>
105 *
106 * <pre>
107 * <licenseMerges>
108 * <licenseMerge>The Apache Software License|Version 2.0,Apache License, Version 2.0</licenseMerge>
109 * </licenseMerges>
110 * </pre>
111 *
112 * @parameter
113 * @since 1.0
114 */
115 protected List<String> licenseMerges;
116
117 /**
118 * The path of the bundled third party file to produce when {@link #generateBundle} is on.
119 * <p/>
120 * <b>Note:</b> This option is not available for {@code pom} module types.
121 *
122 * @parameter expression="${license.bundleThirdPartyPath}"
123 * default-value="META-INF/${project.artifactId}-THIRD-PARTY.txt"
124 * @since 1.0
125 */
126 protected String bundleThirdPartyPath;
127
128 /**
129 * A flag to copy a bundled version of the third-party file. This is usefull to avoid for a final application
130 * collision name of third party file.
131 * <p/>
132 * The file will be copied at the {@link #bundleThirdPartyPath} location.
133 *
134 * @parameter expression="${license.generateBundle}" default-value="false"
135 * @since 1.0
136 */
137 protected boolean generateBundle;
138
139 /**
140 * To force generation of the third-party file even if every thing is up to date.
141 *
142 * @parameter expression="${license.force}" default-value="false"
143 * @since 1.0
144 */
145 protected boolean force;
146
147 /**
148 * A flag to fail the build if at least one dependency was detected without a license.
149 *
150 * @parameter expression="${license.failIfWarning}" default-value="false"
151 * @since 1.0
152 */
153 protected boolean failIfWarning;
154
155 /**
156 * A flag to change the grouping of the generated THIRD-PARTY file.
157 * <p/>
158 * By default, group by dependecies.
159 * <p/>
160 * If sets to {@code true}, the it will group by license type.
161 *
162 * @parameter expression="${license.groupByLicense}" default-value="false"
163 * @since 1.0
164 */
165 protected boolean groupByLicense;
166
167 /**
168 * A filter to exclude some scopes.
169 *
170 * @parameter expression="${license.excludedScopes}" default-value="system"
171 * @since 1.0
172 */
173 protected String excludedScopes;
174
175 /**
176 * A filter to include only some scopes, if let empty then all scopes will be used (no filter).
177 *
178 * @parameter expression="${license.includedScopes}" default-value=""
179 * @since 1.0
180 */
181 protected String includedScopes;
182
183 /**
184 * A filter to exclude some GroupIds
185 *
186 * @parameter expression="${license.excludedGroups}" default-value=""
187 * @since 1.0
188 */
189 protected String excludedGroups;
190
191 /**
192 * A filter to include only some GroupIds
193 *
194 * @parameter expression="${license.includedGroups}" default-value=""
195 * @since 1.0
196 */
197 protected String includedGroups;
198
199 /**
200 * A filter to exclude some ArtifactsIds
201 *
202 * @parameter expression="${license.excludedArtifacts}" default-value=""
203 * @since 1.0
204 */
205 protected String excludedArtifacts;
206
207 /**
208 * A filter to include only some ArtifactsIds
209 *
210 * @parameter expression="${license.includedArtifacts}" default-value=""
211 * @since 1.0
212 */
213 protected String includedArtifacts;
214
215 /**
216 * Include transitive dependencies when downloading license files.
217 *
218 * @parameter default-value="true"
219 * @since 1.0
220 */
221 protected boolean includeTransitiveDependencies;
222
223 /**
224 * third party tool.
225 *
226 * @component
227 * @readonly
228 * @since 1.0
229 */
230 private ThirdPartyTool thridPartyTool;
231
232 private SortedMap<String, MavenProject> projectDependencies;
233
234 private LicenseMap licenseMap;
235
236 private SortedSet<MavenProject> unsafeDependencies;
237
238 private File thirdPartyFile;
239
240 private SortedProperties unsafeMappings;
241
242 private boolean doGenerate;
243
244 private boolean doGenerateBundle;
245
246 public static final String NO_DEPENDENCIES_MESSAGE = "the project has no dependencies.";
247
248 private static SortedMap<String, MavenProject> artifactCache;
249
250 public static SortedMap<String, MavenProject> getArtifactCache() {
251 if (artifactCache == null) {
252 artifactCache = new TreeMap<String, MavenProject>();
253 }
254 return artifactCache;
255 }
256
257 protected abstract SortedMap<String, MavenProject> loadDependencies();
258
259 protected abstract SortedProperties createUnsafeMapping() throws ProjectBuildingException, IOException,
260 ThirdPartyToolException;
261
262 protected boolean exists(String location) {
263 if (StringUtils.isBlank(location)) {
264 return false;
265 }
266 ResourceLoader loader = new DefaultResourceLoader();
267 Resource resource = loader.getResource(location);
268 return resource.exists();
269 }
270
271 protected File copyToFileSystem(String location) {
272 ResourceLoader loader = new DefaultResourceLoader();
273 Resource resource = loader.getResource(location);
274 if (!resource.exists()) {
275 throw new IllegalArgumentException("Can't locate " + location);
276 }
277
278 InputStream in = null;
279 OutputStream out = null;
280 try {
281 in = resource.getInputStream();
282 File temp = new File(getProject().getBuild().getDirectory() + "/license/THIRD-PARTY.properties");
283 out = FileUtils.openOutputStream(temp);
284 IOUtils.copy(in, out);
285 getLog().debug("Created " + temp);
286 return temp;
287 } catch (IOException e) {
288 throw new IllegalArgumentException(e);
289 } finally {
290 IOUtils.closeQuietly(in);
291 IOUtils.closeQuietly(out);
292 }
293 }
294
295 @Override
296 protected void init() throws Exception {
297 if (exists(getArtifactLicenseMapping())) {
298 File propertiesFile = copyToFileSystem(getArtifactLicenseMapping());
299 setMissingFile(propertiesFile);
300 }
301
302 Log log = getLog();
303
304 if (log.isDebugEnabled()) {
305
306 // always be verbose in debug mode
307 setVerbose(true);
308 }
309
310 File file = new File(getOutputDirectory(), getThirdPartyFilename());
311
312 setThirdPartyFile(file);
313
314 long buildTimestamp = getBuildTimestamp();
315
316 if (isVerbose()) {
317 log.info("Build start at : " + buildTimestamp);
318 log.info("third-party file : " + file.lastModified());
319 }
320
321 setDoGenerate(isForce() || !file.exists() || buildTimestamp > file.lastModified());
322
323 if (isGenerateBundle()) {
324
325 File bundleFile = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath());
326
327 if (isVerbose()) {
328 log.info("bundle third-party file : " + bundleFile.lastModified());
329 }
330 setDoGenerateBundle(isForce() || !bundleFile.exists() || buildTimestamp > bundleFile.lastModified());
331 } else {
332
333 // not generating bundled file
334 setDoGenerateBundle(false);
335 }
336
337 projectDependencies = loadDependencies();
338
339 licenseMap = createLicenseMap(projectDependencies);
340
341 SortedSet<MavenProject> unsafeDependencies = getThridPartyTool().getProjectsWithNoLicense(licenseMap,
342 isVerbose());
343
344 setUnsafeDependencies(unsafeDependencies);
345
346 if (!CollectionUtils.isEmpty(unsafeDependencies) && isUseMissingFile() && isDoGenerate()) {
347
348 // load unsafeMapping
349 unsafeMappings = createUnsafeMapping();
350 }
351
352 if (!CollectionUtils.isEmpty(licenseMerges)) {
353
354 // check where is not multi licenses merged main licenses (see OJO-1723)
355 Map<String, String[]> mergedLicenses = new HashMap<String, String[]>();
356
357 for (String merge : licenseMerges) {
358 merge = merge.trim();
359 String[] split = merge.split("\\|");
360
361 String mainLicense = split[0];
362
363 if (mergedLicenses.containsKey(mainLicense)) {
364
365 // this license was already describe, fail the build...
366
367 throw new MojoFailureException(
368 "The merge main license "
369 + mainLicense
370 + " was already registred in the "
371 + "configuration, please use only one such entry as describe in example "
372 + "http://mojo.codehaus.org/license-maven-plugin/examples/example-thirdparty.html#Merge_licenses.");
373 }
374 mergedLicenses.put(mainLicense, split);
375 }
376
377 // merge licenses in license map
378
379 for (String[] mergedLicense : mergedLicenses.values()) {
380 if (isVerbose()) {
381 getLog().info("Will merge " + Arrays.toString(mergedLicense) + "");
382 }
383
384 thridPartyTool.mergeLicenses(licenseMap, mergedLicense);
385 }
386 }
387 }
388
389 protected LicenseMap createLicenseMap(SortedMap<String, MavenProject> dependencies) {
390
391 LicenseMap licenseMap = new LicenseMap();
392
393 for (MavenProject project : dependencies.values()) {
394 thridPartyTool.addLicense(licenseMap, project, project.getLicenses());
395 }
396 return licenseMap;
397 }
398
399 protected boolean checkUnsafeDependencies() {
400 SortedSet<MavenProject> unsafeDependencies = getUnsafeDependencies();
401 boolean unsafe = !CollectionUtils.isEmpty(unsafeDependencies);
402 if (unsafe) {
403 Log log = getLog();
404 log.debug("There is " + unsafeDependencies.size() + " dependencies with no license :");
405 for (MavenProject dep : unsafeDependencies) {
406
407 // no license found for the dependency
408 log.debug(" - " + MojoHelper.getArtifactId(dep.getArtifact()));
409 }
410 }
411 return unsafe;
412 }
413
414 protected void writeThirdPartyFile() throws IOException {
415
416 Log log = getLog();
417 LicenseMap licenseMap = getLicenseMap();
418 File target = getThirdPartyFile();
419
420 if (isDoGenerate()) {
421 StringBuilder sb = new StringBuilder();
422 if (licenseMap.isEmpty()) {
423 sb.append(NO_DEPENDENCIES_MESSAGE);
424 } else {
425 if (isGroupByLicense()) {
426
427 // group by license
428 sb.append("List of third-party dependencies grouped by " + "their license type.");
429 for (String licenseName : licenseMap.keySet()) {
430 SortedSet<MavenProject> projects = licenseMap.get(licenseName);
431
432 // Don't print the license if it isn't being used
433 if (projects == null || projects.size() == 0) {
434 continue;
435 }
436
437 sb.append("\n\n").append(licenseName).append(" : ");
438
439 for (MavenProject mavenProject : projects) {
440 String s = MojoHelper.getArtifactName(mavenProject);
441 sb.append("\n * ").append(s);
442 }
443 }
444
445 } else {
446
447 // group by dependencies
448 SortedMap<MavenProject, String[]> map = licenseMap.toDependencyMap();
449
450 sb.append("List of ").append(map.size()).append(" third-party dependencies.\n");
451
452 List<String> lines = new ArrayList<String>();
453
454 for (Map.Entry<MavenProject, String[]> entry : map.entrySet()) {
455 String artifact = MojoHelper.getArtifactName(entry.getKey());
456 StringBuilder buffer = new StringBuilder();
457 for (String license : entry.getValue()) {
458 buffer.append(" (").append(license).append(")");
459 }
460 String licenses = buffer.toString();
461 String line = licenses + " " + artifact;
462 lines.add(line);
463 }
464
465 Collections.sort(lines);
466 for (String line : lines) {
467 sb.append('\n').append(line);
468 }
469 lines.clear();
470 }
471 }
472 String content = sb.toString();
473
474 log.info("Writing third-party file to " + target);
475 if (isVerbose()) {
476 log.info(content);
477 }
478
479 FileUtil.writeString(target, content, getEncoding());
480 }
481
482 if (isDoGenerateBundle()) {
483
484 // creates the bundled license file
485 File bundleTarget = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath());
486 log.info("Writing bundled third-party file to " + bundleTarget);
487 FileUtil.copyFile(target, bundleTarget);
488 }
489 }
490
491 public boolean isGroupByLicense() {
492 return groupByLicense;
493 }
494
495 public void setGroupByLicense(boolean groupByLicense) {
496 this.groupByLicense = groupByLicense;
497 }
498
499 public File getOutputDirectory() {
500 return outputDirectory;
501 }
502
503 public String getThirdPartyFilename() {
504 return thirdPartyFilename;
505 }
506
507 public String getBundleThirdPartyPath() {
508 return bundleThirdPartyPath;
509 }
510
511 public boolean isGenerateBundle() {
512 return generateBundle;
513 }
514
515 public boolean isFailIfWarning() {
516 return failIfWarning;
517 }
518
519 public SortedMap<String, MavenProject> getProjectDependencies() {
520 return projectDependencies;
521 }
522
523 public SortedSet<MavenProject> getUnsafeDependencies() {
524 return unsafeDependencies;
525 }
526
527 public void setUnsafeDependencies(SortedSet<MavenProject> unsafeDependencies) {
528 this.unsafeDependencies = unsafeDependencies;
529 }
530
531 public File getThirdPartyFile() {
532 return thirdPartyFile;
533 }
534
535 public LicenseMap getLicenseMap() {
536 return licenseMap;
537 }
538
539 public void setOutputDirectory(File outputDirectory) {
540 this.outputDirectory = outputDirectory;
541 }
542
543 public void setThirdPartyFilename(String thirdPartyFilename) {
544 this.thirdPartyFilename = thirdPartyFilename;
545 }
546
547 public void setBundleThirdPartyPath(String bundleThirdPartyPath) {
548 this.bundleThirdPartyPath = bundleThirdPartyPath;
549 }
550
551 public void setGenerateBundle(boolean generateBundle) {
552 this.generateBundle = generateBundle;
553 }
554
555 public void setThirdPartyFile(File thirdPartyFile) {
556 this.thirdPartyFile = thirdPartyFile;
557 }
558
559 public boolean isUseMissingFile() {
560 return useMissingFile;
561 }
562
563 public File getMissingFile() {
564 return missingFile;
565 }
566
567 public void setUseMissingFile(boolean useMissingFile) {
568 this.useMissingFile = useMissingFile;
569 }
570
571 public void setMissingFile(File missingFile) {
572 this.missingFile = missingFile;
573 }
574
575 public void setFailIfWarning(boolean failIfWarning) {
576 this.failIfWarning = failIfWarning;
577 }
578
579 public SortedProperties getUnsafeMappings() {
580 return unsafeMappings;
581 }
582
583 public boolean isForce() {
584 return force;
585 }
586
587 public boolean isDoGenerate() {
588 return doGenerate;
589 }
590
591 public void setForce(boolean force) {
592 this.force = force;
593 }
594
595 public void setDoGenerate(boolean doGenerate) {
596 this.doGenerate = doGenerate;
597 }
598
599 public boolean isDoGenerateBundle() {
600 return doGenerateBundle;
601 }
602
603 public void setDoGenerateBundle(boolean doGenerateBundle) {
604 this.doGenerateBundle = doGenerateBundle;
605 }
606
607 public List<String> getExcludedScopes() {
608 String[] split = excludedScopes == null ? new String[0] : excludedScopes.split(",");
609 return Arrays.asList(split);
610 }
611
612 public void setExcludedScopes(String excludedScopes) {
613 this.excludedScopes = excludedScopes;
614 }
615
616 public List<String> getIncludedScopes() {
617 String[] split = includedScopes == null ? new String[0] : includedScopes.split(",");
618 return Arrays.asList(split);
619 }
620
621 public void setIncludedScopes(String includedScopes) {
622 this.includedScopes = includedScopes;
623 }
624
625 public String getExcludedGroups() {
626 return excludedGroups;
627 }
628
629 public void setExcludedGroups(String excludedGroups) {
630 this.excludedGroups = excludedGroups;
631 }
632
633 public String getIncludedGroups() {
634 return includedGroups;
635 }
636
637 public void setIncludedGroups(String includedGroups) {
638 this.includedGroups = includedGroups;
639 }
640
641 public String getExcludedArtifacts() {
642 return excludedArtifacts;
643 }
644
645 public void setExcludedArtifacts(String excludedArtifacts) {
646 this.excludedArtifacts = excludedArtifacts;
647 }
648
649 public String getIncludedArtifacts() {
650 return includedArtifacts;
651 }
652
653 public void setIncludedArtifacts(String includedArtifacts) {
654 this.includedArtifacts = includedArtifacts;
655 }
656
657 public ThirdPartyTool getThridPartyTool() {
658 return thridPartyTool;
659 }
660
661 public void setThridPartyTool(ThirdPartyTool thridPartyTool) {
662 this.thridPartyTool = thridPartyTool;
663 }
664
665 public String getArtifactLicenseMapping() {
666 return artifactLicenseMapping;
667 }
668
669 public void setArtifactLicenseMapping(String artifactLicenseMapping) {
670 this.artifactLicenseMapping = artifactLicenseMapping;
671 }
672 }