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