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