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.HashMap; 019import java.util.Map; 020import java.util.Map.Entry; 021 022import org.apache.avalon.framework.configuration.Configuration; 023import org.apache.avalon.framework.configuration.ConfigurationException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.commons.lang3.StringUtils; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import org.ametys.plugins.core.ui.util.ConfigurationHelper; 030import org.ametys.runtime.i18n.I18nizableText; 031import org.ametys.runtime.model.disableconditions.DisableCondition; 032import org.ametys.runtime.model.disableconditions.DisableConditions; 033import org.ametys.runtime.model.exception.UnknownTypeException; 034import org.ametys.runtime.model.type.ModelItemType; 035import org.ametys.runtime.model.type.ModelItemTypeExtensionPoint; 036 037/** 038 * {@link ModelItem} parser from an XML configuration. 039 */ 040public abstract class AbstractModelItemParser 041{ 042 private static final Logger __LOGGER = LoggerFactory.getLogger(AbstractModelItemParser.class); 043 044 /** The extension point to use to get available model items types */ 045 protected ModelItemTypeExtensionPoint _modelItemTypeExtensionPoint; 046 047 /** 048 * Creates a model item parser. 049 * @param modelItemTypeExtensionPoint the extension point to use to get available model items types 050 */ 051 public AbstractModelItemParser(ModelItemTypeExtensionPoint modelItemTypeExtensionPoint) 052 { 053 _modelItemTypeExtensionPoint = modelItemTypeExtensionPoint; 054 } 055 056 /** 057 * Parses an element definition from a XML configuration. 058 * @param <T> type of the parsed item 059 * @param serviceManager the service manager 060 * @param pluginName the plugin name declaring this item. 061 * @param itemConfig the XML configuration of the model item. 062 * @param model the model which defines the model item 063 * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent 064 * @return the parsed model item. 065 * @throws ConfigurationException if the configuration is not valid. 066 */ 067 @SuppressWarnings("unchecked") 068 public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, Configuration itemConfig, Model model, ModelItemGroup parent) throws ConfigurationException 069 { 070 return (T) parse(serviceManager, pluginName, "plugin." + pluginName, itemConfig, model, parent); 071 } 072 073 /** 074 * Parses an element definition from a XML configuration. 075 * @param <T> type of the parsed item 076 * @param serviceManager the service manager 077 * @param pluginName the plugin name declaring this item. 078 * @param catalog the catalog 079 * @param itemConfig the XML configuration of the model item. 080 * @param model the model which defines the model item 081 * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent 082 * @return the parsed model item. 083 * @throws ConfigurationException if the configuration is not valid. 084 */ 085 @SuppressWarnings("unchecked") 086 public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration itemConfig, Model model, ModelItemGroup parent) throws ConfigurationException 087 { 088 ModelItem modelItem = _createModelItem(itemConfig); 089 090 modelItem.setModel(model); 091 if (parent != null) 092 { 093 parent.addChild(modelItem); 094 } 095 096 modelItem.setName(_parseName(itemConfig)); 097 modelItem.setPluginName(pluginName); 098 modelItem.setLabel(_parseI18nizableText(itemConfig, catalog, "label")); 099 modelItem.setDescription(_parseI18nizableText(itemConfig, catalog, "description")); 100 modelItem.setType(_parseType(itemConfig)); 101 102 modelItem.setWidget(_parseWidget(itemConfig)); 103 modelItem.setWidgetParameters(_parseWidgetParameters(itemConfig, pluginName)); 104 modelItem.setDisableConditions(_parseDisableConditions(itemConfig)); 105 106 return (T) modelItem; 107 } 108 109 /** 110 * Create the model item to populate it. 111 * @param itemConfig the model item configuration to use. 112 * @return the item instantiated. 113 * @throws ConfigurationException if the configuration is not valid. 114 */ 115 protected abstract ModelItem _createModelItem(Configuration itemConfig) throws ConfigurationException; 116 117 /** 118 * Parses the name of the model item. 119 * @param itemConfig the model item configuration to use. 120 * @return the name of the model item. 121 * @throws ConfigurationException if the configuration is not valid. 122 */ 123 protected String _parseName(Configuration itemConfig) throws ConfigurationException 124 { 125 String itemName = itemConfig.getAttribute(_getNameConfigurationAttribute()); 126 127 if (!itemName.matches("^[a-zA-Z]((?!__)[a-zA-Z0-9_\\.-])*$")) 128 { 129 throw new ConfigurationException("Invalid model item name: " + itemName + ". The item name must start with a letter and must contain only letters, digits, underscore, dot or dash characters.", itemConfig); 130 } 131 132 return itemName; 133 } 134 135 /** 136 * Retrieves the name of the configuration attribute that contains the name of the model item 137 * @return the name of the configuration attribute that contains the name of the model item 138 */ 139 protected String _getNameConfigurationAttribute() 140 { 141 return "name"; 142 } 143 144 /** 145 * Parses a mandatory i18n text configuration, throwing an exception if empty. 146 * @param config the configuration to use. 147 * @param catalog the catalog 148 * @param name the child name. 149 * @return the i18n text. 150 * @throws ConfigurationException if the configuration is not valid. 151 */ 152 protected I18nizableText _parseI18nizableText(Configuration config, String catalog, String name) throws ConfigurationException 153 { 154 return I18nizableText.parseI18nizableText(config.getChild(name), catalog); 155 } 156 157 /** 158 * Parse an optional i18n text configuration, with a default i18n text value. 159 * @param config the configuration to use. 160 * @param catalog the catalog 161 * @param name the child name. 162 * @param defaultValueCatalog the catalog of the default i18n text value 163 * @param defaultValue the default i18n text value. 164 * @return the i18n text. 165 */ 166 protected I18nizableText _parseI18nizableText(Configuration config, String catalog, String name, String defaultValueCatalog, String defaultValue) 167 { 168 return I18nizableText.parseI18nizableText(config.getChild(name), catalog, new I18nizableText(defaultValueCatalog, defaultValue)); 169 } 170 171 /** 172 * Parses the type. 173 * @param config the model item configuration to use. 174 * @return the type. 175 * @throws ConfigurationException if the configuration is not valid. 176 */ 177 protected ModelItemType _parseType(Configuration config) throws ConfigurationException 178 { 179 String typeId = config.getAttribute("type"); 180 181 if (!_modelItemTypeExtensionPoint.hasExtension(typeId)) 182 { 183 String modelItemName = _parseName(config); 184 String availableTypes = StringUtils.join(_modelItemTypeExtensionPoint.getExtensionsIds(), ", "); 185 UnknownTypeException ute = new UnknownTypeException("The type '" + typeId + "' is not available for the extension point '" + _modelItemTypeExtensionPoint + "'. Available types are: '" + availableTypes + "'."); 186 throw new ConfigurationException("Unable to find the type '" + typeId + "' defined on the item '" + modelItemName + "'.", ute); 187 } 188 189 return _modelItemTypeExtensionPoint.getExtension(typeId); 190 } 191 192 /** 193 * Parses the disable condition. 194 * @param definitionConfiguration the configuration of the model item 195 * @return result the parsed disable condition to be converted in JSON 196 * @throws ConfigurationException if an error occurred 197 */ 198 protected DisableConditions _parseDisableConditions(Configuration definitionConfiguration) throws ConfigurationException 199 { 200 Configuration disableConditionsConfiguration = definitionConfiguration.getChild("disable-conditions", false); 201 return _parseConditions(disableConditionsConfiguration); 202 } 203 204 /** 205 * Parses the disable condition. 206 * @param disableConditionsConfiguration the configuration of the disable condition 207 * @return result the parsed disable condition to be converted in JSON 208 * @throws ConfigurationException if an error occurred 209 */ 210 protected DisableConditions _parseConditions(Configuration disableConditionsConfiguration) throws ConfigurationException 211 { 212 DisableConditions conditions = null; 213 214 if (disableConditionsConfiguration != null) 215 { 216 conditions = new DisableConditions(); 217 218 Configuration[] conditionsConfiguration = disableConditionsConfiguration.getChildren(); 219 for (Configuration conditionConfiguration : conditionsConfiguration) 220 { 221 String tagName = conditionConfiguration.getName(); 222 223 // Recursive case 224 if (tagName.equals("conditions")) 225 { 226 conditions.getSubConditions().add(_parseConditions(conditionConfiguration)); 227 } 228 else if (tagName.equals("condition")) 229 { 230 String id = conditionConfiguration.getAttribute("id"); 231 DisableCondition.OPERATOR operator = DisableCondition.OPERATOR.valueOf(conditionConfiguration.getAttribute("operator", "eq").toUpperCase()); 232 String value = conditionConfiguration.getValue(""); 233 234 235 DisableCondition condition = new DisableCondition(id, operator, value); 236 conditions.getConditions().add(condition); 237 } 238 } 239 240 conditions.setAssociation(DisableConditions.ASSOCIATION_TYPE.valueOf(disableConditionsConfiguration.getAttribute("type", "and").toUpperCase())); 241 } 242 243 return conditions; 244 } 245 246 /** 247 * Parses the widget. 248 * @param definitionConfig the element definition configuration to use. 249 * @return the widget or <code>null</code> if none defined. 250 * @throws ConfigurationException if the configuration is not valid. 251 */ 252 protected String _parseWidget(Configuration definitionConfig) throws ConfigurationException 253 { 254 return definitionConfig.getChild("widget").getValue(null); 255 } 256 257 /** 258 * Parses the widget's parameters 259 * @param definitionConfig the parameter ele;ent definition to use. 260 * @param pluginName the current plugin name. 261 * @return the widget's parameters in a Map 262 * @throws ConfigurationException if the configuration is not valid. 263 */ 264 protected Map<String, I18nizableText> _parseWidgetParameters(Configuration definitionConfig, String pluginName) throws ConfigurationException 265 { 266 Map<String, I18nizableText> widgetParams = new HashMap<>(); 267 268 Configuration widgetParamsConfig = definitionConfig.getChild("widget-params", false); 269 if (widgetParamsConfig != null) 270 { 271 Map<String, Object> parsedParams = ConfigurationHelper.parsePluginParameters(widgetParamsConfig, pluginName, __LOGGER); 272 273 for (Entry<String, Object> param : parsedParams.entrySet()) 274 { 275 String paramName = param.getKey(); 276 Object value = param.getValue(); 277 if (value instanceof I18nizableText) 278 { 279 widgetParams.put(paramName, (I18nizableText) value); 280 } 281 else if (value instanceof String) 282 { 283 widgetParams.put(paramName, new I18nizableText((String) value)); 284 } 285 else 286 { 287 __LOGGER.warn("Widget parameter '{}' at location {} is of type [{}] which is not supported. It will be ignored.", paramName, definitionConfig.getLocation(), value.getClass()); 288 } 289 } 290 } 291 292 return widgetParams; 293 } 294}