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.model; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Map.Entry; 023 024import org.apache.avalon.framework.component.ComponentException; 025import org.apache.avalon.framework.configuration.Configuration; 026import org.apache.avalon.framework.configuration.ConfigurationException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.commons.lang3.tuple.ImmutablePair; 029import org.apache.commons.lang3.tuple.Pair; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import org.ametys.plugins.core.ui.util.ConfigurationHelper; 034import org.ametys.runtime.config.ConfigManager; 035import org.ametys.runtime.config.DisableCondition; 036import org.ametys.runtime.config.DisableConditions; 037import org.ametys.runtime.i18n.I18nizableText; 038import org.ametys.runtime.model.type.ElementType; 039import org.ametys.runtime.model.type.ModelItemType; 040import org.ametys.runtime.parameter.DefaultValidator; 041import org.ametys.runtime.parameter.Validator; 042import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 043import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 044 045/** 046 * {@link ElementDefinition} parser from an XML configuration. 047 */ 048public class ElementDefinitionParser extends AbstractModelItemParser 049{ 050 private static final Logger __LOGGER = LoggerFactory.getLogger(ElementDefinitionParser.class); 051 052 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 053 private ThreadSafeComponentManager<Validator> _validatorManager; 054 private final Map<ElementDefinition, String> _validatorsToLookup = new HashMap<>(); 055 private final Map<ElementDefinition, String> _enumeratorsToLookup = new HashMap<>(); 056 057 /** 058 * Creates an element definition parser. 059 * @param modelItemTypeExtensionPoint the extension point to use to get available element types 060 * @param enumeratorManager the enumerator component manager. 061 * @param validatorManager the validator component manager. 062 */ 063 public ElementDefinitionParser(AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> modelItemTypeExtensionPoint, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 064 { 065 super(modelItemTypeExtensionPoint); 066 _enumeratorManager = enumeratorManager; 067 _validatorManager = validatorManager; 068 } 069 070 @Override 071 @SuppressWarnings("unchecked") 072 public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration definitionConfig, Model model, ModelItemGroup parent) throws ConfigurationException 073 { 074 ElementDefinition definition = (ElementDefinition) super.parse(serviceManager, pluginName, catalog, definitionConfig, model, parent); 075 076 definition.setPluginName(pluginName); 077 definition.setParsedDefaultValues(_parseDefaultValues(definitionConfig, definition)); 078 definition.setMultiple(_parseMultiple(definitionConfig)); 079 080 definition.setWidget(_parseWidget(definitionConfig)); 081 definition.setWidgetParameters(_parseWidgetParameters(definitionConfig, pluginName)); 082 083 _parseAndSetEnumerator(pluginName, catalog, definition, definitionConfig); 084 _parseAndSetValidator(pluginName, definition, definitionConfig); 085 086 definition.setDisableConditions(_parseDisableConditions(definitionConfig)); 087 088 return (T) definition; 089 } 090 091 @Override 092 protected ElementDefinition _createModelItem(Configuration definitionConfig) throws ConfigurationException 093 { 094 return new ElementDefinition(); 095 } 096 097 /** 098 * Parses the default values. 099 * @param definitionConfig the element definition configuration. 100 * @param definition the element definition. 101 * @return the default values and their types or <code>null</code> if none default value is defined. 102 * @throws ConfigurationException if the configuration is not valid. 103 */ 104 protected List<Pair<String, Object>> _parseDefaultValues(Configuration definitionConfig, ElementDefinition definition) throws ConfigurationException 105 { 106 Configuration[] defaultValueConfigs = definitionConfig.getChildren("default-value"); 107 if (defaultValueConfigs.length > 0) 108 { 109 List<Pair<String, Object>> defaultValues = new ArrayList<>(); 110 111 for (Configuration defaultValueConfig : defaultValueConfigs) 112 { 113 String defaultValueType = defaultValueConfig.getAttribute("type", null); 114 Object defaultValue = _parseDefaultValue(defaultValueConfig, definition, defaultValueType); 115 defaultValues.add(new ImmutablePair<>(defaultValueType, defaultValue)); 116 } 117 118 return defaultValues; 119 } 120 else 121 { 122 return null; 123 } 124 } 125 126 /** 127 * Parses the default value. 128 * @param defaultValueConfig the default value configuration. 129 * @param definition the element definition. 130 * @param defaultValueType the type of the default value 131 * @return the default value or <code>null</code> if none default value is defined. 132 * @throws ConfigurationException if the configuration is not valid. 133 */ 134 protected Object _parseDefaultValue(Configuration defaultValueConfig, ElementDefinition definition, String defaultValueType) throws ConfigurationException 135 { 136 ElementType type = definition.getType(); 137 if (defaultValueType != null) 138 { 139 if (ElementDefinition.CONFIG_DEFAULT_VALUE_TYPE.equals(defaultValueType)) 140 { 141 String configParamName = defaultValueConfig.getValue(); 142 if (ConfigManager.getInstance().hasModelItem(configParamName)) 143 { 144 ModelItem configParamDefinition = ConfigManager.getInstance().getModelItem(configParamName); 145 String configParamTypeId = configParamDefinition.getType().getId(); 146 if (configParamTypeId.equals(type.getId())) 147 { 148 return configParamName; 149 } 150 else 151 { 152 throw new ConfigurationException("The configuration parameter '" + configParamName + " (" + configParamTypeId + ")' cannot be used as default value for item '" + definition.getPath() + " (" + type.getId() + ")': types are not the same.", defaultValueConfig); 153 } 154 } 155 else 156 { 157 throw new ConfigurationException("The configuration parameter '" + configParamName + "' does not exist, it cannot be used as default value for item '" + definition.getPath() + " (" + type.getId() + ")'.", defaultValueConfig); 158 } 159 } 160 else 161 { 162 throw new ConfigurationException("The type '" + defaultValueType + "' is not available for the default value of item '" + definition.getPath() + " (" + type.getId() + ")'.", defaultValueConfig); 163 } 164 } 165 else 166 { 167 return type.parseConfiguration(defaultValueConfig); 168 } 169 } 170 171 /** 172 * Parses the multiple attribute. 173 * @param definitionConfig the element definition configuration to use. 174 * @return the true if the element is multiple, false otherwise. 175 * @throws ConfigurationException if the configuration is not valid. 176 */ 177 protected Boolean _parseMultiple(Configuration definitionConfig) throws ConfigurationException 178 { 179 return definitionConfig.getAttributeAsBoolean("multiple", false); 180 } 181 182 /** 183 * Parses the widget. 184 * @param definitionConfig the element definition configuration to use. 185 * @return the widget or <code>null</code> if none defined. 186 * @throws ConfigurationException if the configuration is not valid. 187 */ 188 protected String _parseWidget(Configuration definitionConfig) throws ConfigurationException 189 { 190 return definitionConfig.getChild("widget").getValue(null); 191 } 192 193 /** 194 * Parses the widget's parameters 195 * @param definitionConfig the parameter ele;ent definition to use. 196 * @param pluginName the current plugin name. 197 * @return the widget's parameters in a Map 198 * @throws ConfigurationException if the configuration is not valid. 199 */ 200 protected Map<String, I18nizableText> _parseWidgetParameters(Configuration definitionConfig, String pluginName) throws ConfigurationException 201 { 202 Map<String, I18nizableText> widgetParams = new HashMap<>(); 203 204 Configuration widgetParamsConfig = definitionConfig.getChild("widget-params", false); 205 if (widgetParamsConfig != null) 206 { 207 Map<String, Object> parsedParams = ConfigurationHelper.parsePluginParameters(widgetParamsConfig, pluginName, __LOGGER); 208 209 for (Entry<String, Object> param : parsedParams.entrySet()) 210 { 211 String paramName = param.getKey(); 212 Object value = param.getValue(); 213 if (value instanceof I18nizableText) 214 { 215 widgetParams.put(paramName, (I18nizableText) value); 216 } 217 else if (value instanceof String) 218 { 219 widgetParams.put(paramName, new I18nizableText((String) value)); 220 } 221 else 222 { 223 __LOGGER.warn("Widget parameter '{}' at location {} is of type [{}] which is not supported. It will be ignored.", paramName, definitionConfig.getLocation(), value.getClass()); 224 } 225 } 226 } 227 228 return widgetParams; 229 } 230 231 /** 232 * Parses the enumerator. 233 * @param pluginName the plugin name. 234 * @param catalog the catalog 235 * @param definition the element definition. 236 * @param definitionConfig the element definition configuration. 237 * @throws ConfigurationException if the configuration is not valid. 238 */ 239 @SuppressWarnings("unchecked") 240 protected void _parseAndSetEnumerator(String pluginName, String catalog, ElementDefinition definition, Configuration definitionConfig) throws ConfigurationException 241 { 242 Configuration enumeratorConfig = definitionConfig.getChild("enumeration", false); 243 244 if (enumeratorConfig != null) 245 { 246 Configuration customEnumerator = enumeratorConfig.getChild("custom-enumerator", false); 247 248 if (customEnumerator != null) 249 { 250 final String enumeratorClassName = customEnumerator.getAttribute("class"); 251 final String enumeratorRole = definition.getPath(); 252 253 try 254 { 255 Class enumeratorClass = Class.forName(enumeratorClassName); 256 _enumeratorManager.addComponent(pluginName, null, enumeratorRole, enumeratorClass, definitionConfig); 257 } 258 catch (Exception e) 259 { 260 throw new ConfigurationException("Unable to instantiate enumerator for class: " + enumeratorClassName, e); 261 } 262 263 // This enumerator will be affected later when enumeratorManager 264 // will be initialized in lookupComponents() call 265 _enumeratorsToLookup.put(definition, enumeratorRole); 266 267 // Add the custom enumerator information to the element definition 268 definition.setCustomEnumerator(enumeratorClassName); 269 definition.setEnumeratorConfiguration(customEnumerator); 270 } 271 else 272 { 273 StaticEnumerator staticEnumerator = new StaticEnumerator(); 274 275 for (Configuration entryConfig : enumeratorConfig.getChildren("entry")) 276 { 277 Configuration valueConfiguration = entryConfig.getChild("value"); 278 Object value = definition.getType().parseConfiguration(valueConfiguration); 279 I18nizableText label = null; 280 281 if (entryConfig.getChild("label", false) != null) 282 { 283 label = _parseI18nizableText(entryConfig, catalog, "label"); 284 } 285 286 staticEnumerator.add(label, value); 287 } 288 289 definition.setEnumerator(staticEnumerator); 290 } 291 } 292 } 293 294 /** 295 * Parses the validator. 296 * @param pluginName the plugin name. 297 * @param definition the element definition. 298 * @param definitionConfig the element definition configuration. 299 * @throws ConfigurationException if the configuration is not valid. 300 */ 301 @SuppressWarnings("unchecked") 302 protected void _parseAndSetValidator(String pluginName, ElementDefinition definition, Configuration definitionConfig) throws ConfigurationException 303 { 304 Configuration validatorConfig = definitionConfig.getChild("validation", false); 305 306 if (validatorConfig != null) 307 { 308 Configuration customValidator = validatorConfig.getChild("custom-validator", false); 309 String validatorClassName; 310 311 if (customValidator != null) 312 { 313 validatorClassName = customValidator.getAttribute("class"); 314 315 // Add the custom validator information to the element definition 316 definition.setCustomValidator(validatorClassName); 317 definition.setValidatorConfiguration(customValidator); 318 } 319 else 320 { 321 validatorClassName = DefaultValidator.class.getName(); 322 } 323 324 final String validatorRole = definition.getPath(); 325 326 try 327 { 328 Class validatorClass = Class.forName(validatorClassName); 329 _validatorManager.addComponent(pluginName, null, validatorRole, validatorClass, definitionConfig); 330 } 331 catch (Exception e) 332 { 333 throw new ConfigurationException("Unable to instantiate validator for class: " + validatorClassName, e); 334 } 335 336 // Will be affected later when validatorManager will be initialized 337 // in lookupComponents() call 338 _validatorsToLookup.put(definition, validatorRole); 339 } 340 } 341 342 /** 343 * Parses the disable condition. 344 * @param definitionConfiguration the configuration of the disable condition 345 * @return result the parsed disable condition to be converted in JSON 346 * @throws ConfigurationException if an error occurred 347 */ 348 protected DisableConditions _parseDisableConditions(Configuration definitionConfiguration) throws ConfigurationException 349 { 350 Configuration disableConditionConfiguration = definitionConfiguration.getChild("disable-conditions", false); 351 DisableConditions conditions = null; 352 353 if (disableConditionConfiguration != null) 354 { 355 conditions = new DisableConditions(); 356 357 Configuration[] conditionsConfiguration = disableConditionConfiguration.getChildren(); 358 for (Configuration conditionConfiguration : conditionsConfiguration) 359 { 360 String tagName = conditionConfiguration.getName(); 361 362 // Recursive case 363 if (tagName.equals("conditions")) 364 { 365 conditions.getSubConditions().add(_parseDisableConditions(conditionConfiguration)); 366 } 367 else if (tagName.equals("condition")) 368 { 369 String id = conditionConfiguration.getAttribute("id"); 370 DisableCondition.OPERATOR operator = DisableCondition.OPERATOR.valueOf(conditionConfiguration.getAttribute("operator", "eq").toUpperCase()); 371 String value = conditionConfiguration.getValue(""); 372 373 374 DisableCondition condition = new DisableCondition(id, operator, value); 375 conditions.getConditions().add(condition); 376 } 377 } 378 379 conditions.setAssociation(DisableConditions.ASSOCIATION_TYPE.valueOf(disableConditionConfiguration.getAttribute("type", "and").toUpperCase())); 380 } 381 382 return conditions; 383 } 384 385 /** 386 * Retrieves local validators and enumerators components and set them into 387 * previous parsed element definition. 388 * @throws Exception if an error occurs. 389 */ 390 @SuppressWarnings("unchecked") 391 public void lookupComponents() throws Exception 392 { 393 _validatorManager.initialize(); 394 _enumeratorManager.initialize(); 395 396 for (Map.Entry<ElementDefinition, String> entry : _validatorsToLookup.entrySet()) 397 { 398 ElementDefinition definition = entry.getKey(); 399 String validatorRole = entry.getValue(); 400 401 try 402 { 403 definition.setValidator(_validatorManager.lookup(validatorRole)); 404 } 405 catch (ComponentException e) 406 { 407 throw new Exception("Unable to lookup validator role: '" + validatorRole + "' for parameter: " + definition, e); 408 } 409 } 410 411 for (Map.Entry<ElementDefinition, String> entry : _enumeratorsToLookup.entrySet()) 412 { 413 ElementDefinition definition = entry.getKey(); 414 String enumeratorRole = entry.getValue(); 415 416 try 417 { 418 definition.setEnumerator(_enumeratorManager.lookup(enumeratorRole)); 419 } 420 catch (ComponentException e) 421 { 422 throw new Exception("Unable to lookup enumerator role: '" + enumeratorRole + "' for parameter: " + definition, e); 423 } 424 } 425 } 426}