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