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