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