View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   */
19  package org.apache.rat;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.FilenameFilter;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.io.OutputStreamWriter;
28  import java.io.PrintWriter;
29  import java.net.MalformedURLException;
30  import java.net.URI;
31  import java.net.URL;
32  import java.nio.charset.StandardCharsets;
33  import java.nio.file.Files;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.Objects;
37  import java.util.SortedSet;
38  import java.util.TreeSet;
39  import java.util.function.Consumer;
40  
41  import org.apache.commons.io.filefilter.FalseFileFilter;
42  import org.apache.commons.io.filefilter.IOFileFilter;
43  import org.apache.commons.io.function.IOSupplier;
44  import org.apache.rat.config.AddLicenseHeaders;
45  import org.apache.rat.license.ILicense;
46  import org.apache.rat.license.ILicenseFamily;
47  import org.apache.rat.license.LicenseFamilySetFactory;
48  import org.apache.rat.license.LicenseSetFactory;
49  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
50  import org.apache.rat.report.IReportable;
51  import org.apache.rat.utils.Log;
52  import org.apache.rat.utils.ReportingSet;
53  
54  /**
55   * A configuration object is used by the front end to invoke the
56   * {@link Reporter}. The sole purpose of the front ends is to create the
57   * configuration and invoke the {@link Reporter}.
58   */
59  public class ReportConfiguration {
60  
61      public enum Processing {
62          /** List file as present only */
63          NOTIFICATION("List file as present"),
64          /** List all present licenses */
65          PRESENCE("List any licenses found"),
66          /** List all present licenses and unknown licenses */
67          ABSENCE("List licenses found and any unknown licences");
68  
69      private final String description;
70      Processing(String description) {
71          this.description = description;
72      }
73      public String desc() { return description; }
74      }
75  
76  
77      private final ReportingSet<ILicenseFamily> families;
78      private final ReportingSet<ILicense> licenses;
79      private final SortedSet<String> approvedLicenseCategories;
80      private final SortedSet<String> removedLicenseCategories;
81      private boolean addingLicenses;
82      private boolean addingLicensesForced;
83      private String copyrightMessage;
84      private IOSupplier<OutputStream> out;
85      private boolean styleReport;
86      private IOSupplier<InputStream> styleSheet;
87      private IReportable reportable;
88      private FilenameFilter filesToIgnore;
89      private IOFileFilter directoriesToIgnore;
90      private final Log log;
91      private LicenseFilter listFamilies;
92      private LicenseFilter listLicenses;
93      private boolean dryRun;
94      private Processing archiveProcessing;
95      private Processing standardProcessing;
96      /**
97       * Constructor
98       * @param log The Log implementation that messages will be written to.
99       */
100     public ReportConfiguration(Log log) {
101         this.log = log;
102         families = new ReportingSet<>(LicenseFamilySetFactory.emptyLicenseFamilySet()).setLog(log)
103                 .setMsgFormat( s -> String.format("Duplicate LicenseFamily category: %s", s.getFamilyCategory()));
104         licenses = new ReportingSet<>(LicenseSetFactory.emptyLicenseSet()).setLog(log)
105                 .setMsgFormat( s -> String.format( "Duplicate License %s (%s) of type %s", s.getName(), s.getId(), s.getLicenseFamily().getFamilyCategory()));
106         approvedLicenseCategories = new TreeSet<>();
107         removedLicenseCategories = new TreeSet<>();
108         styleReport = true;
109         listFamilies = Defaults.LIST_FAMILIES;
110         listLicenses = Defaults.LIST_LICENSES;
111         dryRun = false;
112     }
113 
114     /**
115      * Retrieves the archive processing type.
116      * @return The archive processing type.
117      */
118     public Processing getArchiveProcessing() {
119         return archiveProcessing == null ? Defaults.ARCHIVE_PROCESSING : archiveProcessing;
120     }
121 
122     /**
123      * Sets the archive processing type.  If not set will default to NOTIFICATION.
124      * @param archiveProcessing the type of processing archives should have.
125      */
126     public void setArchiveProcessing(Processing archiveProcessing) {
127         this.archiveProcessing = archiveProcessing;
128     }
129 
130     /**
131      * Retrieves the archive processing type.
132      * @return The archive processing type.
133      */
134     public Processing getStandardProcessing() {
135         return standardProcessing == null ? Defaults.STANDARD_PROCESSING : standardProcessing;
136     }
137 
138     /**
139      * Sets the archive processing type.  If not set will default to NOTIFICATION.
140      * @param standardProcessing the type of processing archives should have.
141      */
142     public void setStandardProcessing(Processing standardProcessing) {
143         this.standardProcessing = standardProcessing;
144     }
145 
146     /**
147      * Retrieves the Log that was provided in the constructor.
148      * @return the Log for the system.
149      */
150     public Log getLog() {
151         return log;
152     }
153     /**
154      * Set the log level for reporting collisions in the set of license families.
155      * <p>NOTE: should be set before licenses or license families are added.</p>
156      * @param level The log level to use.
157      */
158     public void logFamilyCollisions(Log.Level level) {
159         families.setLogLevel(level);
160     }
161     
162     /**
163      * Sets the reporting option for duplicate license families.
164      * @param state The ReportingSet.Option to use for reporting.
165      */
166     public void familyDuplicateOption(ReportingSet.Options state) {
167         families.setDuplicateOption(state);
168     }
169 
170     /**
171      * Sets the log level for reporting license collisions.
172      * @param level The log level.
173      */
174     public void logLicenseCollisions(Log.Level level) {
175         licenses.setLogLevel(level);
176     }
177     
178     /**
179      * Sets the reporting option for duplicate licenses.
180      * @param state the ReportingSt.Option to use for reporting.
181      */
182     public void licenseDuplicateOption(ReportingSet.Options state) {
183         licenses.setDuplicateOption(state);
184     }
185     
186     /**
187      * Set the level of license families that should be output in the XML document.
188      * @param filter the license families to list.
189      */
190     public void listFamilies(LicenseFilter filter) {
191         listFamilies = filter;
192     }
193     
194     public LicenseFilter listFamilies() {
195         return listFamilies;
196     }
197     
198     /**
199      * Set the level of licenses that should be output in the XML document.
200      * @param filter the licenses to list.
201      */
202     public void listLicenses(LicenseFilter filter) {
203         listLicenses = filter;
204     }
205     
206     public LicenseFilter listLicenses() {
207         return listLicenses;
208     }
209     
210     /**
211      * Sets the dry run flag.
212      * @param state the state for the dry run flag.
213      */
214     public void setDryRun(boolean state) {
215         dryRun = state;
216     }
217     
218     /**
219      * Returns the state of the dry run flag.
220      * @return the state of the dry run flag.
221      */
222     public boolean isDryRun() {
223         return dryRun;
224     }
225     
226     /**
227      * Gets the file name filter for files to ignore.
228      * @return The filename filter that identifies files to ignore.
229      */
230     public FilenameFilter getFilesToIgnore() {
231         return filesToIgnore == null ? Defaults.FILES_TO_IGNORE : filesToIgnore;
232     }
233 
234     /**
235      * Sets the files name filter for files to ignore.
236      * If not set or set to {@code null} uses the Default.FILES_TO_IGNORE value.
237      * @param filesToIgnore the filename filter to filter the input files.
238      * @see Defaults#FILES_TO_IGNORE
239      */
240     public void setFilesToIgnore(FilenameFilter filesToIgnore) {
241         this.filesToIgnore = filesToIgnore;
242     }
243 
244     /**
245      * Gets the directories filter.
246      * @return the directories filter.
247      */
248     public IOFileFilter getDirectoriesToIgnore() {
249         return directoriesToIgnore == null ? Defaults.DIRECTORIES_TO_IGNORE : directoriesToIgnore;
250     }
251 
252     /**
253      * Sets the directories to ignore.
254      * If {@code directoriesToIgnore} is null removes all directories to ignore.
255      * If not set {@code Defaults.DIRECTORIES_TO_IGNORE} is used.
256      * @param directoriesToIgnore the filter that defines the directories to ignore.
257      * @see Defaults#DIRECTORIES_TO_IGNORE
258      */
259     public void setDirectoriesToIgnore(IOFileFilter directoriesToIgnore) {
260         this.directoriesToIgnore = directoriesToIgnore == null ? FalseFileFilter.FALSE : directoriesToIgnore;
261     }
262 
263     /**
264      * Adds a directory filter to the directories to ignore.
265      * @param directoryToIgnore the directory filter to add.
266      */
267     public void addDirectoryToIgnore(IOFileFilter directoryToIgnore) {
268         this.directoriesToIgnore = this.directoriesToIgnore.or(directoryToIgnore);
269     }
270 
271     /**
272      * @return the thing being reported on.
273      */
274     public IReportable getReportable() {
275         return reportable;
276     }
277 
278     /**
279      * @param reportable the thing being reported on.
280      */
281     public void setReportable(IReportable reportable) {
282         this.reportable = reportable;
283     }
284 
285     /**
286      * @return the Supplier of the InputStream that is the XSLT style sheet to style
287      * the report with.
288      */
289     public IOSupplier<InputStream> getStyleSheet() {
290         return styleSheet;
291     }
292 
293     /**
294      * Sets the style sheet for custom processing. The IOSupplier may be called
295      * multiple times, so the input stream must be able to be opened and closed
296      * multiple times.
297      * 
298      * @param styleSheet the XSLT style sheet to style the report with.
299      */
300     public void setStyleSheet(IOSupplier<InputStream> styleSheet) {
301         this.styleSheet = styleSheet;
302     }
303 
304     /**
305      * Adds the licenses and approved licenses from the defaults object to the
306      * configuration. <em>Side effect: </em> if the report should be styled and no
307      * style sheet has been set the plain stylesheet from the defaults will be used.
308      * 
309      * @param defaults The defaults to set.
310      */
311     public void setFrom(Defaults defaults) {
312         addLicensesIfNotPresent(defaults.getLicenses(LicenseFilter.ALL));
313         addApprovedLicenseCategories(defaults.getLicenseIds(LicenseFilter.APPROVED));
314         if (isStyleReport() && getStyleSheet() == null) {
315             setStyleSheet(Defaults.getPlainStyleSheet());
316         }
317     }
318 
319     /**
320      * 
321      * @param styleSheet the XSLT style sheet to style the report with.
322      */
323     public void setStyleSheet(File styleSheet) {
324         Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
325         setStyleSheet(styleSheet.toURI());
326     }
327 
328     /**
329      * Sets the style sheet for custom processing. The stylesheet may be opened
330      * multiple times so the URI must be capable of being opened multiple times.
331      * 
332      * @param styleSheet the URI of the XSLT style sheet to style the report with.
333      */
334     public void setStyleSheet(URI styleSheet) {
335         Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
336         try {
337             setStyleSheet(styleSheet.toURL());
338         } catch (MalformedURLException e) {
339             throw new ConfigurationException("Unable to process stylesheet", e);
340         }
341     }
342 
343     /**
344      * Sets the style sheet for custom processing. The stylesheet may be opened
345      * multiple times so the URL must be capable of being opened multiple times.
346      * 
347      * @param styleSheet the URL of the XSLT style sheet to style the report with.
348      */
349     public void setStyleSheet(URL styleSheet) {
350         Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
351         setStyleSheet(styleSheet::openStream);
352     }
353 
354     /**
355      * @return {@code true} if the XML report should be styled.
356      */
357     public boolean isStyleReport() {
358         return styleReport;
359     }
360 
361     /**
362      * @param styleReport specifies whether the XML report should be styled.
363      */
364     public void setStyleReport(boolean styleReport) {
365         this.styleReport = styleReport;
366     }
367 
368     /**
369      * Sets the supplier for the output stream. The supplier may be called multiple
370      * times to provide the stream. Suppliers should prepare streams that are
371      * appended to and that can be closed. If an {@code OutputStream} should not be
372      * closed consider wrapping it in a {@code NoCloseOutputStream}
373      * 
374      * @param out The OutputStream supplier that provides the output stream to write
375      * the report to. A null value will use System.out.
376      * @see NoCloseOutputStream
377      */
378     public void setOut(IOSupplier<OutputStream> out) {
379         this.out = out;
380     }
381 
382     /**
383      * Sets the OutputStream supplier to use the specified file. The file may be
384      * opened and closed several times. File is deleted first and then may be
385      * repeatedly opened in append mode.
386      * 
387      * @see #setOut(IOSupplier)
388      * @param file The file to create the supplier with.
389      */
390     public void setOut(File file) {
391         Objects.requireNonNull(file, "output file should not be null");
392         if (file.exists()) {
393             try {
394                 Files.delete(file.toPath());
395             } catch (IOException e) {
396                 log.warn("Unable to delete file:"+file);
397             }
398         }
399         setOut(() -> new FileOutputStream(file, true));
400     }
401 
402     /**
403      * Returns the output stream supplier. If no stream has been set returns a
404      * supplier for System.out.
405      * 
406      * @return The supplier of the output stream to write the report to.
407      */
408     public IOSupplier<OutputStream> getOutput() {
409         return out == null ? () -> new NoCloseOutputStream(System.out) : out;
410     }
411 
412     /**
413      * @return A supplier for a PrintWriter that wraps the output stream.
414      * @see #getOutput()
415      */
416     public IOSupplier<PrintWriter> getWriter() {
417         return () -> new PrintWriter(new OutputStreamWriter(getOutput().get(), StandardCharsets.UTF_8));
418     }
419 
420     /**
421      * Adds a license to the list of licenses. Does not add the license to the list
422      * of approved licenses.
423      * 
424      * @param license The license to add to the list of licenses.
425      */
426     public void addLicense(ILicense license) {
427         if (license != null) {
428             this.licenses.add(license);
429             this.families.addIfNotPresent(license.getLicenseFamily());
430         }
431     }
432 
433     /**
434      * Adds a license to the list of licenses. Does not add the license to the list
435      * of approved licenses.
436      * 
437      * @param builder The license builder to build and add to the list of licenses.
438      * @return The ILicense implementation that was added.
439      */
440     public ILicense addLicense(ILicense.Builder builder) {
441         if (builder != null) {
442             ILicense license = builder.setLicenseFamilies(families).build();
443             this.licenses.add(license);
444             return license;
445         }
446         return null;
447     }
448 
449     /**
450      * Adds multiple licenses to the list of licenses. Does not add the licenses to
451      * the list of approved licenses.
452      *
453      * @param licenses The licenses to add.
454      */
455     public void addLicenses(Collection<ILicense> licenses) {
456         this.licenses.addAll(licenses);
457         licenses.stream().map(ILicense::getLicenseFamily).forEach(families::add);
458     }
459 
460     /**
461      * Adds multiple licenses to the list of licenses. Does not add the licenses to
462      * the list of approved licenses.
463      *
464      * @param licenses The licenses to add.
465      */
466     public void addLicensesIfNotPresent(Collection<ILicense> licenses) {
467         this.licenses.addAllIfNotPresent(licenses);
468         licenses.stream().map(ILicense::getLicenseFamily).forEach(families::addIfNotPresent);
469     }
470     
471     /**
472      * Adds a license family to the list of families. Does not add the family to the
473      * list of approved licenses.
474      * 
475      * @param family The license family to add to the list of license families.
476      */
477     public void addFamily(ILicenseFamily family) {
478         if (family != null) {
479             this.families.add(family);
480         }
481     }
482 
483     /**
484      * Adds a license family to the list of families. Does not add the family to the
485      * list of approved licenses.
486      * 
487      * @param builder The licenseFamily.Builder to build and add to the list of
488      * licenses.
489      */
490     public void addFamily(ILicenseFamily.Builder builder) {
491         if (builder != null) {
492             this.families.add(builder.build());
493         }
494     }
495 
496     /**
497      * Adds multiple families to the list of license families. Does not add the
498      * licenses to the list of approved licenses.
499      *
500      * @param families The license families to add.
501      */
502     public void addFamilies(Collection<ILicenseFamily> families) {
503         this.families.addAll(families);
504     }
505 
506     /**
507      * Adds an ILicenseFamily to the list of approved licenses.
508      *
509      * @param approvedILicenseFamily the LicenseFamily to add.
510      */
511     public void addApprovedLicenseCategory(ILicenseFamily approvedILicenseFamily) {
512         approvedLicenseCategories.add(approvedILicenseFamily.getFamilyCategory());
513     }
514 
515     /**
516      * Adds a license family category (id) to the list of approved licenses
517      * 
518      * @param familyCategory the category to add.
519      */
520     public void addApprovedLicenseCategory(String familyCategory) {
521         approvedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
522     }
523 
524     /**
525      * Adds a collection of license family categories to the set of approved license
526      * names.
527      *
528      * @param approvedLicenseCategories set of approved license categories.
529      */
530     public void addApprovedLicenseCategories(Collection<String> approvedLicenseCategories) {
531         approvedLicenseCategories.forEach(this::addApprovedLicenseCategory);
532     }
533 
534     /**
535      * Adds a license family category to the list of approved licenses. <em>Once a
536      * license has been removed from the approved list it cannot be re-added</em>
537      * 
538      * @param familyCategory the category to add.
539      */
540     public void removeApprovedLicenseCategory(String familyCategory) {
541         removedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
542     }
543 
544     /**
545      * Removes a license family category from the list of approved licenses.
546      * <em>Once a license has been removed from the approved list it cannot be
547      * re-added</em>
548      * 
549      * @param familyCategory the family category to remove.
550      */
551     public void removeApprovedLicenseCategories(Collection<String> familyCategory) {
552         familyCategory.forEach(this::removeApprovedLicenseCategory);
553     }
554 
555     /**
556      * Gets the SortedSet of approved license categories. <em>Once a license has
557      * been removed from the approved list it cannot be re-added</em>
558      * 
559      * @return the Sorted set of approved license categories.
560      */
561     public SortedSet<String> getApprovedLicenseCategories() {
562         SortedSet<String> result = new TreeSet<>(approvedLicenseCategories);
563         result.removeAll(removedLicenseCategories);
564         return result;
565     }
566 
567     /**
568      * Returns the optional license copyright being added if RAT is adding headers.
569      * This value is ignored, if no license headers are added.
570      * 
571      * @return the optional copyright message.
572      * @see #isAddingLicenses()
573      */
574     public String getCopyrightMessage() {
575         return copyrightMessage;
576     }
577 
578     /**
579      * Sets the optional copyright message used if RAT is adding license headers.
580      * This value is ignored, if no license headers are added.
581      *
582      * @param copyrightMessage message to set.
583      * @see #isAddingLicenses()
584      */
585     public void setCopyrightMessage(String copyrightMessage) {
586         this.copyrightMessage = copyrightMessage;
587     }
588 
589     /**
590      * This value is ignored if RAT is not adding licenses.
591      * 
592      * @return {@code true} if RAT is forcing the adding license headers.
593      * @see #isAddingLicenses()
594      */
595     public boolean isAddingLicensesForced() {
596         return addingLicensesForced;
597     }
598 
599     /**
600      * @return whether RAT should add missing license headers.
601      * @see #isAddingLicensesForced()
602      * @see #getCopyrightMessage()
603      */
604     public boolean isAddingLicenses() {
605         return addingLicenses;
606     }
607 
608     /**
609      * Sets whether RAT should enable, disable, or force the adding of license
610      * headers.
611      * 
612      * @param addLicenseHeaders enables/disables or forces adding of licenses
613      * headers.
614      * @see #isAddingLicenses()
615      * @see #setCopyrightMessage(String)
616      */
617     public void setAddLicenseHeaders(AddLicenseHeaders addLicenseHeaders) {
618         addingLicenses = false;
619         addingLicensesForced = false;
620         switch (addLicenseHeaders) {
621         case FALSE:
622             // do nothing
623             break;
624         case FORCED:
625             addingLicensesForced = true;
626             // fall through
627         case TRUE:
628             addingLicenses = true;
629             break;
630         }
631     }
632 
633     /**
634      * Gets a set Licenses of depending on the {@code filter} if filter is set:
635      * <ul>
636      * <li>{@code all} - All licenses will be returned.</li>
637      * <li>{@code approved} - Only approved licenses will be returned</li>
638      * <li>{@code none} - No licenses will be returned</li>
639      * </ul>
640      * 
641      * @param filter The license filter.
642      * @return The set of defined licenses.
643      */
644     public SortedSet<ILicense> getLicenses(LicenseFilter filter) {
645         switch (filter) {
646         case ALL:
647             return Collections.unmodifiableSortedSet(licenses);
648         case APPROVED:
649             return new LicenseSetFactory(licenses, getApprovedLicenseCategories()).getLicenses(filter);
650         case NONE:
651         default:
652             return LicenseSetFactory.emptyLicenseSet();
653         }
654     }
655 
656     /**
657      * Gets a sorted set of ILicenseFamily objects based on {@code filter}. if
658      * filter is set:
659      * <ul>
660      * <li>{@code all} - All licenses families will be returned.</li>
661      * <li>{@code approved} - Only approved license families will be returned</li>
662      * <li>{@code none} - No license families will be returned</li>
663      * </ul>
664      * 
665      * @param filter The license filter.
666      * @return The set of defined licenses.
667      */
668     public SortedSet<ILicenseFamily> getLicenseFamilies(LicenseFilter filter) {
669         return new LicenseFamilySetFactory(families, getApprovedLicenseCategories()).getFamilies(filter);
670     }
671 
672     /**
673      * Validates that the configuration is valid.
674      * 
675      * @param logger String consumer to log warning messages to.
676      */
677     public void validate(Consumer<String> logger) {
678         if (reportable == null) {
679             throw new ConfigurationException("Reportable may not be null");
680         }
681         if (licenses.isEmpty()) {
682             throw new ConfigurationException("You must specify at least one license");
683         }
684         if (styleSheet != null && !isStyleReport()) {
685             logger.accept("Ignoring stylesheet because styling is not selected");
686         }
687         if (styleSheet == null && isStyleReport()) {
688             throw new ConfigurationException("Stylesheet must be specified if report styling is selected");
689         }
690     }
691 
692     /**
693      * A wrapper around an output stream that does not close the output stream.
694      */
695     public static class NoCloseOutputStream extends OutputStream {
696         private final OutputStream delegate;
697 
698         public NoCloseOutputStream(OutputStream delegate) {
699             this.delegate = delegate;
700         }
701 
702         @Override
703         public void write(int arg0) throws IOException {
704             delegate.write(arg0);
705         }
706 
707         @Override
708         public void close() throws IOException {
709             this.delegate.flush();
710         }
711 
712         @Override
713         public boolean equals(Object obj) {
714             return delegate.equals(obj);
715         }
716 
717         @Override
718         public void flush() throws IOException {
719             delegate.flush();
720         }
721 
722         @Override
723         public int hashCode() {
724             return delegate.hashCode();
725         }
726 
727         @Override
728         public String toString() {
729             return delegate.toString();
730         }
731 
732         @Override
733         public void write(byte[] arg0, int arg1, int arg2) throws IOException {
734             delegate.write(arg0, arg1, arg2);
735         }
736 
737         @Override
738         public void write(byte[] b) throws IOException {
739             delegate.write(b);
740         }
741     }
742 }