-
Notifications
You must be signed in to change notification settings - Fork 128
/
Copy pathAbstractDownloadLicensesMojo.java
1263 lines (1160 loc) · 50.8 KB
/
AbstractDownloadLicensesMojo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.codehaus.mojo.license;
/*
* #%L
* License Maven Plugin
* %%
* Copyright (C) 2016 Tony Chemit
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.model.License;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Proxy;
import org.codehaus.mojo.license.api.ArtifactFilters;
import org.codehaus.mojo.license.api.MavenProjectDependenciesConfigurator;
import org.codehaus.mojo.license.api.ResolvedProjectDependencies;
import org.codehaus.mojo.license.download.Cache;
import org.codehaus.mojo.license.download.FileNameEntry;
import org.codehaus.mojo.license.download.LicenseDownloader;
import org.codehaus.mojo.license.download.PreferredFileNames;
import org.codehaus.mojo.license.download.ProjectLicense;
import org.codehaus.mojo.license.download.ProjectLicenseInfo;
import org.codehaus.mojo.license.download.LicenseDownloader.LicenseDownloadResult;
import org.codehaus.mojo.license.download.LicenseMatchers;
import org.codehaus.mojo.license.download.LicenseSummaryReader;
import org.codehaus.mojo.license.utils.FileUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.regex.Pattern;
/**
* Created on 23/05/16.
*
* @author Tony Chemit - [email protected]
*/
public abstract class AbstractDownloadLicensesMojo
extends AbstractLicensesXmlMojo
implements MavenProjectDependenciesConfigurator
{
// ----------------------------------------------------------------------
// Mojo Parameters
// ----------------------------------------------------------------------
/**
* Location of the local repository.
*
* @since 1.0
*/
@Parameter( defaultValue = "${localRepository}", readonly = true )
private ArtifactRepository localRepository;
/**
* List of Remote Repositories used by the resolver
*
* @since 1.0
*/
@Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true )
private List<ArtifactRepository> remoteRepositories;
// CHECKSTYLE_OFF: LineLength
/**
* A file containing the license data (most notably license names and license URLs) missing in
* {@code pom.xml} files of the dependencies.
* <p>
* Note that since 1.18, if you set {@link #errorRemedy} to {@code xmlOutput} the format of
* {@link #licensesErrorsFile} is the same as the one of {@link #licensesConfigFile}. So you can use
* {@link #licensesErrorsFile} as a base for {@link #licensesConfigFile}.
* <p>
* Since 1.18, the format of the file is as follows:
* <pre>
* {@code
* <licenseSummary>
* <dependencies>
* <dependency>
* <groupId>\Qaopalliance\E</groupId><!-- A regular expression -->
* <artifactId>\Qaopalliance\E</artifactId><!-- A regular expression -->
* <!-- <version>.*</version> A version pattern is optional, .* being the default.
* <matchLicenses>
* <!-- Match a list of licenses with a single entry having name "Public Domain" -->
* <license>
* <name>\QPublic Domain\E</name><!-- A regular expression -->
* </license>
* </matchLicenses>
* <licenses approved="true" /><!-- Leave the matched dependency as is. -->
* <!-- In this particular case we approve that code in the Public -->
* <!-- Domain does not need any license URL. -->
* </dependency>
* <dependency>
* <groupId>\Qasm\E</groupId>
* <artifactId>\Qasm\E</artifactId>
* <matchLicenses>
* <!-- Match an empty list of licenses -->
* </matchLicenses>
* <!-- Replace the list of licenses in all matching dependencies with the following licenses -->
* <licenses>
* <license>
* <name>BSD 3-Clause ASM</name>
* <url>https://gitlab.ow2.org/asm/asm/raw/ASM_3_1_MVN/LICENSE.txt</url>
* </license>
* </licenses>
* </dependency>
* <dependency>
* <groupId>\Qca.uhn.hapi\E</groupId>
* <artifactId>.*</artifactId>
* <matchLicenses>
* <!-- Match a list of licenses with the following three entries in order: -->
* <license>
* <name>\QHAPI is dual licensed (MPL, GPL)\E</name>
* <comments>\QHAPI is dual licensed under both the Mozilla Public License and the GNU General Public License.\E\s+\QWhat this means is that you may choose to use HAPI under the terms of either license.\E\s+\QYou are both permitted and encouraged to use HAPI, royalty-free, within your applications,\E\s+\Qwhether they are free/open-source or commercial/closed-source, provided you abide by the\E\s+\Qterms of one of the licenses below.\E\s+\QYou are under no obligations to inform the HAPI project about what you are doing with\E\s+\QHAPI, but we would love to hear about it anyway!\E</comments>
* </license>
* <license>
* <name>\QMozilla Public License 1.1\E</name>
* <url>\Qhttp://www.mozilla.org/MPL/MPL-1.1.txt\E</url>
* <file>\Qmozilla public license 1.1 - index.0c5913925d40.txt\E</file>
* </license>
* <license>
* <name>\QGNU General Public License\E</name>
* <url>\Qhttp://www.gnu.org/licenses/gpl.txt\E</url>
* <file>\Qgnu general public license - gpl.txt\E</file>
* </license>
* </matchLicenses>
* <licenses approved="true" /><!-- It is OK that the first entry has no URL -->
* </dependency>
* </dependencies>
* </licenseSummary>
* }
* </pre>
* <p>
* Before 1.18 the format was the same as the one of {@link #licensesOutputFile} and the groupIds and artifactIds
* were matched as plain text rather than regular expressions. No other elements (incl. versions) were matched at
* all. Since 1.18 the backwards compatibility is achieved by falling back to plain text matching of groupIds
* and artifactIds if the given {@code <dependency>} does not contain the {@code <matchLicenses>} element.
* <p>
* Relationship to other parameters:
* <ul>
* <li>License names and license URLs {@link #licensesConfigFile} is applied before
* {@link #licenseUrlReplacements}</li>
* <li>{@link #licenseUrlReplacements} are applied before {@link #licenseUrlFileNames}</li>
* <li>{@link #licenseUrlFileNames} have higher precedence than {@code <file>} elements in
* {@link #licensesConfigFile}</li>
* <li>{@link #licenseUrlFileNames} are ignored when {@link #organizeLicensesByDependencies} is {@code true}</li>
* </ul>
*
* @since 1.0
*/
@Parameter( property = "licensesConfigFile", defaultValue = "${project.basedir}/src/license/licenses.xml" )
protected File licensesConfigFile;
// CHECKSTYLE_ON: LineLength
/**
* The directory to which the dependency licenses should be written.
*
* @since 1.0
*/
@Parameter( property = "licensesOutputDirectory",
defaultValue = "${project.build.directory}/generated-resources/licenses" )
protected File licensesOutputDirectory;
/**
* If {@code true}, the mojo will delete all files from {@link #licensesOutputDirectory} and then download them all
* anew; otherwise the deletion before the download does not happen.
* <p>
* This may be useful if you have removed some dependencies and you want the stale license files to go away.
* <b>
* {@code cleanLicensesOutputDirectory = true} is not implied by {@link #forceDownload} because users may have
* other files there in {@link #licensesOutputDirectory} that were not downloaded by the plugin.
*
* @see #removeOrphanLicenseFiles
* @since 1.18
*/
@Parameter( property = "license.cleanLicensesOutputDirectory", defaultValue = "false" )
private boolean cleanLicensesOutputDirectory;
/**
* If {@code true} the files referenced from {@link AbstractLicensesXmlMojo#licensesOutputFile} before executing
* the mojo but not referenced from {@link AbstractLicensesXmlMojo#licensesOutputFile} after executing
* the mojo will be deleted; otherwise neither before:after diffing nor any file deletions will happen.
* <p>
* Compared to {@link #cleanLicensesOutputDirectory} that removes all files from {@link #licensesOutputDirectory}
* before downloading all licenses anew, the {@link #removeOrphanLicenseFiles} removes only files that
* are certainly not needed anymore, e.g. due to a removal of a dependency. {@link #removeOrphanLicenseFiles} thus
* allows to avoid downloading the license files of dependencies that were downloaded in the past and are still
* available in {@link #licensesOutputDirectory}.
*
* @see #cleanLicensesOutputDirectory
* @since 1.19
*/
@Parameter( property = "license.removeOrphanLicenseFiles", defaultValue = "true" )
private boolean removeOrphanLicenseFiles;
/**
* A file containing dependencies whose licenses could not be downloaded for some reason. The format is similar to
* {@link #licensesOutputFile} but the entries in {@link #licensesErrorsFile} have {@code <downloaderMessage>}
* elements attached to them. Those should explain what kind of error happened during the processing of the given
* dependency.
*
* @since 1.18
*/
@Parameter( property = "license.licensesErrorsFile",
defaultValue = "${project.build.directory}/generated-resources/licenses-errors.xml" )
private File licensesErrorsFile;
/**
* A filter to exclude some scopes.
*
* @since 1.0
*/
@Parameter( property = "license.excludedScopes", defaultValue = "system" )
private String excludedScopes;
/**
* A filter to include only some scopes, if let empty then all scopes will be used (no filter).
*
* @since 1.0
*/
@Parameter( property = "license.includedScopes" )
private String includedScopes;
/**
* A filter to exclude some types.
*
* @since 1.15
*/
@Parameter( property = "license.excludedTypes" )
private String excludedTypes;
/**
* A filter to include only some types, if let empty then all types will be used (no filter).
*
* @since 1.15
*/
@Parameter( property = "license.includedTypes" )
private String includedTypes;
/**
* A URL returning a plain text file that contains include/exclude artifact filters in the following format:
* <pre>
* {@code
* # this is a comment
* include gaPattern org\.my-org:my-artifact
* include gaPattern org\.other-org:other-artifact
* exclude gaPattern org\.yet-anther-org:.*
* include scope compile
* include scope test
* exclude scope system
* include type jar
* exclude type war
* }</pre>
*
* @since 1.18
*/
@Parameter( property = "license.artifactFiltersUrl" )
private String artifactFiltersUrl;
/**
* Settings offline flag (will not download anything if setted to true).
*
* @since 1.0
*/
@Parameter( defaultValue = "${settings.offline}" )
private boolean offline;
/**
* Before 1.18, {@link #quiet} having value {@code false} suppressed any license download related warnings in the
* log. After 1.18 (incl.), the behavior depends on the value of {@link #errorRemedy}:
* <table>
* <tr><th>quiet</th><th>errorRemedy</th><th>effective errorRemedy</th><tr>
* <tr><td>true</td><td>warn</td><td>ignore</td><tr>
* <tr><td>false</td><td>warn</td><td>warn</td><tr>
* <tr><td>true or false</td><td>ignore</td><td>ignore</td><tr>
* <tr><td>true or false</td><td>failFast</td><td>failFast</td><tr>
* <tr><td>true or false</td><td>xmlOutput</td><td>xmlOutput</td><tr>
* </table>
*
* @since 1.0
* @deprecated Use {@link #errorRemedy} instead
*/
@Parameter( defaultValue = "false" )
private boolean quiet;
/**
* What to do on any license download related error. The possible values are:
* <li>
* <ul>{@link ErrorRemedy#ignore}: all errors are ignored</ul>
* <ul>{@link ErrorRemedy#warn}: all errors are output to the log as warnings</ul>
* <ul>{@link ErrorRemedy#failFast}: a {@link MojoFailureException} is thrown on the first download related
* error</ul>
* <ul>{@link ErrorRemedy#xmlOutput}: error messages are added as {@code <downloaderMessages>} to
* {@link AbstractDownloadLicensesMojo#licensesErrorsFile}; in case there are error messages, the build will
* fail after processing all dependencies</ul>
* </li>
* @since 1.18
*/
@Parameter( property = "license.errorRemedy", defaultValue = "warn" )
protected ErrorRemedy errorRemedy;
/**
* If {@code true}, all encountered dependency license URLs are downloaded, no matter what is there in
* {@link #licensesConfigFile} and {@link #licensesOutputFile}; otherwise {@link #licensesConfigFile},
* {@link #licensesOutputFile} (eventually persisted from a previous build) and the content of
* {@link #licensesOutputDirectory} are considered sources of valid information - i.e. only URLs that do not appear
* to have been downloaded in the past will be downloaded.
* <b>
* If your {@link #licensesOutputDirectory} contains only license files downloaded by this plugin, you may consider
* combining {@link #forceDownload} with setting {@link #cleanLicensesOutputDirectory} {@code true}
*
* @since 1.18
*/
@Parameter( property = "license.forceDownload", defaultValue = "false" )
private boolean forceDownload;
/**
* Include transitive dependencies when downloading license files.
*
* @since 1.0
*/
@Parameter( defaultValue = "true" )
private boolean includeTransitiveDependencies;
/**
* Exclude transitive dependencies from excluded artifacts.
*
* @since 1.13
*/
@Parameter( property = "license.excludeTransitiveDependencies", defaultValue = "false" )
private boolean excludeTransitiveDependencies;
/**
* If {@code true} both optional and non-optional dependencies will be included in the list of artifacts for
* creating the license report; otherwise only non-optional dependencies will be considered.
*
* @since 1.19
*/
@Parameter( property = "license.includeOptional", defaultValue = "true" )
boolean includeOptional;
/**
* Get declared proxies from the {@code settings.xml} file.
*
* @since 1.4
*/
@Parameter( defaultValue = "${settings.proxies}", readonly = true )
private List<Proxy> proxies;
/**
* A flag to organize the licenses by dependencies. When this is done, each dependency will
* get its full license file, even if already downloaded for another dependency.
*
* @since 1.9
*/
@Parameter( property = "license.organizeLicensesByDependencies", defaultValue = "false" )
protected boolean organizeLicensesByDependencies;
@Parameter( property = "license.sortByGroupIdAndArtifactId", defaultValue = "false" )
private boolean sortByGroupIdAndArtifactId;
/**
* A filter to exclude some GroupIds
* This is a regular expression that is applied to groupIds (not an ant pattern).
*
* @since 1.11
*/
@Parameter( property = "license.excludedGroups" )
private String excludedGroups;
/**
* A filter to include only some GroupIds
* This is a regular expression applied to artifactIds.
*
* @since 1.11
*/
@Parameter( property = "license.includedGroups" )
private String includedGroups;
/**
* A filter to exclude some ArtifactsIds
* This is a regular expression applied to artifactIds.
*
* @since 1.11
*/
@Parameter( property = "license.excludedArtifacts" )
private String excludedArtifacts;
/**
* A filter to include only some ArtifactsIds
* This is a regular expression applied to artifactIds.
*
* @since 1.11
*/
@Parameter( property = "license.includedArtifacts" )
private String includedArtifacts;
/**
* The Maven Project Object
*
* @since 1.0
*/
@Parameter( defaultValue = "${project}", readonly = true )
protected MavenProject project;
/**
* List of regexps/replacements applied to the license urls prior to download.
*
* <p>License urls that match a regular expression will be replaced by the corresponding
* replacement. Replacement is performed with {@link java.util.regex.Matcher#replaceAll(String)
* java.util.regex.Matcher#replaceAll(String)} so you can take advantage of
* capturing groups to facilitate flexible transformations.</p>
*
* <p>If the replacement element is omitted, this is equivalent to an empty replacement string.</p>
*
* <pre>
* {@code
*
* <licenseUrlReplacements>
* <licenseUrlReplacement>
* <regexp>\Qhttps://glassfish.java.net/public/CDDL+GPL_1_1.html\E</regexp>
* <replacement>https://oss.oracle.com/licenses/CDDL+GPL-1.1</replacement>
* </licenseUrlReplacement>
* <licenseUrlReplacement>
* <regexp>https://(.*)</regexp><!-- replace https with http -->
* <replacement>http://$1</replacement>
* </licenseUrlReplacement>
* <licenseUrlReplacement>
* <regexp>^https?://github\.com/([^/]+)/([^/]+)/blob/(.*)$</regexp><!-- replace GitHub web UI with raw -->
* <replacement>https://raw.githubusercontent.com/$1/$2/$3</replacement>
* </licenseUrlReplacement>
* </licenseUrlReplacements>
* }
* </pre>
* <p>
* Relationship to other parameters:
* <ul>
* <li>License names and license URLs {@link #licensesConfigFile} is applied before
* {@link #licenseUrlReplacements}</li>
* <li>{@link #licenseUrlReplacements} are applied before {@link #licenseUrlFileNames}</li>
* <li>{@link #licenseUrlFileNames} have higher precedence than {@code <file>} elements in
* {@link #licensesConfigFile}</li>
* <li>{@link #licenseUrlFileNames} are ignored when {@link #organizeLicensesByDependencies} is {@code true}</li>
* </ul>
*
* @since 1.17
*/
@Parameter
protected List<LicenseUrlReplacement> licenseUrlReplacements;
/**
* A map that helps to select local files names for the content downloaded from license URLs.
* <p>
* Keys in the map are the local file names. These files will be created under {@link #licensesOutputDirectory}.
* <p>
* Values are white space ({@code " \t\n\r"}) separated lists of regular expressions that will be used to match
* license URLs. The regular expressions are compiled using {@link Pattern#CASE_INSENSITIVE}. Note that various
* characters that commonly occur in URLs have special meanings in regular extensions. Therefore, consider using
* regex quoting as described in {@link Pattern} - e.g. {@code http://example\.com} or
* {@code \Qhttp://example.com\E}
* <p>
* In addition to URL patterns, the list can optionally contain a sha1 checksum of the expected content. This is to
* ensure that the content delivered by a URL does not change without notice. Note that strict checking
* of the checksum happens only when {@link #forceDownload} is {@code true}. Otherwise the mojo assumes that the URL
* -> local name mapping is correct and downloads from the URL only if the local file does not exist.
* <p>
* A special value-less entry {@code <spdx/>} can be used to activate built-in license names that are based on
* license IDs from <a href="https://spdx.org/licenses/">https://spdx.org/licenses</a>. The built-in SPDX mappings
* can be overridden by the subsequent entries. To see which SPDX mappings are built-in, add the {@code <spdx/>}
* entry and run the mojo with debug log level, e.g. using {@code -X} or
* {-Dorg.slf4j.simpleLogger.log.org.codehaus.mojo.license=debug} on the command line.
* <p>
* An example:
* <pre>
* {@code
* <licenseUrlFileNames>
* <spdx/><!-- A special element to activate built-in file name entries based on spdx.org license IDs -->
* <bsd-antlr.html>
* sha1:81ffbd1712afe8cdf138b570c0fc9934742c33c1
* https?://(www\.)?antlr\.org/license\.html
* </bsd-antlr.html>
* <cddl-gplv2-ce.txt>
* sha1:534a3fc9ae1076409bb00d01127dbba1e2620e92
* \Qhttps://raw.githubusercontent.com/javaee/activation/master/LICENSE.txt\E
* </cddl-gplv2-ce.txt>
* </licenseUrlFileNames>
* }
* </pre>
* <p>
* Relationship to other parameters:
* <ul>
* <li>License names and license URLs {@link #licensesConfigFile} is applied before
* {@link #licenseUrlReplacements}</li>
* <li>{@link #licenseUrlReplacements} are applied before {@link #licenseUrlFileNames}</li>
* <li>{@link #licenseUrlFileNames} have higher precedence than {@code <file>} elements in
* {@link #licensesConfigFile}</li>
* <li>{@link #licenseUrlFileNames} are ignored when {@link #organizeLicensesByDependencies} is {@code true}</li>
* </ul>
*
* @since 1.18
*/
@Parameter
protected Map<String, String> licenseUrlFileNames;
/**
* A list of regexp:replacement pairs that should be applied to file names for storing licenses.
* <p>
* Note that these patterns are not applied to file names defined in {@link #licenseUrlFileNames}.
*
* @since 1.18
*/
@Parameter
protected List<LicenseUrlReplacement> licenseUrlFileNameSanitizers;
/**
* If {@code true}, {@link #licensesOutputFile} and {@link #licensesErrorsFile} will contain {@code <version>}
* elements for each {@code <dependency>}; otherwise the {@code <version>} {@link #licensesOutputFile} and
* {@link #licensesErrorsFile} elements will not be appended under {@code <dependency>} elements in
* <b>
* Might be useful if you want to keep the {@link #licensesOutputFile} under source control and you do not want to
* see the changing dependency versions there.
*
* @since 1.18
*/
@Parameter( property = "license.writeVersions", defaultValue = "true" )
private boolean writeVersions;
/**
* Connect timeout in milliseconds passed to the HTTP client when downloading licenses from remote URLs.
*
* @since 1.18
*/
@Parameter( property = "license.connectTimeout", defaultValue = "5000" )
private int connectTimeout;
/**
* Socket timeout in milliseconds passed to the HTTP client when downloading licenses from remote URLs.
*
* @since 1.18
*/
@Parameter( property = "license.socketTimeout", defaultValue = "5000" )
private int socketTimeout;
/**
* Connect request timeout in milliseconds passed to the HTTP client when downloading licenses from remote URLs.
*
* @since 1.18
*/
@Parameter( property = "license.connectionRequestTimeout", defaultValue = "5000" )
private int connectionRequestTimeout;
// ----------------------------------------------------------------------
// Plexus Components
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// Private Fields
// ----------------------------------------------------------------------
private PreferredFileNames preferredFileNames;
/**
* A map from the license URLs to file names (without path) where the
* licenses were downloaded. This helps the plugin to avoid downloading
* the same license multiple times.
*/
private Cache cache;
private int downloadErrorCount = 0;
private ArtifactFilters artifactFilters;
private final Set<String> orphanFileNames = new HashSet<>();
protected abstract boolean isSkip();
protected MavenProject getProject()
{
return project;
}
protected abstract Set<MavenProject> getDependencies();
// ----------------------------------------------------------------------
// Mojo Implementation
// ----------------------------------------------------------------------
@SuppressWarnings( "unchecked" )
protected SortedMap<String, MavenProject> getDependencies( MavenProject project )
{
return dependenciesTool.loadProjectDependencies(
new ResolvedProjectDependencies( project.getArtifacts(), project.getDependencyArtifacts() ),
this, localRepository, remoteRepositories, null );
}
/**
* {@inheritDoc}
* @throws MojoFailureException
*/
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( isSkip() )
{
getLog().info( "skip flag is on, will skip goal." );
return;
}
this.errorRemedy = getEffectiveErrorRemedy( this.quiet, this.errorRemedy );
this.preferredFileNames = PreferredFileNames.build( licensesOutputDirectory, licenseUrlFileNames, getLog() );
this.cache = new Cache( licenseUrlFileNames != null && !licenseUrlFileNames.isEmpty() );
initDirectories();
final LicenseMatchers matchers = LicenseMatchers.load( licensesConfigFile );
if ( !forceDownload )
{
try
{
final List<ProjectLicenseInfo> projectLicenseInfos =
LicenseSummaryReader.parseLicenseSummary( licensesOutputFile );
for ( ProjectLicenseInfo dep : projectLicenseInfos )
{
for ( ProjectLicense lic : dep.getLicenses() )
{
final String fileName = lic.getFile();
if ( fileName != null )
{
orphanFileNames.add( fileName );
final String url = lic.getUrl();
if ( url != null )
{
final File file = new File( licensesOutputDirectory, fileName );
if ( file.exists() )
{
final LicenseDownloadResult entry =
LicenseDownloadResult.success( file, FileUtil.sha1( file.toPath() ), false );
cache.put( url, entry );
}
}
}
}
}
}
catch ( Exception e )
{
throw new MojoExecutionException( "Unable to process license summary file: " + licensesOutputFile, e );
}
}
final Set<MavenProject> dependencies = getDependencies();
// The resulting list of licenses after dependency resolution
final List<ProjectLicenseInfo> depProjectLicenses = new ArrayList<>();
try ( LicenseDownloader licenseDownloader =
new LicenseDownloader( findActiveProxy(), connectTimeout, socketTimeout, connectionRequestTimeout ) )
{
for ( MavenProject project : dependencies )
{
Artifact artifact = project.getArtifact();
getLog().debug( "Checking licenses for project " + artifact );
final ProjectLicenseInfo depProject = createDependencyProject( project );
matchers.replaceMatches( depProject );
depProjectLicenses.add( depProject );
}
if ( !offline )
{
/* First save the matching URLs into the cache */
for ( ProjectLicenseInfo depProject : depProjectLicenses )
{
downloadLicenses( licenseDownloader, depProject, true );
}
getLog().debug( "Finished populating cache" );
/*
* Then attempt to download the rest of the URLs using the available cache entries to select local
* file names based on file content sha1
*/
for ( ProjectLicenseInfo depProject : depProjectLicenses )
{
downloadLicenses( licenseDownloader, depProject, false );
}
}
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
try
{
if ( sortByGroupIdAndArtifactId )
{
sortByGroupIdAndArtifactId( depProjectLicenses );
}
List<ProjectLicenseInfo> depProjectLicensesWithErrors = filterErrors( depProjectLicenses );
writeLicenseSummary( depProjectLicenses, licensesOutputFile, writeVersions );
if ( depProjectLicensesWithErrors != null && !depProjectLicensesWithErrors.isEmpty() )
{
writeLicenseSummary( depProjectLicensesWithErrors, licensesErrorsFile, writeVersions );
}
removeOrphanFiles( depProjectLicenses );
}
catch ( Exception e )
{
throw new MojoExecutionException( "Unable to write license summary file: " + licensesOutputFile, e );
}
switch ( errorRemedy )
{
case ignore:
case failFast:
/* do nothing */
break;
case warn:
getLog().warn( "There were " + downloadErrorCount + " download errors - check the warnings above" );
break;
case xmlOutput:
if ( downloadErrorCount > 0 )
{
throw new MojoFailureException( "There were " + downloadErrorCount + " download errors - check "
+ licensesErrorsFile.getAbsolutePath() );
}
break;
default:
throw new IllegalStateException( "Unexpected value of " + ErrorRemedy.class.getName() + ": "
+ errorRemedy );
}
}
private void removeOrphanFiles( List<ProjectLicenseInfo> deps )
{
if ( removeOrphanLicenseFiles )
{
for ( ProjectLicenseInfo dep : deps )
{
for ( ProjectLicense lic : dep.getLicenses() )
{
orphanFileNames.remove( lic.getFile() );
}
}
for ( String fileName : orphanFileNames )
{
final File file = new File( licensesOutputDirectory, fileName );
if ( file.exists() )
{
getLog().info( "Removing orphan license file \"" + file + "\"" );
file.delete();
}
}
}
}
/**
* Removes from the given {@code depProjectLicenses} those elements which have non-empty
* {@link ProjectLicenseInfo#getDownloaderMessages()} and adds those to the resulting {@link List}.
*
* @param depProjectLicenses the list of {@link ProjectLicenseInfo}s to filter
* @return a new {@link List} of {@link ProjectLicenseInfo}s containing only elements with non-empty
* {@link ProjectLicenseInfo#getDownloaderMessages()}
*/
private List<ProjectLicenseInfo> filterErrors( List<ProjectLicenseInfo> depProjectLicenses )
{
final List<ProjectLicenseInfo> result = new ArrayList<>();
final Iterator<ProjectLicenseInfo> it = depProjectLicenses.iterator();
while ( it.hasNext() )
{
final ProjectLicenseInfo dep = it.next();
final List<String> messages = dep.getDownloaderMessages();
if ( messages != null && !messages.isEmpty() )
{
it.remove();
result.add( dep );
}
}
return result;
}
private static ErrorRemedy getEffectiveErrorRemedy( boolean quiet, ErrorRemedy errorRemedy )
{
switch ( errorRemedy )
{
case warn:
return quiet ? ErrorRemedy.ignore : ErrorRemedy.warn;
default:
return errorRemedy;
}
}
private void sortByGroupIdAndArtifactId( List<ProjectLicenseInfo> depProjectLicenses )
{
Comparator<ProjectLicenseInfo> comparator = new Comparator<ProjectLicenseInfo>()
{
public int compare( ProjectLicenseInfo info1, ProjectLicenseInfo info2 )
{
//ProjectLicenseInfo::getId() can not be used because . is before : thus a:b.c would be after a.b:c
return ( info1.getGroupId() + "+" + info1.getArtifactId() ).compareTo( info2.getGroupId()
+ "+" + info2.getArtifactId() );
}
};
Collections.sort( depProjectLicenses, comparator );
}
// ----------------------------------------------------------------------
// MavenProjectDependenciesConfigurator Implementation
// ----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public boolean isIncludeTransitiveDependencies()
{
return includeTransitiveDependencies;
}
/**
* {@inheritDoc}
*/
public boolean isExcludeTransitiveDependencies()
{
return excludeTransitiveDependencies;
}
/** {@inheritDoc} */
public ArtifactFilters getArtifactFilters()
{
if ( artifactFilters == null )
{
artifactFilters = ArtifactFilters.of( includedGroups, excludedGroups, includedArtifacts, excludedArtifacts,
includedScopes, excludedScopes, includedTypes, excludedTypes,
includeOptional, artifactFiltersUrl , getEncoding() );
}
return artifactFilters;
}
/**
* {@inheritDoc}
*/
public boolean isVerbose()
{
return getLog().isDebugEnabled();
}
// ----------------------------------------------------------------------
// Private Methods
// ----------------------------------------------------------------------
private void initDirectories()
throws MojoExecutionException
{
try
{
if ( licensesOutputDirectory.exists() )
{
if ( cleanLicensesOutputDirectory )
{
getLog().info( "Cleaning licensesOutputDirectory '" + licensesOutputDirectory + "'" );
FileUtils.cleanDirectory( licensesOutputDirectory );
}
}
else
{
FileUtil.createDirectoryIfNecessary( licensesOutputDirectory );
}
FileUtil.createDirectoryIfNecessary( licensesOutputFile.getParentFile() );
FileUtil.createDirectoryIfNecessary( licensesErrorsFile.getParentFile() );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to create a directory...", e );
}
}
/** {@inheritDoc} */
protected Path[] getAutodetectEolFiles()
{
return new Path[] { licensesConfigFile.toPath(), project.getBasedir().toPath().resolve( "pom.xml" ) };
}
private Proxy findActiveProxy()
throws MojoExecutionException
{
for ( Proxy proxy : proxies )
{
if ( proxy.isActive() && "http".equals( proxy.getProtocol() ) )
{
return proxy;
}
}
return null;
}
/**
* Create a simple DependencyProject object containing the GAV and license info from the Maven Artifact
*
* @param depMavenProject the dependency maven project
* @return DependencyProject with artifact and license info
*/
private ProjectLicenseInfo createDependencyProject( MavenProject depMavenProject )
{
ProjectLicenseInfo dependencyProject =
new ProjectLicenseInfo( depMavenProject.getGroupId(), depMavenProject.getArtifactId(),
depMavenProject.getVersion() );
@SuppressWarnings( "unchecked" )
List<License> licenses = depMavenProject.getLicenses();
for ( License license : licenses )
{
dependencyProject.addLicense( new ProjectLicense( license ) );
}
return dependencyProject;
}
/**
* Determine filename to use for downloaded license file. The file name is based on the configured name of the
* license (if available) and the remote filename of the license.
*
* @param depProject the project containing the license
* @param licenseUrl the license url
* @param licenseName the license name
* @param string
* @return A filename to be used for the downloaded license file
* @throws URISyntaxException
*/
private FileNameEntry getLicenseFileName( ProjectLicenseInfo depProject, final String url,
final String licenseName, String licenseFileName )
throws URISyntaxException
{
final URI licenseUrl = new URI( url );
File licenseUrlFile = new File( licenseUrl.getPath() );
if ( organizeLicensesByDependencies )
{
if ( licenseFileName != null && !licenseFileName.isEmpty() )
{
return new FileNameEntry( new File( licensesOutputDirectory, new File( licenseFileName ).getName() ),
false, null );
}
licenseFileName = String.format( "%s.%s%s", depProject.getGroupId(), depProject.getArtifactId(),
licenseName != null
? "_" + licenseName
: "" ).replaceAll( "\\s+", "_" );
}
else
{
final FileNameEntry preferredFileNameEntry = preferredFileNames.getEntryByUrl( url );
if ( preferredFileNameEntry != null )
{
return preferredFileNameEntry;
}
if ( licenseFileName != null && !licenseFileName.isEmpty() )
{
return new FileNameEntry( new File( licensesOutputDirectory,
new File( licenseFileName ).getName() ),
false, null );
}
licenseFileName = licenseUrlFile.getName();
if ( licenseName != null )
{
licenseFileName = licenseName + " - " + licenseUrlFile.getName();