View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck;
19  
20  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21  import org.apache.commons.jcs3.JCS;
22  import org.jetbrains.annotations.NotNull;
23  import org.jetbrains.annotations.Nullable;
24  import org.owasp.dependencycheck.analyzer.AnalysisPhase;
25  import org.owasp.dependencycheck.analyzer.Analyzer;
26  import org.owasp.dependencycheck.analyzer.AnalyzerService;
27  import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
28  import org.owasp.dependencycheck.data.nvdcve.DatabaseManager;
29  import org.owasp.dependencycheck.data.nvdcve.CveDB;
30  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
31  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
32  import org.owasp.dependencycheck.data.update.CachedWebDataSource;
33  import org.owasp.dependencycheck.data.update.UpdateService;
34  import org.owasp.dependencycheck.data.update.exception.UpdateException;
35  import org.owasp.dependencycheck.dependency.Dependency;
36  import org.owasp.dependencycheck.exception.ExceptionCollection;
37  import org.owasp.dependencycheck.exception.InitializationException;
38  import org.owasp.dependencycheck.exception.NoDataException;
39  import org.owasp.dependencycheck.exception.ReportException;
40  import org.owasp.dependencycheck.exception.WriteLockException;
41  import org.owasp.dependencycheck.reporting.ReportGenerator;
42  import org.owasp.dependencycheck.utils.FileUtils;
43  import org.owasp.dependencycheck.utils.Settings;
44  import org.owasp.dependencycheck.utils.WriteLock;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import javax.annotation.concurrent.NotThreadSafe;
49  import java.io.File;
50  import java.io.FileFilter;
51  import java.io.IOException;
52  import java.nio.file.Files;
53  import java.util.ArrayList;
54  import java.util.Arrays;
55  import java.util.Collection;
56  import java.util.Collections;
57  import java.util.EnumMap;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.Iterator;
61  import java.util.List;
62  import java.util.Map;
63  import java.util.Objects;
64  import java.util.Set;
65  import java.util.concurrent.CancellationException;
66  import java.util.concurrent.ExecutionException;
67  import java.util.concurrent.ExecutorService;
68  import java.util.concurrent.Executors;
69  import java.util.concurrent.Future;
70  import java.util.concurrent.TimeUnit;
71  
72  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINAL;
73  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS;
74  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS_PHASE2;
75  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.IDENTIFIER_ANALYSIS;
76  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION;
77  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION2;
78  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INITIAL;
79  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_FINDING_ANALYSIS;
80  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
81  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION1;
82  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION2;
83  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION3;
84  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_FINDING_ANALYSIS;
85  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_IDENTIFIER_ANALYSIS;
86  import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_INFORMATION_COLLECTION;
87  import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
88  import org.owasp.dependencycheck.dependency.naming.Identifier;
89  
90  /**
91   * Scans files, directories, etc. for Dependencies. Analyzers are loaded and
92   * used to process the files found by the scan, if a file is encountered and an
93   * Analyzer is associated with the file type then the file is turned into a
94   * dependency.
95   *
96   * @author Jeremy Long
97   */
98  @NotThreadSafe
99  public class Engine implements FileFilter, AutoCloseable {
100 
101     /**
102      * The Logger for use throughout the class.
103      */
104     private static final Logger LOGGER = LoggerFactory.getLogger(Engine.class);
105     /**
106      * The list of dependencies.
107      */
108     private final List<Dependency> dependencies = Collections.synchronizedList(new ArrayList<>());
109     /**
110      * A Map of analyzers grouped by Analysis phase.
111      */
112     private final Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<>(AnalysisPhase.class);
113     /**
114      * A Map of analyzers grouped by Analysis phase.
115      */
116     private final Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<>();
117     /**
118      * The engine execution mode indicating it will either collect evidence or
119      * process evidence or both.
120      */
121     private final Mode mode;
122     /**
123      * The ClassLoader to use when dynamically loading Analyzer and Update
124      * services.
125      */
126     private final ClassLoader serviceClassLoader;
127     /**
128      * The configured settings.
129      */
130     private final Settings settings;
131     /**
132      * A storage location to persist objects throughout the execution of ODC.
133      */
134     private final Map<String, Object> objects = new HashMap<>();
135     /**
136      * The external view of the dependency list.
137      */
138     private Dependency[] dependenciesExternalView = null;
139     /**
140      * A reference to the database.
141      */
142     private CveDB database = null;
143     /**
144      * Used to store the value of
145      * System.getProperty("javax.xml.accessExternalSchema") - ODC may change the
146      * value of this system property at runtime. We store the value to reset the
147      * property to its original value.
148      */
149     private final String accessExternalSchema;
150 
151     /**
152      * Creates a new {@link Mode#STANDALONE} Engine.
153      *
154      * @param settings reference to the configured settings
155      */
156     public Engine(@NotNull final Settings settings) {
157         this(Mode.STANDALONE, settings);
158     }
159 
160     /**
161      * Creates a new Engine.
162      *
163      * @param mode the mode of operation
164      * @param settings reference to the configured settings
165      */
166     public Engine(@NotNull final Mode mode, @NotNull final Settings settings) {
167         this(Thread.currentThread().getContextClassLoader(), mode, settings);
168     }
169 
170     /**
171      * Creates a new {@link Mode#STANDALONE} Engine.
172      *
173      * @param serviceClassLoader a reference the class loader being used
174      * @param settings reference to the configured settings
175      */
176     public Engine(@NotNull final ClassLoader serviceClassLoader, @NotNull final Settings settings) {
177         this(serviceClassLoader, Mode.STANDALONE, settings);
178     }
179 
180     /**
181      * Creates a new Engine.
182      *
183      * @param serviceClassLoader a reference the class loader being used
184      * @param mode the mode of the engine
185      * @param settings reference to the configured settings
186      */
187     public Engine(@NotNull final ClassLoader serviceClassLoader, @NotNull final Mode mode, @NotNull final Settings settings) {
188         this.settings = settings;
189         this.serviceClassLoader = serviceClassLoader;
190         this.mode = mode;
191         this.accessExternalSchema = System.getProperty("javax.xml.accessExternalSchema");
192 
193         initializeEngine();
194     }
195 
196     /**
197      * Creates a new Engine using the specified classloader to dynamically load
198      * Analyzer and Update services.
199      *
200      * @throws DatabaseException thrown if there is an error connecting to the
201      * database
202      */
203     protected final void initializeEngine() {
204         loadAnalyzers();
205     }
206 
207     /**
208      * Properly cleans up resources allocated during analysis.
209      */
210     @Override
211     public void close() {
212         if (mode.isDatabaseRequired()) {
213             if (database != null) {
214                 database.close();
215                 database = null;
216             }
217         }
218         if (accessExternalSchema != null) {
219             System.setProperty("javax.xml.accessExternalSchema", accessExternalSchema);
220         } else {
221             System.clearProperty("javax.xml.accessExternalSchema");
222         }
223         JCS.shutdown();
224     }
225 
226     /**
227      * Loads the analyzers specified in the configuration file (or system
228      * properties).
229      */
230     private void loadAnalyzers() {
231         if (!analyzers.isEmpty()) {
232             return;
233         }
234         mode.getPhases().forEach((phase) -> analyzers.put(phase, new ArrayList<>()));
235         final AnalyzerService service = new AnalyzerService(serviceClassLoader, settings);
236         final List<Analyzer> iterator = service.getAnalyzers(mode.getPhases());
237         iterator.forEach((a) -> {
238             a.initialize(this.settings);
239             analyzers.get(a.getAnalysisPhase()).add(a);
240             if (a instanceof FileTypeAnalyzer) {
241                 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
242             }
243         });
244     }
245 
246     /**
247      * Get the List of the analyzers for a specific phase of analysis.
248      *
249      * @param phase the phase to get the configured analyzers.
250      * @return the analyzers loaded
251      */
252     public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
253         return analyzers.get(phase);
254     }
255 
256     /**
257      * Adds a dependency. In some cases, when adding a virtual dependency, the
258      * method will identify if the virtual dependency was previously added and
259      * update the existing dependency rather then adding a duplicate.
260      *
261      * @param dependency the dependency to add
262      */
263     public synchronized void addDependency(Dependency dependency) {
264         if (dependency.isVirtual()) {
265             for (Dependency existing : dependencies) {
266                 if (existing.isVirtual()
267                         && existing.getSha256sum() != null
268                         && existing.getSha256sum().equals(dependency.getSha256sum())
269                         && existing.getDisplayFileName() != null
270                         && existing.getDisplayFileName().equals(dependency.getDisplayFileName())
271                         && identifiersMatch(existing.getSoftwareIdentifiers(), dependency.getSoftwareIdentifiers())) {
272                     DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
273                     return;
274                 }
275             }
276         }
277         dependencies.add(dependency);
278         dependenciesExternalView = null;
279     }
280 
281     /**
282      * Sorts the dependency list.
283      */
284     public synchronized void sortDependencies() {
285         //TODO - is this actually necassary????
286 //        Collections.sort(dependencies);
287 //        dependenciesExternalView = null;
288     }
289 
290     /**
291      * Removes the dependency.
292      *
293      * @param dependency the dependency to remove.
294      */
295     public synchronized void removeDependency(@NotNull final Dependency dependency) {
296         dependencies.remove(dependency);
297         dependenciesExternalView = null;
298     }
299 
300     /**
301      * Returns a copy of the dependencies as an array.
302      *
303      * @return the dependencies identified
304      */
305     @SuppressFBWarnings(justification = "This is the intended external view of the dependencies", value = {"EI_EXPOSE_REP"})
306     public synchronized Dependency[] getDependencies() {
307         if (dependenciesExternalView == null) {
308             dependenciesExternalView = dependencies.toArray(new Dependency[0]);
309         }
310         return dependenciesExternalView;
311     }
312 
313     /**
314      * Sets the dependencies.
315      *
316      * @param dependencies the dependencies
317      */
318     public synchronized void setDependencies(@NotNull final List<Dependency> dependencies) {
319         this.dependencies.clear();
320         this.dependencies.addAll(dependencies);
321         dependenciesExternalView = null;
322     }
323 
324     /**
325      * Scans an array of files or directories. If a directory is specified, it
326      * will be scanned recursively. Any dependencies identified are added to the
327      * dependency collection.
328      *
329      * @param paths an array of paths to files or directories to be analyzed
330      * @return the list of dependencies scanned
331      * @since v0.3.2.5
332      */
333     public List<Dependency> scan(@NotNull final String[] paths) {
334         return scan(paths, null);
335     }
336 
337     /**
338      * Scans an array of files or directories. If a directory is specified, it
339      * will be scanned recursively. Any dependencies identified are added to the
340      * dependency collection.
341      *
342      * @param paths an array of paths to files or directories to be analyzed
343      * @param projectReference the name of the project or scope in which the
344      * dependency was identified
345      * @return the list of dependencies scanned
346      * @since v1.4.4
347      */
348     public List<Dependency> scan(@NotNull final String[] paths, @Nullable final String projectReference) {
349         final List<Dependency> deps = new ArrayList<>();
350         for (String path : paths) {
351             final List<Dependency> d = scan(path, projectReference);
352             if (d != null) {
353                 deps.addAll(d);
354             }
355         }
356         return deps;
357     }
358 
359     /**
360      * Scans a given file or directory. If a directory is specified, it will be
361      * scanned recursively. Any dependencies identified are added to the
362      * dependency collection.
363      *
364      * @param path the path to a file or directory to be analyzed
365      * @return the list of dependencies scanned
366      */
367     public List<Dependency> scan(@NotNull final String path) {
368         return scan(path, null);
369     }
370 
371     /**
372      * Scans a given file or directory. If a directory is specified, it will be
373      * scanned recursively. Any dependencies identified are added to the
374      * dependency collection.
375      *
376      * @param path the path to a file or directory to be analyzed
377      * @param projectReference the name of the project or scope in which the
378      * dependency was identified
379      * @return the list of dependencies scanned
380      * @since v1.4.4
381      */
382     public List<Dependency> scan(@NotNull final String path, String projectReference) {
383         final File file = new File(path);
384         return scan(file, projectReference);
385     }
386 
387     /**
388      * Scans an array of files or directories. If a directory is specified, it
389      * will be scanned recursively. Any dependencies identified are added to the
390      * dependency collection.
391      *
392      * @param files an array of paths to files or directories to be analyzed.
393      * @return the list of dependencies
394      * @since v0.3.2.5
395      */
396     public List<Dependency> scan(File[] files) {
397         return scan(files, null);
398     }
399 
400     /**
401      * Scans an array of files or directories. If a directory is specified, it
402      * will be scanned recursively. Any dependencies identified are added to the
403      * dependency collection.
404      *
405      * @param files an array of paths to files or directories to be analyzed.
406      * @param projectReference the name of the project or scope in which the
407      * dependency was identified
408      * @return the list of dependencies
409      * @since v1.4.4
410      */
411     public List<Dependency> scan(File[] files, String projectReference) {
412         final List<Dependency> deps = new ArrayList<>();
413         for (File file : files) {
414             final List<Dependency> d = scan(file, projectReference);
415             if (d != null) {
416                 deps.addAll(d);
417             }
418         }
419         return deps;
420     }
421 
422     /**
423      * Scans a collection of files or directories. If a directory is specified,
424      * it will be scanned recursively. Any dependencies identified are added to
425      * the dependency collection.
426      *
427      * @param files a set of paths to files or directories to be analyzed
428      * @return the list of dependencies scanned
429      * @since v0.3.2.5
430      */
431     public List<Dependency> scan(Collection<File> files) {
432         return scan(files, null);
433     }
434 
435     /**
436      * Scans a collection of files or directories. If a directory is specified,
437      * it will be scanned recursively. Any dependencies identified are added to
438      * the dependency collection.
439      *
440      * @param files a set of paths to files or directories to be analyzed
441      * @param projectReference the name of the project or scope in which the
442      * dependency was identified
443      * @return the list of dependencies scanned
444      * @since v1.4.4
445      */
446     public List<Dependency> scan(Collection<File> files, String projectReference) {
447         final List<Dependency> deps = new ArrayList<>();
448         files.stream().map((file) -> scan(file, projectReference))
449                 .filter(Objects::nonNull)
450                 .forEach(deps::addAll);
451         return deps;
452     }
453 
454     /**
455      * Scans a given file or directory. If a directory is specified, it will be
456      * scanned recursively. Any dependencies identified are added to the
457      * dependency collection.
458      *
459      * @param file the path to a file or directory to be analyzed
460      * @return the list of dependencies scanned
461      * @since v0.3.2.4
462      */
463     public List<Dependency> scan(File file) {
464         return scan(file, null);
465     }
466 
467     /**
468      * Scans a given file or directory. If a directory is specified, it will be
469      * scanned recursively. Any dependencies identified are added to the
470      * dependency collection.
471      *
472      * @param file the path to a file or directory to be analyzed
473      * @param projectReference the name of the project or scope in which the
474      * dependency was identified
475      * @return the list of dependencies scanned
476      * @since v1.4.4
477      */
478     @Nullable
479     public List<Dependency> scan(@NotNull final File file, String projectReference) {
480         if (file.exists()) {
481             if (file.isDirectory()) {
482                 return scanDirectory(file, projectReference);
483             } else {
484                 final Dependency d = scanFile(file, projectReference);
485                 if (d != null) {
486                     final List<Dependency> deps = new ArrayList<>();
487                     deps.add(d);
488                     return deps;
489                 }
490             }
491         }
492         return null;
493     }
494 
495     /**
496      * Recursively scans files and directories. Any dependencies identified are
497      * added to the dependency collection.
498      *
499      * @param dir the directory to scan
500      * @return the list of Dependency objects scanned
501      */
502     protected List<Dependency> scanDirectory(File dir) {
503         return scanDirectory(dir, null);
504     }
505 
506     /**
507      * Recursively scans files and directories. Any dependencies identified are
508      * added to the dependency collection.
509      *
510      * @param dir the directory to scan
511      * @param projectReference the name of the project or scope in which the
512      * dependency was identified
513      * @return the list of Dependency objects scanned
514      * @since v1.4.4
515      */
516     protected List<Dependency> scanDirectory(@NotNull final File dir, @Nullable final String projectReference) {
517         final File[] files = dir.listFiles();
518         final List<Dependency> deps = new ArrayList<>();
519         if (files != null) {
520             for (File f : files) {
521                 if (f.isDirectory()) {
522                     final List<Dependency> d = scanDirectory(f, projectReference);
523                     if (d != null) {
524                         deps.addAll(d);
525                     }
526                 } else {
527                     final Dependency d = scanFile(f, projectReference);
528                     if (d != null) {
529                         deps.add(d);
530                     }
531                 }
532             }
533         }
534         return deps;
535     }
536 
537     /**
538      * Scans a specified file. If a dependency is identified it is added to the
539      * dependency collection.
540      *
541      * @param file The file to scan
542      * @return the scanned dependency
543      */
544     protected Dependency scanFile(@NotNull final File file) {
545         return scanFile(file, null);
546     }
547 
548     //CSOFF: NestedIfDepth
549     /**
550      * Scans a specified file. If a dependency is identified it is added to the
551      * dependency collection.
552      *
553      * @param file The file to scan
554      * @param projectReference the name of the project or scope in which the
555      * dependency was identified
556      * @return the scanned dependency
557      * @since v1.4.4
558      */
559     protected synchronized Dependency scanFile(@NotNull final File file, @Nullable final String projectReference) {
560         Dependency dependency = null;
561         if (file.isFile()) {
562             if (accept(file)) {
563                 dependency = new Dependency(file);
564                 if (projectReference != null) {
565                     dependency.addProjectReference(projectReference);
566                 }
567                 final String sha1 = dependency.getSha1sum();
568                 boolean found = false;
569 
570                 if (sha1 != null) {
571                     for (Dependency existing : dependencies) {
572                         if (sha1.equals(existing.getSha1sum())) {
573                             if (existing.getDisplayFileName().contains(": ")
574                                     || dependency.getDisplayFileName().contains(": ")
575                                     || dependency.getActualFilePath().contains("dctemp")) {
576                                 continue;
577                             }
578                             found = true;
579                             if (projectReference != null) {
580                                 existing.addProjectReference(projectReference);
581                             }
582                             if (existing.getActualFilePath() != null && dependency.getActualFilePath() != null
583                                     && !existing.getActualFilePath().equals(dependency.getActualFilePath())) {
584 
585                                 if (DependencyBundlingAnalyzer.firstPathIsShortest(existing.getFilePath(), dependency.getFilePath())) {
586                                     DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
587 
588                                     //return null;
589                                     return existing;
590                                 } else {
591                                     //Merging dependency<-existing could be complicated. Instead analyze them seperately
592                                     //and possibly merge them at the end.
593                                     found = false;
594                                 }
595 
596                             } else { //somehow we scanned the same file twice?
597                                 //return null;
598                                 return existing;
599                             }
600                             break;
601                         }
602                     }
603                 }
604                 if (!found) {
605                     dependencies.add(dependency);
606                     dependenciesExternalView = null;
607                 }
608             }
609         } else {
610             LOGGER.debug("Path passed to scanFile(File) is not a file that can be scanned by dependency-check: {}. Skipping the file.", file);
611         }
612         return dependency;
613     }
614     //CSON: NestedIfDepth
615 
616     /**
617      * Runs the analyzers against all of the dependencies. Since the mutable
618      * dependencies list is exposed via {@link #getDependencies()}, this method
619      * iterates over a copy of the dependencies list. Thus, the potential for
620      * {@link java.util.ConcurrentModificationException}s is avoided, and
621      * analyzers may safely add or remove entries from the dependencies list.
622      * <p>
623      * Every effort is made to complete analysis on the dependencies. In some
624      * cases an exception will occur with part of the analysis being performed
625      * which may not affect the entire analysis. If an exception occurs it will
626      * be included in the thrown exception collection.
627      *
628      * @throws ExceptionCollection a collections of any exceptions that occurred
629      * during analysis
630      */
631     public void analyzeDependencies() throws ExceptionCollection {
632         final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
633 
634         initializeAndUpdateDatabase(exceptions);
635 
636         //need to ensure that data exists
637         try {
638             ensureDataExists();
639         } catch (NoDataException ex) {
640             throwFatalExceptionCollection("Unable to continue dependency-check analysis.", ex, exceptions);
641         }
642         LOGGER.info("\n\nDependency-Check is an open source tool performing a best effort analysis of 3rd party dependencies; false positives and "
643                 + "false negatives may exist in the analysis performed by the tool. Use of the tool and the reporting provided constitutes "
644                 + "acceptance for use in an AS IS condition, and there are NO warranties, implied or otherwise, with regard to the analysis "
645                 + "or its use. Any use of the tool and the reporting provided is at the user's risk. In no event shall the copyright holder "
646                 + "or OWASP be held liable for any damages whatsoever arising out of or in connection with the use of this tool, the analysis "
647                 + "performed, or the resulting report.\n\n\n"
648                 + "   About ODC: https://dependency-check.github.io/DependencyCheck/general/internals.html\n"
649                 + "   False Positives: https://dependency-check.github.io/DependencyCheck/general/suppression.html\n"
650                 + "\n");
651         LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
652         LOGGER.info("Analysis Started");
653         final long analysisStart = System.currentTimeMillis();
654 
655         // analysis phases
656         for (AnalysisPhase phase : mode.getPhases()) {
657             final List<Analyzer> analyzerList = analyzers.get(phase);
658 
659             for (final Analyzer analyzer : analyzerList) {
660                 final long analyzerStart = System.currentTimeMillis();
661                 try {
662                     initializeAnalyzer(analyzer);
663                 } catch (InitializationException ex) {
664                     exceptions.add(ex);
665                     if (ex.isFatal()) {
666                         continue;
667                     }
668                 }
669 
670                 if (analyzer.isEnabled()) {
671                     executeAnalysisTasks(analyzer, exceptions);
672 
673                     final long analyzerDurationMillis = System.currentTimeMillis() - analyzerStart;
674                     final long analyzerDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(analyzerDurationMillis);
675                     LOGGER.info("Finished {} ({} seconds)", analyzer.getName(), analyzerDurationSeconds);
676                 } else {
677                     LOGGER.debug("Skipping {} (not enabled)", analyzer.getName());
678                 }
679             }
680         }
681         mode.getPhases().stream()
682                 .map(analyzers::get)
683                 .forEach((analyzerList) -> analyzerList.forEach(this::closeAnalyzer));
684 
685         LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
686         final long analysisDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - analysisStart);
687         LOGGER.info("Analysis Complete ({} seconds)", analysisDurationSeconds);
688         if (exceptions.size() > 0) {
689             throw new ExceptionCollection(exceptions);
690         }
691     }
692 
693     /**
694      * Performs any necessary updates and initializes the database.
695      *
696      * @param exceptions a collection to store non-fatal exceptions
697      * @throws ExceptionCollection thrown if fatal exceptions occur
698      */
699     private void initializeAndUpdateDatabase(@NotNull final List<Throwable> exceptions) throws ExceptionCollection {
700         if (!mode.isDatabaseRequired()) {
701             return;
702         }
703         final boolean autoUpdate;
704         autoUpdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
705         if (autoUpdate) {
706             try {
707                 doUpdates(true);
708             } catch (UpdateException ex) {
709                 exceptions.add(ex);
710                 LOGGER.warn("Unable to update 1 or more Cached Web DataSource, using local "
711                         + "data instead. Results may not include recent vulnerabilities.");
712                 LOGGER.debug("Update Error", ex);
713             } catch (DatabaseException ex) {
714                 throwFatalDatabaseException(ex, exceptions);
715             }
716         } else {
717             try {
718                 if (DatabaseManager.isH2Connection(settings) && !DatabaseManager.h2DataFileExists(settings)) {
719                     throw new ExceptionCollection(new NoDataException("Autoupdate is disabled and the database does not exist"), true);
720                 } else {
721                     openDatabase(true, true);
722                 }
723             } catch (IOException ex) {
724                 throw new ExceptionCollection(new DatabaseException("Autoupdate is disabled and unable to connect to the database"), true);
725             } catch (DatabaseException ex) {
726                 throwFatalDatabaseException(ex, exceptions);
727             }
728         }
729     }
730 
731     /**
732      * Utility method to throw a fatal database exception.
733      *
734      * @param ex the exception that was caught
735      * @param exceptions the exception collection
736      * @throws ExceptionCollection the collection of exceptions is always thrown
737      * as a fatal exception
738      */
739     private void throwFatalDatabaseException(DatabaseException ex, final List<Throwable> exceptions) throws ExceptionCollection {
740         final String msg;
741         if (ex.getMessage().contains("Unable to connect") && DatabaseManager.isH2Connection(settings)) {
742             msg = "Unable to connect to the database - if this error persists it may be "
743                     + "due to a corrupt database. Consider running `purge` to delete the existing database";
744         } else {
745             msg = "Unable to connect to the dependency-check database";
746         }
747         exceptions.add(new DatabaseException(msg, ex));
748         throw new ExceptionCollection(exceptions, true);
749     }
750 
751     /**
752      * Executes executes the analyzer using multiple threads.
753      *
754      * @param exceptions a collection of exceptions that occurred during
755      * analysis
756      * @param analyzer the analyzer to execute
757      * @throws ExceptionCollection thrown if exceptions occurred during analysis
758      */
759     protected void executeAnalysisTasks(@NotNull final Analyzer analyzer, List<Throwable> exceptions) throws ExceptionCollection {
760         LOGGER.debug("Starting {}", analyzer.getName());
761         final List<AnalysisTask> analysisTasks = getAnalysisTasks(analyzer, exceptions);
762         final ExecutorService executorService = getExecutorService(analyzer);
763 
764         try {
765             final int timeout = settings.getInt(Settings.KEYS.ANALYSIS_TIMEOUT, 180);
766             final List<Future<Void>> results = executorService.invokeAll(analysisTasks, timeout, TimeUnit.MINUTES);
767 
768             // ensure there was no exception during execution
769             for (Future<Void> result : results) {
770                 try {
771                     result.get();
772                 } catch (ExecutionException e) {
773                     throwFatalExceptionCollection("Analysis task failed with a fatal exception.", e, exceptions);
774                 } catch (CancellationException e) {
775                     throwFatalExceptionCollection("Analysis task was cancelled.", e, exceptions);
776                 }
777             }
778         } catch (InterruptedException e) {
779             Thread.currentThread().interrupt();
780             throwFatalExceptionCollection("Analysis has been interrupted.", e, exceptions);
781         } finally {
782             executorService.shutdown();
783         }
784     }
785 
786     /**
787      * Returns the analysis tasks for the dependencies.
788      *
789      * @param analyzer the analyzer to create tasks for
790      * @param exceptions the collection of exceptions to collect
791      * @return a collection of analysis tasks
792      */
793     protected synchronized List<AnalysisTask> getAnalysisTasks(Analyzer analyzer, List<Throwable> exceptions) {
794         final List<AnalysisTask> result = new ArrayList<>();
795         dependencies.stream().map((dependency) -> new AnalysisTask(analyzer, dependency, this, exceptions)).forEach(result::add);
796         return result;
797     }
798 
799     /**
800      * Returns the executor service for a given analyzer.
801      *
802      * @param analyzer the analyzer to obtain an executor
803      * @return the executor service
804      */
805     protected ExecutorService getExecutorService(Analyzer analyzer) {
806         if (analyzer.supportsParallelProcessing()) {
807             final int maximumNumberOfThreads = Runtime.getRuntime().availableProcessors();
808             LOGGER.debug("Parallel processing with up to {} threads: {}.", maximumNumberOfThreads, analyzer.getName());
809             return Executors.newFixedThreadPool(maximumNumberOfThreads);
810         } else {
811             LOGGER.debug("Parallel processing is not supported: {}.", analyzer.getName());
812             return Executors.newSingleThreadExecutor();
813         }
814     }
815 
816     /**
817      * Initializes the given analyzer.
818      *
819      * @param analyzer the analyzer to prepare
820      * @throws InitializationException thrown when there is a problem
821      * initializing the analyzer
822      */
823     protected void initializeAnalyzer(@NotNull final Analyzer analyzer) throws InitializationException {
824         try {
825             LOGGER.debug("Initializing {}", analyzer.getName());
826             analyzer.prepare(this);
827         } catch (InitializationException ex) {
828             LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
829             LOGGER.debug("", ex);
830             if (ex.isFatal()) {
831                 try {
832                     analyzer.close();
833                 } catch (Throwable ex1) {
834                     LOGGER.trace("", ex1);
835                 }
836             }
837             throw ex;
838         } catch (Throwable ex) {
839             LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName());
840             LOGGER.debug("", ex);
841             try {
842                 analyzer.close();
843             } catch (Throwable ex1) {
844                 LOGGER.trace("", ex1);
845             }
846             throw new InitializationException("Unexpected Exception", ex);
847         }
848     }
849 
850     /**
851      * Closes the given analyzer.
852      *
853      * @param analyzer the analyzer to close
854      */
855     protected void closeAnalyzer(@NotNull final Analyzer analyzer) {
856         LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
857         try {
858             analyzer.close();
859         } catch (Throwable ex) {
860             LOGGER.trace("", ex);
861         }
862     }
863 
864     /**
865      * Cycles through the cached web data sources and calls update on all of
866      * them.
867      *
868      * @throws UpdateException thrown if the operation fails
869      * @throws DatabaseException if the operation fails due to a local database
870      * failure
871      * @return Whether any updates actually happened
872      */
873     public boolean doUpdates() throws UpdateException, DatabaseException {
874         return doUpdates(false);
875     }
876 
877     /**
878      * Cycles through the cached web data sources and calls update on all of
879      * them.
880      *
881      * @param remainOpen whether or not the database connection should remain
882      * open
883      * @throws UpdateException thrown if the operation fails
884      * @throws DatabaseException if the operation fails due to a local database
885      * failure
886      * @return Whether any updates actually happened
887      */
888     public boolean doUpdates(boolean remainOpen) throws UpdateException, DatabaseException {
889         if (mode.isDatabaseRequired()) {
890             try (WriteLock dblock = new WriteLock(getSettings(), DatabaseManager.isH2Connection(getSettings()))) {
891                 //lock is not needed as we already have the lock held
892                 openDatabase(false, false);
893                 LOGGER.info("Checking for updates");
894                 final long updateStart = System.currentTimeMillis();
895                 final UpdateService service = new UpdateService(serviceClassLoader);
896                 final Iterator<CachedWebDataSource> iterator = service.getDataSources();
897                 boolean dbUpdatesMade = false;
898                 UpdateException updateException = null;
899                 while (iterator.hasNext()) {
900                     try {
901                         final CachedWebDataSource source = iterator.next();
902                         dbUpdatesMade |= source.update(this);
903                     } catch (UpdateException ex) {
904                         updateException = ex;
905                         LOGGER.error(ex.getMessage(), ex);
906                     }
907                 }
908                 if (dbUpdatesMade) {
909                     database.defrag();
910                 }
911                 database.close();
912                 database = null;
913                 if (updateException != null) {
914                     throw updateException;
915                 }
916                 LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
917                 if (remainOpen) {
918                     //lock is not needed as we already have the lock held
919                     openDatabase(true, false);
920                 }
921 
922                 return dbUpdatesMade;
923             } catch (WriteLockException ex) {
924                 throw new UpdateException("Unable to obtain an exclusive lock on the H2 database to perform updates", ex);
925             }
926         } else {
927             LOGGER.info("Skipping update check in evidence collection mode.");
928             return false;
929         }
930     }
931 
932     /**
933      * Purges the cached web data sources.
934      *
935      * @return <code>true</code> if the purge was successful; otherwise
936      * <code>false</code>
937      */
938     public boolean purge() {
939         boolean result = true;
940         final UpdateService service = new UpdateService(serviceClassLoader);
941         final Iterator<CachedWebDataSource> iterator = service.getDataSources();
942         while (iterator.hasNext()) {
943             result &= iterator.next().purge(this);
944         }
945         try {
946             final File cache = new File(settings.getDataDirectory(), "cache");
947             if (cache.exists()) {
948                 if (FileUtils.delete(cache)) {
949                     LOGGER.info("Cache directory purged");
950                 }
951             }
952         } catch (IOException ex) {
953             throw new RuntimeException(ex);
954         }
955         try {
956             final File cache = new File(settings.getDataDirectory(), "oss_cache");
957             if (cache.exists()) {
958                 if (FileUtils.delete(cache)) {
959                     LOGGER.info("OSS Cache directory purged");
960                 }
961             }
962         } catch (IOException ex) {
963             throw new RuntimeException(ex);
964         }
965 
966         return result;
967     }
968 
969     /**
970      * <p>
971      * This method is only public for unit/integration testing. This method
972      * should not be called by any integration that uses
973      * dependency-check-core.</p>
974      * <p>
975      * Opens the database connection.</p>
976      *
977      * @throws DatabaseException if the database connection could not be created
978      */
979     public void openDatabase() throws DatabaseException {
980         openDatabase(false, true);
981     }
982 
983     /**
984      * <p>
985      * This method is only public for unit/integration testing. This method
986      * should not be called by any integration that uses
987      * dependency-check-core.</p>
988      * <p>
989      * Opens the database connection; if readOnly is true a copy of the database
990      * will be made.</p>
991      *
992      * @param readOnly whether or not the database connection should be readonly
993      * @param lockRequired whether or not a lock needs to be acquired when
994      * opening the database
995      * @throws DatabaseException if the database connection could not be created
996      */
997     @SuppressWarnings("try")
998     public void openDatabase(boolean readOnly, boolean lockRequired) throws DatabaseException {
999         if (mode.isDatabaseRequired() && database == null) {
1000             try (WriteLock dblock = new WriteLock(getSettings(), lockRequired && DatabaseManager.isH2Connection(settings))) {
1001                 if (readOnly
1002                         && DatabaseManager.isH2Connection(settings)
1003                         && settings.getString(Settings.KEYS.DB_CONNECTION_STRING).contains("file:%s")) {
1004                     final File db = DatabaseManager.getH2DataFile(settings);
1005                     if (db.isFile()) {
1006                         final File temp = settings.getTempDirectory();
1007                         final File tempDB = new File(temp, db.getName());
1008                         LOGGER.debug("copying database {} to {}", db.toPath(), temp.toPath());
1009                         Files.copy(db.toPath(), tempDB.toPath());
1010                         settings.setString(Settings.KEYS.H2_DATA_DIRECTORY, temp.getPath());
1011                         final String connStr = settings.getString(Settings.KEYS.DB_CONNECTION_STRING);
1012                         if (!connStr.contains("ACCESS_MODE_DATA")) {
1013                             settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connStr + "ACCESS_MODE_DATA=r");
1014                         }
1015                         settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false);
1016                         database = new CveDB(settings);
1017                     } else {
1018                         throw new DatabaseException("Unable to open database - configured database file does not exist: " + db);
1019                     }
1020                 } else {
1021                     database = new CveDB(settings);
1022                 }
1023             } catch (IOException ex) {
1024                 throw new DatabaseException("Unable to open database in read only mode", ex);
1025             } catch (WriteLockException ex) {
1026                 throw new DatabaseException("Failed to obtain lock - unable to open database", ex);
1027             }
1028             database.open();
1029         }
1030     }
1031 
1032     /**
1033      * Returns a reference to the database.
1034      *
1035      * @return a reference to the database
1036      */
1037     public CveDB getDatabase() {
1038         return this.database;
1039     }
1040 
1041     /**
1042      * Returns a full list of all of the analyzers. This is useful for reporting
1043      * which analyzers where used.
1044      *
1045      * @return a list of Analyzers
1046      */
1047     @NotNull
1048     public List<Analyzer> getAnalyzers() {
1049         final List<Analyzer> analyzerList = new ArrayList<>();
1050         //insteae of forEach - we can just do a collect
1051         mode.getPhases().stream()
1052                 .map(analyzers::get)
1053                 .forEachOrdered(analyzerList::addAll);
1054         return analyzerList;
1055     }
1056 
1057     /**
1058      * Checks all analyzers to see if an extension is supported.
1059      *
1060      * @param file a file extension
1061      * @return true or false depending on whether or not the file extension is
1062      * supported
1063      */
1064     @Override
1065     public boolean accept(@Nullable final File file) {
1066         if (file == null) {
1067             return false;
1068         }
1069         /* note, we can't break early on this loop as the analyzers need to know if
1070         they have files to work on prior to initialization */
1071         return this.fileTypeAnalyzers.stream().map((a) -> a.accept(file)).reduce(false, (accumulator, result) -> accumulator || result);
1072     }
1073 
1074     /**
1075      * Returns the set of file type analyzers.
1076      *
1077      * @return the set of file type analyzers
1078      */
1079     public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
1080         return this.fileTypeAnalyzers;
1081     }
1082 
1083     /**
1084      * Returns the configured settings.
1085      *
1086      * @return the configured settings
1087      */
1088     public Settings getSettings() {
1089         return settings;
1090     }
1091 
1092     /**
1093      * Retrieve an object from the objects collection.
1094      *
1095      * @param key the key to retrieve the object
1096      * @return the object
1097      */
1098     public Object getObject(String key) {
1099         return objects.get(key);
1100     }
1101 
1102     /**
1103      * Put an object in the object collection.
1104      *
1105      * @param key the key to store the object
1106      * @param object the object to store
1107      */
1108     public void putObject(String key, Object object) {
1109         objects.put(key, object);
1110     }
1111 
1112     /**
1113      * Verifies if the object exists in the object store.
1114      *
1115      * @param key the key to retrieve the object
1116      * @return <code>true</code> if the object exists; otherwise
1117      * <code>false</code>
1118      */
1119     public boolean hasObject(String key) {
1120         return objects.containsKey(key);
1121     }
1122 
1123     /**
1124      * Removes an object from the object store.
1125      *
1126      * @param key the key to the object
1127      */
1128     public void removeObject(String key) {
1129         objects.remove(key);
1130     }
1131 
1132     /**
1133      * Returns the mode of the engine.
1134      *
1135      * @return the mode of the engine
1136      */
1137     public Mode getMode() {
1138         return mode;
1139     }
1140 
1141     /**
1142      * Adds a file type analyzer. This has been added solely to assist in unit
1143      * testing the Engine.
1144      *
1145      * @param fta the file type analyzer to add
1146      */
1147     protected void addFileTypeAnalyzer(@NotNull final FileTypeAnalyzer fta) {
1148         this.fileTypeAnalyzers.add(fta);
1149     }
1150 
1151     /**
1152      * Checks the CPE Index to ensure documents exists. If none exist a
1153      * NoDataException is thrown.
1154      *
1155      * @throws NoDataException thrown if no data exists in the CPE Index
1156      */
1157     private void ensureDataExists() throws NoDataException {
1158         if (mode.isDatabaseRequired() && (database == null || !database.dataExists())) {
1159             throw new NoDataException("No documents exist");
1160         }
1161     }
1162 
1163     /**
1164      * Constructs and throws a fatal exception collection.
1165      *
1166      * @param message the exception message
1167      * @param throwable the cause
1168      * @param exceptions a collection of exception to include
1169      * @throws ExceptionCollection a collection of exceptions that occurred
1170      * during analysis
1171      */
1172     private void throwFatalExceptionCollection(String message, @NotNull final Throwable throwable,
1173             @NotNull final List<Throwable> exceptions) throws ExceptionCollection {
1174         LOGGER.error(message);
1175         LOGGER.debug("", throwable);
1176         exceptions.add(throwable);
1177         throw new ExceptionCollection(exceptions, true);
1178     }
1179 
1180     /**
1181      * Writes the report to the given output directory.
1182      *
1183      * @param applicationName the name of the application/project
1184      * @param outputDir the path to the output directory (can include the full
1185      * file name if the format is not ALL)
1186      * @param format the report format (see {@link ReportGenerator.Format})
1187      * @throws ReportException thrown if there is an error generating the report
1188      * @deprecated use
1189      * {@link #writeReports(java.lang.String, java.io.File, java.lang.String, org.owasp.dependencycheck.exception.ExceptionCollection)}
1190      */
1191     @Deprecated
1192     public void writeReports(String applicationName, File outputDir, String format) throws ReportException {
1193         writeReports(applicationName, null, null, null, outputDir, format, null);
1194     }
1195 
1196     //CSOFF: LineLength
1197     /**
1198      * Writes the report to the given output directory.
1199      *
1200      * @param applicationName the name of the application/project
1201      * @param outputDir the path to the output directory (can include the full
1202      * file name if the format is not ALL)
1203      * @param format the report format (see {@link ReportGenerator.Format})
1204      * @param exceptions a collection of exceptions that may have occurred
1205      * during the analysis
1206      * @throws ReportException thrown if there is an error generating the report
1207      */
1208     public void writeReports(String applicationName, File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1209         writeReports(applicationName, null, null, null, outputDir, format, exceptions);
1210     }
1211     //CSON: LineLength
1212 
1213     /**
1214      * Writes the report to the given output directory.
1215      *
1216      * @param applicationName the name of the application/project
1217      * @param groupId the Maven groupId
1218      * @param artifactId the Maven artifactId
1219      * @param version the Maven version
1220      * @param outputDir the path to the output directory (can include the full
1221      * file name if the format is not ALL)
1222      * @param format the report format (see {@link ReportGenerator.Format})
1223      * @throws ReportException thrown if there is an error generating the report
1224      * @deprecated use
1225      * {@link #writeReports(String, String, String, String, File, String, ExceptionCollection)}
1226      */
1227     @Deprecated
1228     public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1229             @Nullable final String artifactId, @Nullable final String version,
1230             @NotNull final File outputDir, String format) throws ReportException {
1231         writeReports(applicationName, groupId, artifactId, version, outputDir, format, null);
1232     }
1233 
1234     //CSOFF: LineLength
1235     /**
1236      * Writes the report to the given output directory.
1237      *
1238      * @param applicationName the name of the application/project
1239      * @param groupId the Maven groupId
1240      * @param artifactId the Maven artifactId
1241      * @param version the Maven version
1242      * @param outputDir the path to the output directory (can include the full
1243      * file name if the format is not ALL)
1244      * @param format the report format  (see {@link ReportGenerator.Format})
1245      * @param exceptions a collection of exceptions that may have occurred
1246      * during the analysis
1247      * @throws ReportException thrown if there is an error generating the report
1248      */
1249     public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1250             @Nullable final String artifactId, @Nullable final String version,
1251             @NotNull final File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1252         if (mode == Mode.EVIDENCE_COLLECTION) {
1253             throw new UnsupportedOperationException("Cannot generate report in evidence collection mode.");
1254         }
1255         final DatabaseProperties prop = database.getDatabaseProperties();
1256 
1257         final ReportGenerator r = new ReportGenerator(applicationName, groupId, artifactId, version,
1258                 dependencies, getAnalyzers(), prop, settings, exceptions);
1259         try {
1260             r.write(outputDir.getAbsolutePath(), format);
1261         } catch (ReportException ex) {
1262             final String msg = String.format("Error generating the report for %s", applicationName);
1263             LOGGER.debug(msg, ex);
1264             throw new ReportException(msg, ex);
1265         }
1266     }
1267     //CSON: LineLength
1268 
1269     private boolean identifiersMatch(Set<Identifier> left, Set<Identifier> right) {
1270         if (left != null && right != null && left.size() > 0 && left.size() == right.size()) {
1271             int count = 0;
1272             for (Identifier l : left) {
1273                 for (Identifier r : right) {
1274                     if (l.getValue().equals(r.getValue())) {
1275                         count += 1;
1276                         break;
1277                     }
1278                 }
1279             }
1280             return count == left.size();
1281         }
1282         return false;
1283     }
1284 
1285     /**
1286      * {@link Engine} execution modes.
1287      */
1288     public enum Mode {
1289         /**
1290          * In evidence collection mode the {@link Engine} only collects evidence
1291          * from the scan targets, and doesn't require a database.
1292          */
1293         EVIDENCE_COLLECTION(
1294                 false,
1295                 INITIAL,
1296                 PRE_INFORMATION_COLLECTION,
1297                 INFORMATION_COLLECTION,
1298                 INFORMATION_COLLECTION2,
1299                 POST_INFORMATION_COLLECTION1,
1300                 POST_INFORMATION_COLLECTION2,
1301                 POST_INFORMATION_COLLECTION3
1302         ),
1303         /**
1304          * In evidence processing mode the {@link Engine} processes the evidence
1305          * collected using the {@link #EVIDENCE_COLLECTION} mode. Dependencies
1306          * should be injected into the {@link Engine} using
1307          * {@link Engine#setDependencies(List)}.
1308          */
1309         EVIDENCE_PROCESSING(
1310                 true,
1311                 PRE_IDENTIFIER_ANALYSIS,
1312                 IDENTIFIER_ANALYSIS,
1313                 POST_IDENTIFIER_ANALYSIS,
1314                 PRE_FINDING_ANALYSIS,
1315                 FINDING_ANALYSIS,
1316                 POST_FINDING_ANALYSIS,
1317                 FINDING_ANALYSIS_PHASE2,
1318                 FINAL
1319         ),
1320         /**
1321          * In standalone mode the {@link Engine} will collect and process
1322          * evidence in a single execution.
1323          */
1324         STANDALONE(true, AnalysisPhase.values());
1325 
1326         /**
1327          * Whether the database is required in this mode.
1328          */
1329         private final boolean databaseRequired;
1330         /**
1331          * The analysis phases included in the mode.
1332          */
1333         private final List<AnalysisPhase> phases;
1334 
1335         /**
1336          * Constructs a new mode.
1337          *
1338          * @param databaseRequired if the database is required for the mode
1339          * @param phases the analysis phases to include in the mode
1340          */
1341         Mode(boolean databaseRequired, AnalysisPhase... phases) {
1342             this.databaseRequired = databaseRequired;
1343             this.phases = Collections.unmodifiableList(Arrays.asList(phases));
1344         }
1345 
1346         /**
1347          * Returns true if the database is required; otherwise false.
1348          *
1349          * @return whether or not the database is required
1350          */
1351         private boolean isDatabaseRequired() {
1352             return databaseRequired;
1353         }
1354 
1355         /**
1356          * Returns the phases for this mode.
1357          *
1358          * @return the phases for this mode
1359          */
1360         public List<AnalysisPhase> getPhases() {
1361             return phases;
1362         }
1363     }
1364 }