001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.config; 017 018import java.io.File; 019import java.util.HashMap; 020import java.util.Map; 021import java.util.Map.Entry; 022import java.util.Set; 023 024import org.apache.avalon.framework.configuration.Configuration; 025import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import org.ametys.runtime.model.DefinitionAndValue; 030import org.ametys.runtime.model.ElementDefinition; 031import org.ametys.runtime.model.Model; 032import org.ametys.runtime.model.ModelItem; 033import org.ametys.runtime.model.type.DataContext; 034import org.ametys.runtime.model.type.ElementType; 035import org.ametys.runtime.model.type.xml.XMLElementType; 036 037/** 038 * Bean to read / Write config file 039 */ 040public final class Config 041{ 042 // Logger for traces 043 private static Logger __logger = LoggerFactory.getLogger(Config.class); 044 045 // shared instance for optimization 046 private static Config __config; 047 048 // Initialization status 049 private static boolean __initialized; 050 051 // config file 052 private static String __filename; 053 054 // true if the config file does exist 055 private static boolean __fileExists; 056 057 // The last modification date 058 private static long __lastModified = -1; 059 060 // The configuration model 061 private static Model __model; 062 063 // Typed value (filled after read) 064 private Map<String, DefinitionAndValue> _definitionAndValues; 065 066 private Config() 067 { 068 } 069 070 /** 071 * Get the instance of Config using the config file. 072 * @return An instance of Config containing config file values and definition, or null if config file cannot be read. 073 */ 074 public static Config getInstance() 075 { 076 if (!__initialized) 077 { 078 return null; 079 } 080 081 if (__config == null) 082 { 083 try 084 { 085 __config = new Config(); 086 __config._load(); 087 } 088 catch (Exception e) 089 { 090 __logger.warn("Exception creating Config, it won't be accessible.", e); 091 return null; 092 } 093 } 094 else 095 { 096 __config._reloadIfNeeded(); 097 } 098 099 return __config; 100 } 101 102 /** 103 * Gets the configuration model 104 * @return the model or null if the configuration is not initialized 105 */ 106 public static Model getModel() 107 { 108 return __model; 109 } 110 111 /** 112 * Sets the configuration model 113 * @param model the model to set 114 */ 115 public static void setModel(Model model) 116 { 117 __model = model; 118 } 119 120 private void _load() throws Exception 121 { 122 __logger.info("Loading configuration values from file {}.", __filename); 123 _definitionAndValues = __read(); 124 } 125 126 private void _reloadIfNeeded() 127 { 128 if (__fileExists && new File(__filename).exists() && __lastModified < new File(__filename).lastModified()) 129 { 130 try 131 { 132 __logger.info("The config file has changed. Let's reload."); 133 _load(); 134 } 135 catch (Exception e) 136 { 137 // __lastModified was changed, so we will not fail several times 138 // _values was not modified 139 // __fileExists is still true 140 __logger.error("The config file '" + __filename + "' was modified but could not be reloaded due to an exception", e); 141 } 142 } 143 } 144 145 /** 146 * Get the last modification of the configuration file 147 * @return The last modified or -1 if the file does not exists 148 */ 149 public long getLastModified() 150 { 151 return __lastModified; 152 } 153 154 /** 155 * Read configuration file 156 * @return the configuration definition and values 157 * @throws Exception if a problem occurs reading values 158 */ 159 static Map<String, DefinitionAndValue> __read() throws Exception 160 { 161 Map<String, DefinitionAndValue> definitionAndValues = new HashMap<>(); 162 163 File configFile = new File(__filename); 164 __fileExists = configFile.exists(); 165 166 if (__fileExists) 167 { 168 __lastModified = configFile.lastModified(); 169 170 Configuration configuration = new DefaultConfigurationBuilder().buildFromFile(configFile); 171 for (Configuration parameter : configuration.getChildren()) 172 { 173 String parameterName = parameter.getName(); 174 if (__model.hasModelItem(parameterName)) 175 { 176 ModelItem modelItem = __model.getModelItem(parameterName); 177 178 if (modelItem instanceof ElementDefinition) 179 { 180 ElementDefinition definition = (ElementDefinition) modelItem; 181 ElementType type = definition.getType(); 182 183 if (type instanceof XMLElementType) 184 { 185 Object value = ((XMLElementType) type).read(configuration, parameterName); 186 DefinitionAndValue definitionAndValue = new DefinitionAndValue<>(null, definition, value); 187 definitionAndValues.put(parameterName, definitionAndValue); 188 } 189 } 190 } 191 else 192 { 193 __logger.warn("The parameter {} is not defined in configuration. This parameter is ignored.", parameterName); 194 } 195 } 196 } 197 else 198 { 199 __lastModified = -1; 200 } 201 202 return definitionAndValues; 203 } 204 205 /** 206 * Set the initialization status of the configuration 207 * @param initialized the initialization status of the configuration 208 */ 209 public static void setInitialized(boolean initialized) 210 { 211 __initialized = initialized; 212 } 213 214 /** 215 * Set the configuration filename 216 * @param filename Name with path of the configuration file 217 */ 218 public static void setFilename(String filename) 219 { 220 __filename = filename; 221 __fileExists = new File(__filename).exists(); 222 } 223 224 /** 225 * Returns true if the configuration filename exists. 226 * @return true if the configuration filename exists. 227 */ 228 public static boolean fileExists() 229 { 230 return __fileExists; 231 } 232 233 /** 234 * Retrieves all configuration parameter values 235 * @return the configuration values 236 */ 237 public Map<String, Object> getValues() 238 { 239 return __extractValues(_definitionAndValues); 240 } 241 242 static Map<String, Object> __extractValues(Map<String, DefinitionAndValue> definitionAndValues) 243 { 244 return DefinitionAndValue.extractValues(definitionAndValues); 245 } 246 247 /** 248 * Retrieves all configuration parameter values as JSON objects 249 * @return the configuration values as JSON objects 250 */ 251 public static Map<String, Object> getValuesAsJSONForClient() 252 { 253 Map<String, Object> jsonValues = new HashMap<>(); 254 255 Set<Entry<String, DefinitionAndValue>> values; 256 257 try 258 { 259 values = __read().entrySet(); 260 } 261 catch (Exception e) 262 { 263 __logger.warn("Config values are unreadable. Using default values", e); 264 return jsonValues; 265 } 266 267 for (Map.Entry<String, DefinitionAndValue> entry : values) 268 { 269 DefinitionAndValue definitionAndValue = entry.getValue(); 270 271 Object value = definitionAndValue.getValue(); 272 ElementDefinition definition = (ElementDefinition) definitionAndValue.getDefinition(); 273 274 ElementType<Object> type = definition.getType(); 275 Object jsonValue = type.valueToJSONForClient(value, DataContext.newInstance()); 276 277 jsonValues.put(entry.getKey(), jsonValue); 278 } 279 280 return jsonValues; 281 282 } 283 284 /** 285 * Retrieves the typed parameter value 286 * @param <T> type of the value to retrieve 287 * @param name name of the configuration parameter 288 * @return the parameter value 289 */ 290 @SuppressWarnings("unchecked") 291 public <T extends Object> T getValue(String name) 292 { 293 DefinitionAndValue definitionAndValue = _definitionAndValues.get(name); 294 if (definitionAndValue == null) 295 { 296 __logger.warn("The configuration parameter {} doesn't exist. A null value is retrieved.", name); 297 return null; 298 } 299 300 return (T) definitionAndValue.getValue(); 301 } 302 303 /** 304 * Retrieves the typed parameter value, or the default value 305 * @param name name of the configuration parameter 306 * @param useDefaultFromModel true to use the default value from the model, false to use the give default value 307 * @param defaultValue default value used if value is null and useDefaultFromModel is false, or if there is no default value on model 308 * @param <T> type of the value to retrieve 309 * @return the parameter value 310 */ 311 public <T> T getValue(String name, boolean useDefaultFromModel, T defaultValue) 312 { 313 DefinitionAndValue definitionAndValue = _definitionAndValues.get(name); 314 if (definitionAndValue == null) 315 { 316 __logger.warn("The configuration parameter {} doesn't exist. The given default value is retrieved.", name); 317 return defaultValue; 318 } 319 320 @SuppressWarnings("unchecked") 321 T value = (T) definitionAndValue.getValue(); 322 if (value != null) 323 { 324 return value; 325 } 326 327 if (useDefaultFromModel) 328 { 329 ElementDefinition definition = (ElementDefinition) definitionAndValue.getDefinition(); 330 @SuppressWarnings("unchecked") 331 T defaultFromModel = (T) definition.getDefaultValue(); 332 if (defaultFromModel != null) 333 { 334 return defaultFromModel; 335 } 336 __logger.debug("There is no default value in model for the configuration parameter {}. The given default value is retrieved.", name); 337 } 338 339 return defaultValue; 340 } 341 342 /** 343 * Dispose this {@link Config} instance 344 */ 345 public static void dispose() 346 { 347 __config = null; 348 } 349}