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.config.DisableCondition; 031import org.ametys.runtime.config.DisableConditions; 032import org.ametys.runtime.i18n.I18nizableText; 033import org.ametys.runtime.model.exception.UnknownTypeException; 034import org.ametys.runtime.model.type.ModelItemType; 035import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 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 AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> _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(AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> 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 disable condition 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 disableConditionConfiguration = definitionConfiguration.getChild("disable-conditions", false); 201 DisableConditions conditions = null; 202 203 if (disableConditionConfiguration != null) 204 { 205 conditions = new DisableConditions(); 206 207 Configuration[] conditionsConfiguration = disableConditionConfiguration.getChildren(); 208 for (Configuration conditionConfiguration : conditionsConfiguration) 209 { 210 String tagName = conditionConfiguration.getName(); 211 212 // Recursive case 213 if (tagName.equals("conditions")) 214 { 215 conditions.getSubConditions().add(_parseDisableConditions(conditionConfiguration)); 216 } 217 else if (tagName.equals("condition")) 218 { 219 String id = conditionConfiguration.getAttribute("id"); 220 DisableCondition.OPERATOR operator = DisableCondition.OPERATOR.valueOf(conditionConfiguration.getAttribute("operator", "eq").toUpperCase()); 221 String value = conditionConfiguration.getValue(""); 222 223 224 DisableCondition condition = new DisableCondition(id, operator, value); 225 conditions.getConditions().add(condition); 226 } 227 } 228 229 conditions.setAssociation(DisableConditions.ASSOCIATION_TYPE.valueOf(disableConditionConfiguration.getAttribute("type", "and").toUpperCase())); 230 } 231 232 return conditions; 233 } 234 235 /** 236 * Parses the widget. 237 * @param definitionConfig the element definition configuration to use. 238 * @return the widget or <code>null</code> if none defined. 239 * @throws ConfigurationException if the configuration is not valid. 240 */ 241 protected String _parseWidget(Configuration definitionConfig) throws ConfigurationException 242 { 243 return definitionConfig.getChild("widget").getValue(null); 244 } 245 246 /** 247 * Parses the widget's parameters 248 * @param definitionConfig the parameter ele;ent definition to use. 249 * @param pluginName the current plugin name. 250 * @return the widget's parameters in a Map 251 * @throws ConfigurationException if the configuration is not valid. 252 */ 253 protected Map<String, I18nizableText> _parseWidgetParameters(Configuration definitionConfig, String pluginName) throws ConfigurationException 254 { 255 Map<String, I18nizableText> widgetParams = new HashMap<>(); 256 257 Configuration widgetParamsConfig = definitionConfig.getChild("widget-params", false); 258 if (widgetParamsConfig != null) 259 { 260 Map<String, Object> parsedParams = ConfigurationHelper.parsePluginParameters(widgetParamsConfig, pluginName, __LOGGER); 261 262 for (Entry<String, Object> param : parsedParams.entrySet()) 263 { 264 String paramName = param.getKey(); 265 Object value = param.getValue(); 266 if (value instanceof I18nizableText) 267 { 268 widgetParams.put(paramName, (I18nizableText) value); 269 } 270 else if (value instanceof String) 271 { 272 widgetParams.put(paramName, new I18nizableText((String) value)); 273 } 274 else 275 { 276 __LOGGER.warn("Widget parameter '{}' at location {} is of type [{}] which is not supported. It will be ignored.", paramName, definitionConfig.getLocation(), value.getClass()); 277 } 278 } 279 } 280 281 return widgetParams; 282 } 283}