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