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