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