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