001/* 002 * #%L 003 * Netarchivesuite - common 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023package dk.netarkivet.common.utils; 024 025import java.io.File; 026import java.io.InputStream; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.List; 030 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import dk.netarkivet.common.exceptions.ArgumentNotValid; 035import dk.netarkivet.common.exceptions.IOFailure; 036import dk.netarkivet.common.exceptions.UnknownID; 037 038/** 039 * Provides access to general application settings. The settings are retrieved from xml files. XML files may be 040 * specified one of two places: 1) Default settings in XML files, specified by class path. These are intended to be 041 * packaged in the jar files, to provide a fallback for settings. 2) Overriding settings in XML files in file systems. 042 * These are intended to override the necessary values with minimal XML files. The location of these files are either 043 * specified by the system property {@link #SETTINGS_FILE_PROPERTY}, multiple files can be separated by 044 * {@link File#pathSeparator}, that is ':' on linux and ';' on windows; or if that property is not set, the default 045 * location is {@link #DEFAULT_SETTINGS_FILEPATH}. 046 */ 047public class Settings { 048 049 /** Logger for this class. */ 050 private static final Logger log = LoggerFactory.getLogger(Settings.class); 051 052 /** 053 * The objects representing the contents of the settings xml files. For handling multithreaded instances this list 054 * must be initialised through the method Collections.synchronizedList(). 055 */ 056 private static final List<SimpleXml> fileSettingsXmlList; 057 058 /** 059 * The objects representing the contents of the default settings xml files in classpath. For handling multithreaded 060 * instances this list must be initialised through the method Collections.synchronizedList(). 061 */ 062 private static final List<SimpleXml> defaultClasspathSettingsXmlList; 063 064 static { 065 // All static initialization in one place 066 fileSettingsXmlList = Collections.synchronizedList(new ArrayList<SimpleXml>()); 067 defaultClasspathSettingsXmlList = Collections.synchronizedList(new ArrayList<SimpleXml>()); 068 // Perform an initial loading of the settings. 069 reload(); 070 } 071 072 /** 073 * This system property specifies alternative position(s) to look for settings files. If more files are specified, 074 * they should be separated by {@link File#pathSeparatorChar} 075 */ 076 public static final String SETTINGS_FILE_PROPERTY = "dk.netarkivet.settings.file"; 077 078 /** 079 * The file path to look for settings in, if the system property {@link #SETTINGS_FILE_PROPERTY} is not set. 080 */ 081 public static final String DEFAULT_SETTINGS_FILEPATH = "conf/settings.xml"; 082 083 /** The newest "last modified" date of all settings files. */ 084 private static long lastModified; 085 086 /** 087 * Return the file these settings are read from. If the property given in the constructor is set, that will be used 088 * to determine the file. If it is not set, the default settings file path given in the constructor will be used. 089 * 090 * @return The settings file. 091 */ 092 public static List<File> getSettingsFiles() { 093 String[] pathList = System.getProperty(SETTINGS_FILE_PROPERTY, DEFAULT_SETTINGS_FILEPATH).split( 094 File.pathSeparator); 095 List<File> result = new ArrayList<File>(); 096 for (String path : pathList) { 097 if (path.trim().length() != 0) { 098 File settingsFile = new File(path); 099 if (settingsFile.isFile()) { 100 result.add(settingsFile); 101 } 102 } 103 } 104 return result; 105 } 106 107 /** 108 * Gets a setting. The search order for a given setting is as follows: 109 * <p> 110 * First it is checked, if the argument key is set as a System property. If yes, return this value. If no, we 111 * continue the search. 112 * <p> 113 * Secondly, we check, if the setting is in one of the loaded settings xml files. If the value is there, it is 114 * returned. If no, we continue the search. 115 * <p> 116 * Finally, we check if the setting is in one of default settings files from classpath. If the value is there, it is 117 * returned. Otherwise an UnknownId exception is thrown. 118 * <p> 119 * Note: The retrieved value can be the empty string 120 * 121 * @param key name of the setting to retrieve 122 * @return the retrieved value 123 * @throws ArgumentNotValid if key is null or the empty string 124 * @throws UnknownID if no setting loaded matches key 125 * @throws IOFailure if IO Failure 126 */ 127 public static String get(String key) throws UnknownID, IOFailure, ArgumentNotValid { 128 ArgumentNotValid.checkNotNullOrEmpty(key, "String key"); 129 String val = System.getProperty(key); 130 if (val != null) { 131 return val; 132 } 133 134 // Key not in System.properties try loaded data instead 135 synchronized (fileSettingsXmlList) { 136 for (SimpleXml settingsXml : fileSettingsXmlList) { 137 if (settingsXml.hasKey(key)) { 138 return settingsXml.getString(key); 139 } 140 } 141 } 142 143 // Key not in file based settings, try classpath settings instead 144 synchronized (defaultClasspathSettingsXmlList) { 145 for (SimpleXml settingsXml : defaultClasspathSettingsXmlList) { 146 if (settingsXml.hasKey(key)) { 147 return settingsXml.getString(key); 148 } 149 } 150 } 151 throw new UnknownID("No match for key '" + key + "' in settings"); 152 } 153 154 /** 155 * Gets a setting as an int. This method calls get(key) and then parses the value as integer. 156 * 157 * @param key name of the setting to retrieve 158 * @return the retrieved int 159 * @throws ArgumentNotValid if key is null, the empty string or key is not parseable as an integer 160 * @throws UnknownID if no setting loaded matches key 161 */ 162 public static int getInt(String key) throws UnknownID, ArgumentNotValid { 163 String value = get(key); 164 try { 165 return Integer.parseInt(value); 166 } catch (NumberFormatException e) { 167 String msg = "Invalid setting. Value '" + value + "' for key '" + key 168 + "' could not be parsed as an integer."; 169 throw new ArgumentNotValid(msg, e); 170 } 171 } 172 173 /** 174 * Gets a setting as a long. This method calls get(key) and then parses the value as a long. 175 * 176 * @param key name of the setting to retrieve 177 * @return the retrieved long 178 * @throws ArgumentNotValid if key is null, the empty string or key is not parseable as a long 179 * @throws UnknownID if no setting loaded matches key 180 */ 181 public static long getLong(String key) throws UnknownID, ArgumentNotValid { 182 String value = get(key); 183 try { 184 return Long.parseLong(value); 185 } catch (NumberFormatException e) { 186 String msg = "Invalid setting. Value '" + value + "' for key '" + key + "' could not be parsed as a long."; 187 throw new ArgumentNotValid(msg, e); 188 } 189 } 190 191 /** 192 * Gets a setting as a double. This method calls get(key) and then parses the value as a double. 193 * 194 * @param key name of the setting to retrieve 195 * @return the retrieved double 196 * @throws ArgumentNotValid if key is null, the empty string or key is not parseable as a double 197 * @throws UnknownID if no setting loaded matches key 198 */ 199 public static double getDouble(String key) throws UnknownID, ArgumentNotValid { 200 String value = get(key); 201 try { 202 return Double.parseDouble(value); 203 } catch (NumberFormatException e) { 204 String msg = "Invalid setting. Value '" + value + "' for key '" + key 205 + "' could not be parsed as a double."; 206 throw new ArgumentNotValid(msg, e); 207 } 208 } 209 210 /** 211 * Gets a setting as a file. This method calls get(key) and then returns the value as a file. 212 * 213 * @param key name of the setting to retrieve 214 * @return the retrieved file 215 * @throws ArgumentNotValid if key is null, the empty string 216 * @throws UnknownID if no setting loaded matches ke 217 */ 218 public static File getFile(String key) { 219 ArgumentNotValid.checkNotNullOrEmpty(key, "String key"); 220 return new File(get(key)); 221 } 222 223 /** 224 * Gets a setting as a boolean. This method calls get(key) and then parses the value as a boolean. 225 * 226 * @param key name of the setting to retrieve 227 * @return the retrieved boolean 228 * @throws ArgumentNotValid if key is null or the empty string 229 * @throws UnknownID if no setting loaded matches key 230 */ 231 public static boolean getBoolean(String key) throws UnknownID, ArgumentNotValid { 232 ArgumentNotValid.checkNotNullOrEmpty(key, "String key"); 233 String value = get(key); 234 return Boolean.parseBoolean(value); 235 } 236 237 /** 238 * Gets a list of settings. First it is checked, if the key is registered as a System property. If yes, registered 239 * value is returned in a list of length 1. If no, the data loaded from the settings xml files are examined. If 240 * value is there, it is returned in a list. If not, the default settings from classpath are examined. If values for 241 * this setting are found here, they are returned. Otherwise, an UnknownId exception is thrown. 242 * <p> 243 * Note that the values will not be concatenated, the first place with a match will define the entire list. 244 * Furthemore the list cannot be empty. 245 * 246 * @param key name of the setting to retrieve 247 * @return the retrieved values (as a non-empty String array) 248 * @throws ArgumentNotValid if key is null or the empty string 249 * @throws UnknownID if no setting loaded matches key 250 */ 251 public static String[] getAll(String key) throws UnknownID, ArgumentNotValid { 252 ArgumentNotValid.checkNotNullOrEmpty(key, "key"); 253 String val = System.getProperty(key); 254 if (val != null) { 255 return new String[] {val}; 256 } 257 if (fileSettingsXmlList.isEmpty()) { 258 System.out.print("The list of loaded data settings is empty. Is this OK?"); 259 } 260 // Key not in System.properties try loaded data instead 261 synchronized (fileSettingsXmlList) { 262 for (SimpleXml settingsXml : fileSettingsXmlList) { 263 List<String> result = settingsXml.getList(key); 264 if (result.size() == 0) { 265 continue; 266 } 267 if (log.isDebugEnabled()) { 268 log.debug("Value found in loaded data: {}", StringUtils.conjoin(",", result)); 269 } 270 return result.toArray(new String[result.size()]); 271 } 272 } 273 274 // Key not in file based settings, try settings from classpath 275 synchronized (defaultClasspathSettingsXmlList) { 276 for (SimpleXml settingsXml : defaultClasspathSettingsXmlList) { 277 List<String> result = settingsXml.getList(key); 278 if (result.size() == 0) { 279 continue; 280 } 281 if (log.isDebugEnabled()) { 282 log.debug("Value found in classpath data: {}", StringUtils.conjoin(",", result)); 283 } 284 return result.toArray(new String[result.size()]); 285 } 286 } 287 throw new UnknownID("No match for key '" + key + "' in settings"); 288 } 289 290 /** 291 * Sets the key to one or more values. Calls to this method are forgotten whenever the {@link #reload()} is 292 * executed. 293 * <p> 294 * TODO write these values to its own simpleXml structure, that are not reset during reload. 295 * 296 * @param key The settings key to add this under, legal keys are fields in this class. 297 * @param values The (ordered) list of values to put under this key. 298 * @throws ArgumentNotValid if key or values are null 299 * @throws UnknownID if the key does not already exist 300 */ 301 public static void set(String key, String... values) { 302 ArgumentNotValid.checkNotNullOrEmpty(key, "key"); 303 ArgumentNotValid.checkNotNull(values, "values"); 304 305 if (fileSettingsXmlList.isEmpty()) { 306 fileSettingsXmlList.add(new SimpleXml("settings")); 307 } 308 SimpleXml simpleXml = fileSettingsXmlList.get(0); 309 if (simpleXml.hasKey(key)) { 310 simpleXml.update(key, values); 311 } else { 312 simpleXml.add(key, values); 313 } 314 } 315 316 /** 317 * Reloads the settings. This will reload the settings from disk, and forget all settings that were set with 318 * {@link #set} 319 * <p> 320 * The field {@link #lastModified} is updated to timestamp of the settings file that has been changed most recently. 321 * 322 * @throws IOFailure if settings cannot be loaded 323 */ 324 public static synchronized void reload() { 325 lastModified = 0; 326 List<File> settingsFiles = getSettingsFiles(); 327 List<SimpleXml> simpleXmlList = new ArrayList<SimpleXml>(); 328 for (File settingsFile : settingsFiles) { 329 if (settingsFile.isFile()) { 330 simpleXmlList.add(new SimpleXml(settingsFile)); 331 } else { 332 log.warn("The file '{}' is not a file, and therefore not loaded", settingsFile.getAbsolutePath()); 333 } 334 if (settingsFile.lastModified() > lastModified) { 335 lastModified = settingsFile.lastModified(); 336 } 337 } 338 synchronized (fileSettingsXmlList) { 339 fileSettingsXmlList.clear(); 340 fileSettingsXmlList.addAll(simpleXmlList); 341 } 342 } 343 344 /** 345 * Add the settings file represented by this path to the list of default classpath settings. 346 * 347 * @param defaultClasspathSettingsPath the given default classpath setting. 348 */ 349 public static void addDefaultClasspathSettings(String defaultClasspathSettingsPath) { 350 ArgumentNotValid.checkNotNullOrEmpty(defaultClasspathSettingsPath, "String defaultClasspathSettingsPath"); 351 InputStream stream = Thread.currentThread().getContextClassLoader() 352 .getResourceAsStream(defaultClasspathSettingsPath); 353 if (stream != null) { 354 defaultClasspathSettingsXmlList.add(new SimpleXml(stream)); 355 } else { 356 log.warn("Unable to read the settings file represented by path: '{}'", defaultClasspathSettingsPath); 357 } 358 } 359 360 /** 361 * Get a tree view of a part of the settings. Note: settings read with this mechanism do not support overriding with 362 * system properties! 363 * 364 * @param path Dotted path to a unique element in the tree. 365 * @return The part of the setting structure below the element given. 366 */ 367 public static StringTree<String> getTree(String path) { 368 synchronized (fileSettingsXmlList) { 369 for (SimpleXml settingsXml : fileSettingsXmlList) { 370 if (settingsXml.hasKey(path)) { 371 return settingsXml.getTree(path); 372 } 373 } 374 } 375 376 // Key not in file based settings, try classpath settings instead 377 synchronized (defaultClasspathSettingsXmlList) { 378 for (SimpleXml settingsXml : defaultClasspathSettingsXmlList) { 379 if (settingsXml.hasKey(path)) { 380 return settingsXml.getTree(path); 381 } 382 } 383 } 384 throw new UnknownID("No match for key '" + path + "' in settings"); 385 } 386 387}