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.utils;
19  
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.PrintWriter;
29  import java.io.StringWriter;
30  import java.io.UnsupportedEncodingException;
31  import java.net.URLDecoder;
32  import java.util.Enumeration;
33  import java.util.Properties;
34  
35  /**
36   * A simple settings container that wraps the dependencycheck.properties file.
37   *
38   * @author Jeremy Long
39   */
40  public final class Settings {
41  
42      //<editor-fold defaultstate="collapsed" desc="KEYS used to access settings">
43      /**
44       * The collection of keys used within the properties file.
45       */
46      public static final class KEYS {
47  
48          /**
49           * The key to obtain the application name.
50           */
51          public static final String APPLICATION_NAME = "application.name";
52          /**
53           * The key to obtain the application version.
54           */
55          public static final String APPLICATION_VERSION = "application.version";
56          /**
57           * The key to obtain the URL to retrieve the current release version
58           * from.
59           */
60          public static final String ENGINE_VERSION_CHECK_URL = "engine.version.url";
61          /**
62           * The properties key indicating whether or not the cached data sources
63           * should be updated.
64           */
65          public static final String AUTO_UPDATE = "autoupdate";
66          /**
67           * The database driver class name. If this is not in the properties file
68           * the embedded database is used.
69           */
70          public static final String DB_DRIVER_NAME = "data.driver_name";
71          /**
72           * The database driver class name. If this is not in the properties file
73           * the embedded database is used.
74           */
75          public static final String DB_DRIVER_PATH = "data.driver_path";
76          /**
77           * The database connection string. If this is not in the properties file
78           * the embedded database is used.
79           */
80          public static final String DB_CONNECTION_STRING = "data.connection_string";
81          /**
82           * The username to use when connecting to the database.
83           */
84          public static final String DB_USER = "data.user";
85          /**
86           * The password to authenticate to the database.
87           */
88          public static final String DB_PASSWORD = "data.password";
89          /**
90           * The base path to use for the data directory (for embedded db).
91           */
92          public static final String DATA_DIRECTORY = "data.directory";
93          /**
94           * The database file name.
95           */
96          public static final String DB_FILE_NAME = "data.file_name";
97          /**
98           * The database schema version.
99           */
100         public static final String DB_VERSION = "data.version";
101         /**
102          * The starts with filter used to exclude CVE entries from the database.
103          * By default this is set to 'cpe:/a:' which limits the CVEs imported to
104          * just those that are related to applications. If this were set to just
105          * 'cpe:' the OS, hardware, and application related CVEs would be
106          * imported.
107          */
108         public static final String CVE_CPE_STARTS_WITH_FILTER = "cve.cpe.startswith.filter";
109         /**
110          * The properties key for the URL to retrieve the "meta" data from about
111          * the CVE entries.
112          *
113          * @deprecated this is not currently used
114          */
115         @Deprecated
116         public static final String CVE_META_URL = "cve.url.meta";
117         /**
118          * The properties key for the URL to retrieve the recently modified and
119          * added CVE entries (last 8 days) using the 2.0 schema.
120          */
121         public static final String CVE_MODIFIED_20_URL = "cve.url-2.0.modified";
122         /**
123          * The properties key for the URL to retrieve the recently modified and
124          * added CVE entries (last 8 days) using the 2.0 schema.
125          */
126         public static final String CVE_ORIGINAL_MODIFIED_20_URL = "cve.url-2.0.original";
127         /**
128          * The properties key for the URL to retrieve the recently modified and
129          * added CVE entries (last 8 days) using the 1.2 schema.
130          */
131         public static final String CVE_MODIFIED_12_URL = "cve.url-1.2.modified";
132         /**
133          * The properties key for the URL to retrieve the recently modified and
134          * added CVE entries (last 8 days).
135          */
136         public static final String CVE_MODIFIED_VALID_FOR_DAYS = "cve.url.modified.validfordays";
137         /**
138          * The properties key to control the skipping of the check for CVE
139          * updates.
140          */
141         public static final String CVE_CHECK_VALID_FOR_HOURS = "cve.check.validforhours";
142         /**
143          * The properties key for the telling us how many cve.url.* URLs exists.
144          * This is used in combination with CVE_BASE_URL to be able to retrieve
145          * the URLs for all of the files that make up the NVD CVE listing.
146          */
147         public static final String CVE_START_YEAR = "cve.startyear";
148         /**
149          * The properties key for the CVE schema version 1.2.
150          */
151         public static final String CVE_SCHEMA_1_2 = "cve.url-1.2.base";
152         /**
153          * The properties key for the CVE schema version 2.0.
154          */
155         public static final String CVE_SCHEMA_2_0 = "cve.url-2.0.base";
156         /**
157          * The properties key that indicates how often the CPE data needs to be
158          * updated.
159          */
160         public static final String CPE_MODIFIED_VALID_FOR_DAYS = "cpe.validfordays";
161         /**
162          * The properties key for the URL to retrieve the CPE.
163          */
164         public static final String CPE_URL = "cpe.url";
165         /**
166          * The properties key for the proxy server.
167          *
168          * @deprecated use
169          * {@link org.owasp.dependencycheck.utils.Settings.KEYS#PROXY_SERVER}
170          * instead.
171          */
172         @Deprecated
173         public static final String PROXY_URL = "proxy.server";
174         /**
175          * The properties key for the proxy server.
176          */
177         public static final String PROXY_SERVER = "proxy.server";
178         /**
179          * The properties key for the proxy port - this must be an integer
180          * value.
181          */
182         public static final String PROXY_PORT = "proxy.port";
183         /**
184          * The properties key for the proxy username.
185          */
186         public static final String PROXY_USERNAME = "proxy.username";
187         /**
188          * The properties key for the proxy password.
189          */
190         public static final String PROXY_PASSWORD = "proxy.password";
191         /**
192          * The properties key for the non proxy hosts.
193          */
194         public static final String PROXY_NON_PROXY_HOSTS = "proxy.nonproxyhosts";
195         /**
196          * The properties key for the connection timeout.
197          */
198         public static final String CONNECTION_TIMEOUT = "connection.timeout";
199         /**
200          * The location of the temporary directory.
201          */
202         public static final String TEMP_DIRECTORY = "temp.directory";
203         /**
204          * The maximum number of threads to allocate when downloading files.
205          */
206         public static final String MAX_DOWNLOAD_THREAD_POOL_SIZE = "max.download.threads";
207         /**
208          * The key for the suppression file.
209          */
210         public static final String SUPPRESSION_FILE = "suppression.file";
211         /**
212          * The key for the hint file.
213          */
214         public static final String HINTS_FILE = "hints.file";
215         /**
216          * The properties key for whether the Jar Analyzer is enabled.
217          */
218         public static final String ANALYZER_JAR_ENABLED = "analyzer.jar.enabled";
219         /**
220          * The properties key for whether experimental analyzers are loaded.
221          */
222         public static final String ANALYZER_EXPERIMENTAL_ENABLED = "analyzer.experimental.enabled";
223         /**
224          * The properties key for whether the Archive analyzer is enabled.
225          */
226         public static final String ANALYZER_ARCHIVE_ENABLED = "analyzer.archive.enabled";
227         /**
228          * The properties key for whether the node.js package analyzer is
229          * enabled.
230          */
231         public static final String ANALYZER_NODE_PACKAGE_ENABLED = "analyzer.node.package.enabled";
232         /**
233          * The properties key for whether the composer lock file analyzer is
234          * enabled.
235          */
236         public static final String ANALYZER_COMPOSER_LOCK_ENABLED = "analyzer.composer.lock.enabled";
237         /**
238          * The properties key for whether the Python Distribution analyzer is
239          * enabled.
240          */
241         public static final String ANALYZER_PYTHON_DISTRIBUTION_ENABLED = "analyzer.python.distribution.enabled";
242         /**
243          * The properties key for whether the Python Package analyzer is
244          * enabled.
245          */
246         public static final String ANALYZER_PYTHON_PACKAGE_ENABLED = "analyzer.python.package.enabled";
247         /**
248          * The properties key for whether the Ruby Gemspec Analyzer is enabled.
249          */
250         public static final String ANALYZER_RUBY_GEMSPEC_ENABLED = "analyzer.ruby.gemspec.enabled";
251         /**
252          * The properties key for whether the Autoconf analyzer is enabled.
253          */
254         public static final String ANALYZER_AUTOCONF_ENABLED = "analyzer.autoconf.enabled";
255         /**
256          * The properties key for whether the CMake analyzer is enabled.
257          */
258         public static final String ANALYZER_CMAKE_ENABLED = "analyzer.cmake.enabled";
259         /**
260          * The properties key for whether the Ruby Bundler Audit analyzer is
261          * enabled.
262          */
263         public static final String ANALYZER_BUNDLE_AUDIT_ENABLED = "analyzer.bundle.audit.enabled";
264         /**
265          * The properties key for whether the .NET Assembly analyzer is enabled.
266          */
267         public static final String ANALYZER_ASSEMBLY_ENABLED = "analyzer.assembly.enabled";
268         /**
269          * The properties key for whether the .NET Nuspec analyzer is enabled.
270          */
271         public static final String ANALYZER_NUSPEC_ENABLED = "analyzer.nuspec.enabled";
272         /**
273          * The properties key for whether the Nexus analyzer is enabled.
274          */
275         public static final String ANALYZER_NEXUS_ENABLED = "analyzer.nexus.enabled";
276         /**
277          * The properties key for the Nexus search URL.
278          */
279         public static final String ANALYZER_NEXUS_URL = "analyzer.nexus.url";
280         /**
281          * The properties key for using the proxy to reach Nexus.
282          */
283         public static final String ANALYZER_NEXUS_USES_PROXY = "analyzer.nexus.proxy";
284         /**
285          * The properties key for whether the Central analyzer is enabled.
286          */
287         public static final String ANALYZER_CENTRAL_ENABLED = "analyzer.central.enabled";
288         /**
289          * The properties key for whether the OpenSSL analyzer is enabled.
290          */
291         public static final String ANALYZER_OPENSSL_ENABLED = "analyzer.openssl.enabled";
292         /**
293          * The properties key for whether the cocoapods analyzer is enabled.
294          */
295         public static final String ANALYZER_COCOAPODS_ENABLED = "analyzer.cocoapods.enabled";
296         /**
297          * The properties key for whether the SWIFT package manager analyzer is
298          * enabled.
299          */
300         public static final String ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED = "analyzer.swift.package.manager.enabled";
301         /**
302          * The properties key for the Central search URL.
303          */
304         public static final String ANALYZER_CENTRAL_URL = "analyzer.central.url";
305         /**
306          * The path to mono, if available.
307          */
308         public static final String ANALYZER_ASSEMBLY_MONO_PATH = "analyzer.assembly.mono.path";
309         /**
310          * The path to bundle-audit, if available.
311          */
312         public static final String ANALYZER_BUNDLE_AUDIT_PATH = "analyzer.bundle.audit.path";
313         /**
314          * The additional configured zip file extensions, if available.
315          */
316         public static final String ADDITIONAL_ZIP_EXTENSIONS = "extensions.zip";
317         /**
318          * The key to obtain the path to the VFEED data file.
319          */
320         public static final String VFEED_DATA_FILE = "vfeed.data_file";
321         /**
322          * The key to obtain the VFEED connection string.
323          */
324         public static final String VFEED_CONNECTION_STRING = "vfeed.connection_string";
325         /**
326          * The key to obtain the base download URL for the VFeed data file.
327          */
328         public static final String VFEED_DOWNLOAD_URL = "vfeed.download_url";
329         /**
330          * The key to obtain the download file name for the VFeed data.
331          */
332         public static final String VFEED_DOWNLOAD_FILE = "vfeed.download_file";
333         /**
334          * The key to obtain the VFeed update status.
335          */
336         public static final String VFEED_UPDATE_STATUS = "vfeed.update_status";
337         /**
338          * The key to the HTTP request method for query last modified date.
339          */
340         public static final String DOWNLOADER_QUICK_QUERY_TIMESTAMP = "downloader.quick.query.timestamp";
341         /**
342          * The key to HTTP protocol list to use.
343          */
344         public static final String DOWNLOADER_TLS_PROTOCOL_LIST = "downloader.tls.protocols";
345         /**
346          * The key to determine if the CPE analyzer is enabled.
347          */
348         public static final String ANALYZER_CPE_ENABLED = "analyzer.cpe.enabled";
349         /**
350          * The key to determine if the CPE Suppression analyzer is enabled.
351          */
352         public static final String ANALYZER_CPE_SUPPRESSION_ENABLED = "analyzer.cpesuppression.enabled";
353         /**
354          * The key to determine if the Dependency Bundling analyzer is enabled.
355          */
356         public static final String ANALYZER_DEPENDENCY_BUNDLING_ENABLED = "analyzer.dependencybundling.enabled";
357         /**
358          * The key to determine if the Dependency Merging analyzer is enabled.
359          */
360         public static final String ANALYZER_DEPENDENCY_MERGING_ENABLED = "analyzer.dependencymerging.enabled";
361         /**
362          * The key to determine if the False Positive analyzer is enabled.
363          */
364         public static final String ANALYZER_FALSE_POSITIVE_ENABLED = "analyzer.falsepositive.enabled";
365         /**
366          * The key to determine if the File Name analyzer is enabled.
367          */
368         public static final String ANALYZER_FILE_NAME_ENABLED = "analyzer.filename.enabled";
369         /**
370          * The key to determine if the Hint analyzer is enabled.
371          */
372         public static final String ANALYZER_HINT_ENABLED = "analyzer.hint.enabled";
373         /**
374          * The key to determine if the Version Filter analyzer is enabled.
375          */
376         public static final String ANALYZER_VERSION_FILTER_ENABLED = "analyzer.versionfilter.enabled";
377         /**
378          * The key to determine if the NVD CVE analyzer is enabled.
379          */
380         public static final String ANALYZER_NVD_CVE_ENABLED = "analyzer.nvdcve.enabled";
381         /**
382          * The key to determine if the Vulnerability Suppression analyzer is
383          * enabled.
384          */
385         public static final String ANALYZER_VULNERABILITY_SUPPRESSION_ENABLED = "analyzer.vulnerabilitysuppression.enabled";
386         /**
387          * The key to determine if the NVD CVE updater should be enabled.
388          */
389         public static final String UPDATE_NVDCVE_ENABLED = "updater.nvdcve.enabled";
390         /**
391          * The key to determine if dependency-check should check if there is a
392          * new version available.
393          */
394         public static final String UPDATE_VERSION_CHECK_ENABLED = "updater.versioncheck.enabled";
395 
396         /**
397          * private constructor because this is a "utility" class containing
398          * constants
399          */
400         private KEYS() {
401             //do nothing
402         }
403     }
404     //</editor-fold>
405 
406     /**
407      * The logger.
408      */
409     private static final Logger LOGGER = LoggerFactory.getLogger(Settings.class);
410     /**
411      * The properties file location.
412      */
413     private static final String PROPERTIES_FILE = "dependencycheck.properties";
414     /**
415      * Thread local settings.
416      */
417     private static final ThreadLocal<Settings> LOCAL_SETTINGS = new ThreadLocal<Settings>();
418     /**
419      * The properties.
420      */
421     private Properties props = null;
422 
423     /**
424      * Private constructor for the Settings class. This class loads the
425      * properties files.
426      *
427      * @param propertiesFilePath the path to the base properties file to load
428      */
429     private Settings(String propertiesFilePath) {
430         InputStream in = null;
431         props = new Properties();
432         try {
433             in = this.getClass().getClassLoader().getResourceAsStream(propertiesFilePath);
434             props.load(in);
435         } catch (NullPointerException ex) {
436             LOGGER.error("Did not find settings file '{}'.", propertiesFilePath);
437             LOGGER.debug("", ex);
438         } catch (IOException ex) {
439             LOGGER.error("Unable to load settings from '{}'.", propertiesFilePath);
440             LOGGER.debug("", ex);
441         } finally {
442             if (in != null) {
443                 try {
444                     in.close();
445                 } catch (IOException ex) {
446                     LOGGER.trace("", ex);
447                 }
448             }
449         }
450         logProperties("Properties loaded", props);
451     }
452 
453     /**
454      * Initializes the thread local settings object. Note, to use the settings
455      * object you must call this method. However, you must also call
456      * Settings.cleanup() to properly release resources.
457      */
458     public static void initialize() {
459         LOCAL_SETTINGS.set(new Settings(PROPERTIES_FILE));
460     }
461 
462     /**
463      * Initializes the thread local settings object. Note, to use the settings
464      * object you must call this method. However, you must also call
465      * Settings.cleanup() to properly release resources.
466      *
467      * @param propertiesFilePath the path to the base properties file to load
468      */
469     public static void initialize(String propertiesFilePath) {
470         LOCAL_SETTINGS.set(new Settings(propertiesFilePath));
471     }
472 
473     /**
474      * Cleans up resources to prevent memory leaks.
475      *
476      */
477     public static void cleanup() {
478         cleanup(true);
479     }
480 
481     /**
482      * Cleans up resources to prevent memory leaks.
483      *
484      * @param deleteTemporary flag indicating whether any temporary directories
485      * generated should be removed
486      */
487     public static synchronized void cleanup(boolean deleteTemporary) {
488         if (deleteTemporary && tempDirectory != null && tempDirectory.exists()) {
489             FileUtils.delete(tempDirectory);
490             tempDirectory = null;
491         }
492         try {
493             LOCAL_SETTINGS.remove();
494         } catch (Throwable ex) {
495             LOGGER.debug("Error cleaning up Settings", ex);
496         }
497     }
498 
499     /**
500      * Gets the underlying instance of the Settings object.
501      *
502      * @return the Settings object
503      */
504     public static Settings getInstance() {
505         return LOCAL_SETTINGS.get();
506     }
507 
508     /**
509      * Sets the instance of the Settings object to use in this thread.
510      *
511      * @param instance the instance of the settings object to use in this thread
512      */
513     public static void setInstance(Settings instance) {
514         LOCAL_SETTINGS.set(instance);
515     }
516 
517     /**
518      * Logs the properties. This will not log any properties that contain
519      * 'password' in the key.
520      *
521      * @param header the header to print with the log message
522      * @param properties the properties to log
523      */
524     private static void logProperties(String header, Properties properties) {
525         if (LOGGER.isDebugEnabled()) {
526             final StringWriter sw = new StringWriter();
527             PrintWriter pw = null;
528             try {
529                 pw = new PrintWriter(sw);
530                 pw.format("%s:%n%n", header);
531                 final Enumeration<?> e = properties.propertyNames();
532                 while (e.hasMoreElements()) {
533                     final String key = (String) e.nextElement();
534                     if (key.contains("password")) {
535                         pw.format("%s='*****'%n", key);
536                     } else {
537                         final String value = properties.getProperty(key);
538                         if (value != null) {
539                             pw.format("%s='%s'%n", key, value);
540                         }
541                     }
542                 }
543                 pw.flush();
544                 LOGGER.debug(sw.toString());
545             } finally {
546                 if (pw != null) {
547                     pw.close();
548                 }
549             }
550 
551         }
552     }
553 
554     /**
555      * Sets a property value.
556      *
557      * @param key the key for the property
558      * @param value the value for the property
559      */
560     public static void setString(String key, String value) {
561         LOCAL_SETTINGS.get().props.setProperty(key, value);
562         LOGGER.debug("Setting: {}='{}'", key, value);
563     }
564 
565     /**
566      * Sets a property value only if the value is not null.
567      *
568      * @param key the key for the property
569      * @param value the value for the property
570      */
571     public static void setStringIfNotNull(String key, String value) {
572         if (null != value) {
573             setString(key, value);
574         }
575     }
576 
577     /**
578      * Sets a property value only if the value is not null and not empty.
579      *
580      * @param key the key for the property
581      * @param value the value for the property
582      */
583     public static void setStringIfNotEmpty(String key, String value) {
584         if (null != value && !value.isEmpty()) {
585             setString(key, value);
586         }
587     }
588 
589     /**
590      * Sets a property value.
591      *
592      * @param key the key for the property
593      * @param value the value for the property
594      */
595     public static void setBoolean(String key, boolean value) {
596         setString(key, Boolean.toString(value));
597     }
598 
599     /**
600      * Sets a property value only if the value is not null.
601      *
602      * @param key the key for the property
603      * @param value the value for the property
604      */
605     public static void setBooleanIfNotNull(String key, Boolean value) {
606         if (null != value) {
607             setBoolean(key, value);
608         }
609     }
610 
611     /**
612      * Sets a property value.
613      *
614      * @param key the key for the property
615      * @param value the value for the property
616      */
617     public static void setInt(String key, int value) {
618         LOCAL_SETTINGS.get().props.setProperty(key, String.valueOf(value));
619         LOGGER.debug("Setting: {}='{}'", key, value);
620     }
621 
622     /**
623      * Sets a property value only if the value is not null.
624      *
625      * @param key the key for the property
626      * @param value the value for the property
627      */
628     public static void setIntIfNotNull(String key, Integer value) {
629         if (null != value) {
630             setInt(key, value);
631         }
632     }
633 
634     /**
635      * Merges a new properties file into the current properties. This method
636      * allows for the loading of a user provided properties file.<br><br>
637      * <b>Note</b>: even if using this method - system properties will be loaded
638      * before properties loaded from files.
639      *
640      * @param filePath the path to the properties file to merge.
641      * @throws FileNotFoundException is thrown when the filePath points to a
642      * non-existent file
643      * @throws IOException is thrown when there is an exception loading/merging
644      * the properties
645      */
646     public static void mergeProperties(File filePath) throws FileNotFoundException, IOException {
647         FileInputStream fis = null;
648         try {
649             fis = new FileInputStream(filePath);
650             mergeProperties(fis);
651         } finally {
652             if (fis != null) {
653                 try {
654                     fis.close();
655                 } catch (IOException ex) {
656                     LOGGER.trace("close error", ex);
657                 }
658             }
659         }
660     }
661 
662     /**
663      * Merges a new properties file into the current properties. This method
664      * allows for the loading of a user provided properties file.<br><br>
665      * Note: even if using this method - system properties will be loaded before
666      * properties loaded from files.
667      *
668      * @param filePath the path to the properties file to merge.
669      * @throws FileNotFoundException is thrown when the filePath points to a
670      * non-existent file
671      * @throws IOException is thrown when there is an exception loading/merging
672      * the properties
673      */
674     public static void mergeProperties(String filePath) throws FileNotFoundException, IOException {
675         FileInputStream fis = null;
676         try {
677             fis = new FileInputStream(filePath);
678             mergeProperties(fis);
679         } finally {
680             if (fis != null) {
681                 try {
682                     fis.close();
683                 } catch (IOException ex) {
684                     LOGGER.trace("close error", ex);
685                 }
686             }
687         }
688     }
689 
690     /**
691      * Merges a new properties file into the current properties. This method
692      * allows for the loading of a user provided properties file.<br><br>
693      * <b>Note</b>: even if using this method - system properties will be loaded
694      * before properties loaded from files.
695      *
696      * @param stream an Input Stream pointing at a properties file to merge
697      * @throws IOException is thrown when there is an exception loading/merging
698      * the properties
699      */
700     public static void mergeProperties(InputStream stream) throws IOException {
701         LOCAL_SETTINGS.get().props.load(stream);
702         logProperties("Properties updated via merge", LOCAL_SETTINGS.get().props);
703     }
704 
705     /**
706      * Returns a value from the properties file as a File object. If the value
707      * was specified as a system property or passed in via the -Dprop=value
708      * argument - this method will return the value from the system properties
709      * before the values in the contained configuration file.
710      *
711      * @param key the key to lookup within the properties file
712      * @return the property from the properties file converted to a File object
713      */
714     public static File getFile(String key) {
715         final String file = getString(key);
716         if (file == null) {
717             return null;
718         }
719         return new File(file);
720     }
721 
722     /**
723      * Returns a value from the properties file as a File object. If the value
724      * was specified as a system property or passed in via the -Dprop=value
725      * argument - this method will return the value from the system properties
726      * before the values in the contained configuration file.
727      *
728      * This method will check the configured base directory and will use this as
729      * the base of the file path. Additionally, if the base directory begins
730      * with a leading "[JAR]\" sequence with the path to the folder containing
731      * the JAR file containing this class.
732      *
733      * @param key the key to lookup within the properties file
734      * @return the property from the properties file converted to a File object
735      */
736     protected static File getDataFile(String key) {
737         final String file = getString(key);
738         LOGGER.debug("Settings.getDataFile() - file: '{}'", file);
739         if (file == null) {
740             return null;
741         }
742         if (file.startsWith("[JAR]")) {
743             LOGGER.debug("Settings.getDataFile() - transforming filename");
744             final File jarPath = getJarPath();
745             LOGGER.debug("Settings.getDataFile() - jar file: '{}'", jarPath.toString());
746             final File retVal = new File(jarPath, file.substring(6));
747             LOGGER.debug("Settings.getDataFile() - returning: '{}'", retVal.toString());
748             return retVal;
749         }
750         return new File(file);
751     }
752 
753     /**
754      * Attempts to retrieve the folder containing the Jar file containing the
755      * Settings class.
756      *
757      * @return a File object
758      */
759     private static File getJarPath() {
760         final String jarPath = Settings.class.getProtectionDomain().getCodeSource().getLocation().getPath();
761         String decodedPath = ".";
762         try {
763             decodedPath = URLDecoder.decode(jarPath, "UTF-8");
764         } catch (UnsupportedEncodingException ex) {
765             LOGGER.trace("", ex);
766         }
767 
768         final File path = new File(decodedPath);
769         if (path.getName().toLowerCase().endsWith(".jar")) {
770             return path.getParentFile();
771         } else {
772             return new File(".");
773         }
774     }
775 
776     /**
777      * Returns a value from the properties file. If the value was specified as a
778      * system property or passed in via the -Dprop=value argument - this method
779      * will return the value from the system properties before the values in the
780      * contained configuration file.
781      *
782      * @param key the key to lookup within the properties file
783      * @param defaultValue the default value for the requested property
784      * @return the property from the properties file
785      */
786     public static String getString(String key, String defaultValue) {
787         final String str = System.getProperty(key, LOCAL_SETTINGS.get().props.getProperty(key, defaultValue));
788         return str;
789     }
790 
791     /**
792      * A reference to the temporary directory; used incase it needs to be
793      * deleted during cleanup.
794      */
795     private static File tempDirectory = null;
796 
797     /**
798      * Returns the temporary directory.
799      *
800      * @return the temporary directory
801      * @throws java.io.IOException thrown if the temporary directory does not
802      * exist and cannot be created
803      */
804     public static synchronized File getTempDirectory() throws IOException {
805         if (tempDirectory == null) {
806             final File baseTemp = new File(Settings.getString(Settings.KEYS.TEMP_DIRECTORY, System.getProperty("java.io.tmpdir")));
807             tempDirectory = FileUtils.createTempDirectory(baseTemp);
808         }
809         return tempDirectory;
810     }
811 
812     /**
813      * Returns a value from the properties file. If the value was specified as a
814      * system property or passed in via the -Dprop=value argument - this method
815      * will return the value from the system properties before the values in the
816      * contained configuration file.
817      *
818      * @param key the key to lookup within the properties file
819      * @return the property from the properties file
820      */
821     public static String getString(String key) {
822         return System.getProperty(key, LOCAL_SETTINGS.get().props.getProperty(key));
823     }
824 
825     /**
826      * Removes a property from the local properties collection. This is mainly
827      * used in test cases.
828      *
829      * @param key the property key to remove
830      */
831     public static void removeProperty(String key) {
832         LOCAL_SETTINGS.get().props.remove(key);
833     }
834 
835     /**
836      * Returns an int value from the properties file. If the value was specified
837      * as a system property or passed in via the -Dprop=value argument - this
838      * method will return the value from the system properties before the values
839      * in the contained configuration file.
840      *
841      * @param key the key to lookup within the properties file
842      * @return the property from the properties file
843      * @throws InvalidSettingException is thrown if there is an error retrieving
844      * the setting
845      */
846     public static int getInt(String key) throws InvalidSettingException {
847         try {
848             return Integer.parseInt(Settings.getString(key));
849         } catch (NumberFormatException ex) {
850             throw new InvalidSettingException("Could not convert property '" + key + "' to an int.", ex);
851         }
852     }
853 
854     /**
855      * Returns an int value from the properties file. If the value was specified
856      * as a system property or passed in via the -Dprop=value argument - this
857      * method will return the value from the system properties before the values
858      * in the contained configuration file.
859      *
860      * @param key the key to lookup within the properties file
861      * @param defaultValue the default value to return
862      * @return the property from the properties file or the defaultValue if the
863      * property does not exist or cannot be converted to an integer
864      */
865     public static int getInt(String key, int defaultValue) {
866         int value;
867         try {
868             value = Integer.parseInt(Settings.getString(key));
869         } catch (NumberFormatException ex) {
870             if (!Settings.getString(key, "").isEmpty()) {
871                 LOGGER.debug("Could not convert property '{}={}' to an int; using {} instead.", key, Settings.getString(key), defaultValue);
872             }
873             value = defaultValue;
874         }
875         return value;
876     }
877 
878     /**
879      * Returns a long value from the properties file. If the value was specified
880      * as a system property or passed in via the -Dprop=value argument - this
881      * method will return the value from the system properties before the values
882      * in the contained configuration file.
883      *
884      * @param key the key to lookup within the properties file
885      * @return the property from the properties file
886      * @throws InvalidSettingException is thrown if there is an error retrieving
887      * the setting
888      */
889     public static long getLong(String key) throws InvalidSettingException {
890         try {
891             return Long.parseLong(Settings.getString(key));
892         } catch (NumberFormatException ex) {
893             throw new InvalidSettingException("Could not convert property '" + key + "' to a long.", ex);
894         }
895     }
896 
897     /**
898      * Returns a boolean value from the properties file. If the value was
899      * specified as a system property or passed in via the
900      * <code>-Dprop=value</code> argument this method will return the value from
901      * the system properties before the values in the contained configuration
902      * file.
903      *
904      * @param key the key to lookup within the properties file
905      * @return the property from the properties file
906      * @throws InvalidSettingException is thrown if there is an error retrieving
907      * the setting
908      */
909     public static boolean getBoolean(String key) throws InvalidSettingException {
910         return Boolean.parseBoolean(Settings.getString(key));
911     }
912 
913     /**
914      * Returns a boolean value from the properties file. If the value was
915      * specified as a system property or passed in via the
916      * <code>-Dprop=value</code> argument this method will return the value from
917      * the system properties before the values in the contained configuration
918      * file.
919      *
920      * @param key the key to lookup within the properties file
921      * @param defaultValue the default value to return if the setting does not
922      * exist
923      * @return the property from the properties file
924      * @throws InvalidSettingException is thrown if there is an error retrieving
925      * the setting
926      */
927     public static boolean getBoolean(String key, boolean defaultValue) throws InvalidSettingException {
928         return Boolean.parseBoolean(Settings.getString(key, Boolean.toString(defaultValue)));
929     }
930 
931     /**
932      * Returns a connection string from the configured properties. If the
933      * connection string contains a %s, this method will determine the 'data'
934      * directory and replace the %s with the path to the data directory. If the
935      * data directory does not exists it will be created.
936      *
937      * @param connectionStringKey the property file key for the connection
938      * string
939      * @param dbFileNameKey the settings key for the db filename
940      * @return the connection string
941      * @throws IOException thrown the data directory cannot be created
942      * @throws InvalidSettingException thrown if there is an invalid setting
943      */
944     public static String getConnectionString(String connectionStringKey, String dbFileNameKey)
945             throws IOException, InvalidSettingException {
946         final String connStr = Settings.getString(connectionStringKey);
947         if (connStr == null) {
948             final String msg = String.format("Invalid properties file; %s is missing.", connectionStringKey);
949             throw new InvalidSettingException(msg);
950         }
951         if (connStr.contains("%s")) {
952             final File directory = getDataDirectory();
953             String fileName = null;
954             if (dbFileNameKey != null) {
955                 fileName = Settings.getString(dbFileNameKey);
956             }
957             if (fileName == null) {
958                 final String msg = String.format("Invalid properties file to get a file based connection string; '%s' must be defined.",
959                         dbFileNameKey);
960                 throw new InvalidSettingException(msg);
961             }
962             if (connStr.startsWith("jdbc:h2:file:") && fileName.endsWith(".h2.db")) {
963                 fileName = fileName.substring(0, fileName.length() - 6);
964             }
965             // yes, for H2 this path won't actually exists - but this is sufficient to get the value needed
966             final File dbFile = new File(directory, fileName);
967             final String cString = String.format(connStr, dbFile.getCanonicalPath());
968             LOGGER.debug("Connection String: '{}'", cString);
969             return cString;
970         }
971         return connStr;
972     }
973 
974     /**
975      * Retrieves the directory that the JAR file exists in so that we can ensure
976      * we always use a common data directory for the embedded H2 database. This
977      * is public solely for some unit tests; otherwise this should be private.
978      *
979      * @return the data directory to store data files
980      * @throws IOException is thrown if an IOException occurs of course...
981      */
982     public static File getDataDirectory() throws IOException {
983         final File path = Settings.getDataFile(Settings.KEYS.DATA_DIRECTORY);
984         if (path.exists() || path.mkdirs()) {
985             return path;
986         }
987         throw new IOException(String.format("Unable to create the data directory '%s'", path.getAbsolutePath()));
988     }
989 }