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;
022import java.util.UUID;
023
024import org.apache.avalon.framework.component.ComponentException;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang3.tuple.ImmutablePair;
029import org.apache.commons.lang3.tuple.Pair;
030
031import org.ametys.runtime.config.ConfigManager;
032import org.ametys.runtime.i18n.I18nizableText;
033import org.ametys.runtime.model.type.ElementType;
034import org.ametys.runtime.model.type.ModelItemTypeExtensionPoint;
035import org.ametys.runtime.parameter.DefaultValidator;
036import org.ametys.runtime.parameter.Validator;
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 ThreadSafeComponentManager<Enumerator> _enumeratorManager;
045    private ThreadSafeComponentManager<Validator> _validatorManager;
046    private final Map<String, ElementDefinition> _validatorsToLookup = new HashMap<>();
047    private final Map<String, ElementDefinition> _enumeratorsToLookup = new HashMap<>();
048    
049    /**
050     * Creates an element definition parser.
051     * @param modelItemTypeExtensionPoint the extension point to use to get available element types
052     * @param enumeratorManager the enumerator component manager.
053     * @param validatorManager the validator component manager.
054     */
055    public ElementDefinitionParser(ModelItemTypeExtensionPoint modelItemTypeExtensionPoint, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
056    {
057        super(modelItemTypeExtensionPoint);
058        _enumeratorManager = enumeratorManager;
059        _validatorManager = validatorManager;
060    }
061    
062    @Override
063    @SuppressWarnings("unchecked")
064    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration definitionConfig, Model model, ModelItemGroup parent) throws ConfigurationException
065    {
066        ElementDefinition definition = (ElementDefinition) super.parse(serviceManager, pluginName, catalog, definitionConfig, model, parent);
067        
068        definition.setParsedDefaultValues(_parseDefaultValues(definitionConfig, definition));
069        definition.setMultiple(_parseMultiple(definitionConfig));
070        
071        _parseAndSetEnumerator(pluginName, catalog, definition, definitionConfig);
072        _parseAndSetValidator(pluginName, definition, definitionConfig);
073        
074        return (T) definition;
075    }
076    
077    @Override
078    protected ElementDefinition _createModelItem(Configuration definitionConfig) throws ConfigurationException
079    {
080        return new DefaultElementDefinition();
081    }
082
083    /**
084     * Parses the default values.
085     * @param definitionConfig the element definition configuration.
086     * @param definition the element definition.
087     * @return the default values and their types or <code>null</code> if none default value is defined.
088     * @throws ConfigurationException if the configuration is not valid.
089     */
090    protected List<Pair<String, Object>> _parseDefaultValues(Configuration definitionConfig, ElementDefinition definition) throws ConfigurationException
091    {
092        Configuration[] defaultValueConfigs = definitionConfig.getChildren("default-value");
093        if (defaultValueConfigs.length > 0)
094        {
095            List<Pair<String, Object>> defaultValues = new ArrayList<>();
096            
097            for (Configuration defaultValueConfig : defaultValueConfigs)
098            {
099                String defaultValueType = defaultValueConfig.getAttribute("type", null);
100                Object defaultValue = _parseDefaultValue(defaultValueConfig, definition, defaultValueType);
101                defaultValues.add(new ImmutablePair<>(defaultValueType, defaultValue));
102            }
103            
104            return defaultValues;
105        }
106        else
107        {
108            return null;
109        }
110    }
111    
112    /**
113     * Parses the default value.
114     * @param defaultValueConfig the default value configuration.
115     * @param definition the element definition.
116     * @param defaultValueType the type of the default value
117     * @return the default value or <code>null</code> if none default value is defined.
118     * @throws ConfigurationException if the configuration is not valid.
119     */
120    protected Object _parseDefaultValue(Configuration defaultValueConfig, ElementDefinition definition, String defaultValueType) throws ConfigurationException
121    {
122        ElementType type = definition.getType();
123        if (defaultValueType != null)
124        {
125            if (ElementDefinition.CONFIG_DEFAULT_VALUE_TYPE.equals(defaultValueType))
126            {
127                String configParamName = defaultValueConfig.getValue();
128                if (ConfigManager.getInstance().hasModelItem(configParamName))
129                {
130                    ModelItem configParamDefinition = ConfigManager.getInstance().getModelItem(configParamName);
131                    String configParamTypeId = configParamDefinition.getType().getId();
132                    if (configParamTypeId.equals(type.getId()))
133                    {
134                        return configParamName;
135                    }
136                    else
137                    {
138                        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);
139                    }
140                }
141                else
142                {
143                    throw new ConfigurationException("The configuration parameter '" + configParamName + "' does not exist, it cannot be used as default value for item '" + definition.getPath() + " (" + type.getId() + ")'.", defaultValueConfig);
144                }
145            }
146            else
147            {
148                throw new ConfigurationException("The type '" + defaultValueType + "' is not available for the default value of item '" + definition.getPath() + " (" + type.getId() + ")'.", defaultValueConfig);
149            }
150        }
151        else
152        {
153            return type.parseConfiguration(defaultValueConfig);
154        }
155    }
156    
157    /**
158     * Parses the multiple attribute.
159     * @param definitionConfig the element definition configuration to use.
160     * @return the true if the element is multiple, false otherwise.
161     * @throws ConfigurationException if the configuration is not valid.
162     */
163    protected Boolean _parseMultiple(Configuration definitionConfig) throws ConfigurationException
164    {
165        return definitionConfig.getAttributeAsBoolean("multiple", false);
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() + "$" + UUID.randomUUID().toString();
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(enumeratorRole, definition);
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() + "$" + UUID.randomUUID().toString();
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(validatorRole, definition);
276        }
277    }
278    
279    /**
280     * Retrieves local validators and enumerators components and set them into
281     * previous parsed element definition.
282     * @throws Exception if an error occurs.
283     */
284    @SuppressWarnings("unchecked")
285    public void lookupComponents() throws Exception
286    {
287        _validatorManager.initialize();
288        _enumeratorManager.initialize();
289        
290        for (Map.Entry<String, ElementDefinition> entry : _validatorsToLookup.entrySet())
291        {
292            String validatorRole = entry.getKey();
293            ElementDefinition definition = entry.getValue();
294            
295            try
296            {
297                definition.setValidator(_validatorManager.lookup(validatorRole));
298            }
299            catch (ComponentException e)
300            {
301                throw new Exception("Unable to lookup validator role: '" + validatorRole + "' for parameter: " + definition, e);
302            }
303        }
304        
305        for (Map.Entry<String, ElementDefinition> entry : _enumeratorsToLookup.entrySet())
306        {
307            String enumeratorRole = entry.getKey();
308            ElementDefinition definition = entry.getValue();
309            
310            try
311            {
312                definition.setEnumerator(_enumeratorManager.lookup(enumeratorRole));
313            }
314            catch (ComponentException e)
315            {
316                throw new Exception("Unable to lookup enumerator role: '" + enumeratorRole + "' for parameter: " + definition, e);
317            }
318        }
319    }
320}