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;
022
023import org.apache.avalon.framework.component.ComponentException;
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.commons.lang3.tuple.ImmutablePair;
028import org.apache.commons.lang3.tuple.Pair;
029
030import org.ametys.runtime.config.ConfigManager;
031import org.ametys.runtime.i18n.I18nizableText;
032import org.ametys.runtime.model.type.ElementType;
033import org.ametys.runtime.model.type.ModelItemTypeExtensionPoint;
034import org.ametys.runtime.parameter.DefaultValidator;
035import org.ametys.runtime.parameter.Validator;
036import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
037
038/**
039 * {@link ElementDefinition} parser from an XML configuration.
040 */
041public class ElementDefinitionParser extends AbstractModelItemParser
042{
043    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
044    private ThreadSafeComponentManager<Validator> _validatorManager;
045    private final Map<ElementDefinition, String> _validatorsToLookup = new HashMap<>();
046    private final Map<ElementDefinition, String> _enumeratorsToLookup = new HashMap<>();
047    
048    /**
049     * Creates an element definition parser.
050     * @param modelItemTypeExtensionPoint the extension point to use to get available element types
051     * @param enumeratorManager the enumerator component manager.
052     * @param validatorManager the validator component manager.
053     */
054    public ElementDefinitionParser(ModelItemTypeExtensionPoint modelItemTypeExtensionPoint, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
055    {
056        super(modelItemTypeExtensionPoint);
057        _enumeratorManager = enumeratorManager;
058        _validatorManager = validatorManager;
059    }
060    
061    @Override
062    @SuppressWarnings("unchecked")
063    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration definitionConfig, Model model, ModelItemGroup parent) throws ConfigurationException
064    {
065        ElementDefinition definition = (ElementDefinition) super.parse(serviceManager, pluginName, catalog, definitionConfig, model, parent);
066        
067        definition.setParsedDefaultValues(_parseDefaultValues(definitionConfig, definition));
068        definition.setMultiple(_parseMultiple(definitionConfig));
069        
070        _parseAndSetEnumerator(pluginName, catalog, definition, definitionConfig);
071        _parseAndSetValidator(pluginName, definition, definitionConfig);
072        
073        return (T) definition;
074    }
075    
076    @Override
077    protected ElementDefinition _createModelItem(Configuration definitionConfig) throws ConfigurationException
078    {
079        return new DefaultElementDefinition();
080    }
081
082    /**
083     * Parses the default values.
084     * @param definitionConfig the element definition configuration.
085     * @param definition the element definition.
086     * @return the default values and their types or <code>null</code> if none default value is defined.
087     * @throws ConfigurationException if the configuration is not valid.
088     */
089    protected List<Pair<String, Object>> _parseDefaultValues(Configuration definitionConfig, ElementDefinition definition) throws ConfigurationException
090    {
091        Configuration[] defaultValueConfigs = definitionConfig.getChildren("default-value");
092        if (defaultValueConfigs.length > 0)
093        {
094            List<Pair<String, Object>> defaultValues = new ArrayList<>();
095            
096            for (Configuration defaultValueConfig : defaultValueConfigs)
097            {
098                String defaultValueType = defaultValueConfig.getAttribute("type", null);
099                Object defaultValue = _parseDefaultValue(defaultValueConfig, definition, defaultValueType);
100                defaultValues.add(new ImmutablePair<>(defaultValueType, defaultValue));
101            }
102            
103            return defaultValues;
104        }
105        else
106        {
107            return null;
108        }
109    }
110    
111    /**
112     * Parses the default value.
113     * @param defaultValueConfig the default value configuration.
114     * @param definition the element definition.
115     * @param defaultValueType the type of the default value
116     * @return the default value or <code>null</code> if none default value is defined.
117     * @throws ConfigurationException if the configuration is not valid.
118     */
119    protected Object _parseDefaultValue(Configuration defaultValueConfig, ElementDefinition definition, String defaultValueType) throws ConfigurationException
120    {
121        ElementType type = definition.getType();
122        if (defaultValueType != null)
123        {
124            if (ElementDefinition.CONFIG_DEFAULT_VALUE_TYPE.equals(defaultValueType))
125            {
126                String configParamName = defaultValueConfig.getValue();
127                if (ConfigManager.getInstance().hasModelItem(configParamName))
128                {
129                    ModelItem configParamDefinition = ConfigManager.getInstance().getModelItem(configParamName);
130                    String configParamTypeId = configParamDefinition.getType().getId();
131                    if (configParamTypeId.equals(type.getId()))
132                    {
133                        return configParamName;
134                    }
135                    else
136                    {
137                        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);
138                    }
139                }
140                else
141                {
142                    throw new ConfigurationException("The configuration parameter '" + configParamName + "' does not exist, it cannot be used as default value for item '" + definition.getPath() + " (" + type.getId() + ")'.", defaultValueConfig);
143                }
144            }
145            else
146            {
147                throw new ConfigurationException("The type '" + defaultValueType + "' is not available for the default value of item '" + definition.getPath() + " (" + type.getId() + ")'.", defaultValueConfig);
148            }
149        }
150        else
151        {
152            return type.parseConfiguration(defaultValueConfig);
153        }
154    }
155    
156    /**
157     * Parses the multiple attribute.
158     * @param definitionConfig the element definition configuration to use.
159     * @return the true if the element is multiple, false otherwise.
160     * @throws ConfigurationException if the configuration is not valid.
161     */
162    protected Boolean _parseMultiple(Configuration definitionConfig) throws ConfigurationException
163    {
164        return definitionConfig.getAttributeAsBoolean("multiple", false);
165    }
166    
167    /**
168     * Parses the enumerator.
169     * @param pluginName the plugin name.
170     * @param catalog the catalog
171     * @param definition the element definition.
172     * @param definitionConfig the element definition configuration.
173     * @throws ConfigurationException if the configuration is not valid.
174     */
175    @SuppressWarnings("unchecked")
176    protected void _parseAndSetEnumerator(String pluginName, String catalog, ElementDefinition definition, Configuration definitionConfig) throws ConfigurationException
177    {
178        Configuration enumeratorConfig = definitionConfig.getChild("enumeration", false);
179        
180        if (enumeratorConfig != null)
181        {
182            Configuration customEnumerator = enumeratorConfig.getChild("custom-enumerator", false);
183            
184            if (customEnumerator != null)
185            {
186                final String enumeratorClassName = customEnumerator.getAttribute("class");
187                final String enumeratorRole = definition.getPath();
188                
189                try
190                {
191                    Class enumeratorClass = Class.forName(enumeratorClassName);
192                    _enumeratorManager.addComponent(pluginName, null, enumeratorRole, enumeratorClass, definitionConfig);
193                }
194                catch (Exception e)
195                {
196                    throw new ConfigurationException("Unable to instantiate enumerator for class: " + enumeratorClassName, e);
197                }
198
199                // This enumerator will be affected later when enumeratorManager
200                // will be initialized in lookupComponents() call
201                _enumeratorsToLookup.put(definition, enumeratorRole);
202                    
203                // Add the custom enumerator information to the element definition
204                definition.setCustomEnumerator(enumeratorClassName);
205                definition.setEnumeratorConfiguration(customEnumerator);
206            }
207            else
208            {
209                StaticEnumerator staticEnumerator = new StaticEnumerator();
210                
211                for (Configuration entryConfig : enumeratorConfig.getChildren("entry"))
212                {
213                    Configuration valueConfiguration = entryConfig.getChild("value");
214                    Object value = definition.getType().parseConfiguration(valueConfiguration);
215                    I18nizableText label = null;
216                    
217                    if (entryConfig.getChild("label", false) != null)
218                    {
219                        label = _parseI18nizableText(entryConfig, catalog, "label");
220                    }
221                    
222                    staticEnumerator.add(label, value);
223                }
224                
225                definition.setEnumerator(staticEnumerator);
226            }
227        }
228    }
229
230    /**
231     * Parses the validator.
232     * @param pluginName the plugin name.
233     * @param definition the element definition.
234     * @param definitionConfig the element definition configuration.
235     * @throws ConfigurationException if the configuration is not valid.
236     */
237    @SuppressWarnings("unchecked")
238    protected void _parseAndSetValidator(String pluginName, ElementDefinition definition, Configuration definitionConfig) throws ConfigurationException
239    {
240        Configuration validatorConfig = definitionConfig.getChild("validation", false);
241        
242        if (validatorConfig != null)
243        {
244            Configuration customValidator = validatorConfig.getChild("custom-validator", false);
245            String validatorClassName;
246
247            if (customValidator != null)
248            {
249                validatorClassName = customValidator.getAttribute("class");
250                
251                // Add the custom validator information to the element definition
252                definition.setCustomValidator(validatorClassName);
253                definition.setValidatorConfiguration(customValidator);
254            }
255            else
256            {
257                validatorClassName = DefaultValidator.class.getName();
258            }
259            
260            final String validatorRole = definition.getPath();
261            
262            try
263            {
264                Class validatorClass = Class.forName(validatorClassName);
265                _validatorManager.addComponent(pluginName, null, validatorRole, validatorClass, definitionConfig);
266            }
267            catch (Exception e)
268            {
269                throw new ConfigurationException("Unable to instantiate validator for class: " + validatorClassName, e);
270            }
271
272            // Will be affected later when validatorManager will be initialized
273            // in lookupComponents() call
274            _validatorsToLookup.put(definition, validatorRole);
275        }
276    }
277    
278    /**
279     * Retrieves local validators and enumerators components and set them into
280     * previous parsed element definition.
281     * @throws Exception if an error occurs.
282     */
283    @SuppressWarnings("unchecked")
284    public void lookupComponents() throws Exception
285    {
286        _validatorManager.initialize();
287        _enumeratorManager.initialize();
288        
289        for (Map.Entry<ElementDefinition, String> entry : _validatorsToLookup.entrySet())
290        {
291            ElementDefinition definition = entry.getKey();
292            String validatorRole = entry.getValue();
293            
294            try
295            {
296                definition.setValidator(_validatorManager.lookup(validatorRole));
297            }
298            catch (ComponentException e)
299            {
300                throw new Exception("Unable to lookup validator role: '" + validatorRole + "' for parameter: " + definition, e);
301            }
302        }
303        
304        for (Map.Entry<ElementDefinition, String> entry : _enumeratorsToLookup.entrySet())
305        {
306            ElementDefinition definition = entry.getKey();
307            String enumeratorRole = entry.getValue();
308            
309            try
310            {
311                definition.setEnumerator(_enumeratorManager.lookup(enumeratorRole));
312            }
313            catch (ComponentException e)
314            {
315                throw new Exception("Unable to lookup enumerator role: '" + enumeratorRole + "' for parameter: " + definition, e);
316            }
317        }
318    }
319}