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