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