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.List;
020import java.util.Map;
021import java.util.UUID;
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.Pair;
028
029import org.ametys.runtime.i18n.I18nizableText;
030import org.ametys.runtime.model.disableconditions.DisableConditions;
031import org.ametys.runtime.model.type.ModelItemTypeExtensionPoint;
032import org.ametys.runtime.parameter.DefaultValidator;
033import org.ametys.runtime.parameter.Validator;
034import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
035
036/**
037 * {@link ElementDefinition} parser from an XML configuration.
038 */
039public abstract class AbstractElementDefinitionParser extends AbstractModelItemParser
040{
041    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
042    private ThreadSafeComponentManager<Validator> _validatorManager;
043    private final Map<String, ElementDefinition> _validatorsToLookup = new HashMap<>();
044    private final Map<String, ElementDefinition> _enumeratorsToLookup = new HashMap<>();
045    
046    /**
047     * Creates an element definition parser.
048     * @param modelItemTypeExtensionPoint the extension point to use to get available element types
049     * @param disableConditionsManager the disable conditions component manager
050     * @param enumeratorManager the enumerator component manager.
051     * @param validatorManager the validator component manager.
052     */
053    public AbstractElementDefinitionParser(ModelItemTypeExtensionPoint modelItemTypeExtensionPoint, ThreadSafeComponentManager<DisableConditions> disableConditionsManager, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
054    {
055        super(modelItemTypeExtensionPoint, disableConditionsManager);
056        _enumeratorManager = enumeratorManager;
057        _validatorManager = validatorManager;
058    }
059    
060    @Override
061    @SuppressWarnings("unchecked")
062    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration definitionConfig, Model model, ModelItemGroup parent) throws ConfigurationException
063    {
064        ElementDefinition definition = (ElementDefinition) super.parse(serviceManager, pluginName, catalog, definitionConfig, model, parent);
065        
066        definition.setParsedDefaultValues(_parseDefaultValues(definitionConfig, definition));
067        definition.setMultiple(ItemParserHelper.parseMultiple(definitionConfig));
068        
069        _parseAndSetEnumerator(pluginName, catalog, definition, definitionConfig);
070        _parseAndSetValidator(pluginName, definition, definitionConfig);
071        
072        return (T) definition;
073    }
074    
075    @Override
076    protected ElementDefinition _createModelItem(Configuration definitionConfig) throws ConfigurationException
077    {
078        return new DefaultElementDefinition();
079    }
080
081    /**
082     * Parses the default values.
083     * @param definitionConfig the element definition configuration.
084     * @param definition the element definition.
085     * @return the default values and their types or <code>null</code> if none default value is defined.
086     * @throws ConfigurationException if the configuration is not valid.
087     */
088    protected List<Pair<String, Object>> _parseDefaultValues(Configuration definitionConfig, ElementDefinition definition) throws ConfigurationException
089    {
090        return ItemParserHelper.parseDefaultValues(definitionConfig, definition, ItemParserHelper::parseDefaultValue);
091    }
092    
093    /**
094     * Parses the enumerator.
095     * @param pluginName the plugin name.
096     * @param catalog the catalog
097     * @param definition the element definition.
098     * @param definitionConfig the element definition configuration.
099     * @throws ConfigurationException if the configuration is not valid.
100     */
101    @SuppressWarnings("unchecked")
102    protected void _parseAndSetEnumerator(String pluginName, String catalog, ElementDefinition definition, Configuration definitionConfig) throws ConfigurationException
103    {
104        Configuration enumeratorConfig = definitionConfig.getChild("enumeration", false);
105        
106        if (enumeratorConfig != null)
107        {
108            Configuration customEnumerator = enumeratorConfig.getChild("custom-enumerator", false);
109            
110            if (customEnumerator != null)
111            {
112                final String enumeratorClassName = customEnumerator.getAttribute("class");
113                final String enumeratorRole = definition.getPath() + "$" + UUID.randomUUID().toString();
114                
115                try
116                {
117                    Class enumeratorClass = Class.forName(enumeratorClassName);
118                    _enumeratorManager.addComponent(pluginName, null, enumeratorRole, enumeratorClass, definitionConfig);
119                }
120                catch (Exception e)
121                {
122                    throw new ConfigurationException("Unable to instantiate enumerator for class: " + enumeratorClassName, e);
123                }
124
125                // This enumerator will be affected later when enumeratorManager
126                // will be initialized in lookupComponents() call
127                _enumeratorsToLookup.put(enumeratorRole, definition);
128                    
129                // Add the custom enumerator information to the element definition
130                definition.setCustomEnumerator(enumeratorClassName);
131                definition.setEnumeratorConfiguration(customEnumerator);
132            }
133            else
134            {
135                StaticEnumerator staticEnumerator = new StaticEnumerator();
136                
137                for (Configuration entryConfig : enumeratorConfig.getChildren("entry"))
138                {
139                    Configuration valueConfiguration = entryConfig.getChild("value");
140                    Object value = definition.getType().parseConfiguration(valueConfiguration);
141                    I18nizableText label = null;
142                    
143                    if (entryConfig.getChild("label", false) != null)
144                    {
145                        label = _parseI18nizableText(entryConfig, catalog, "label");
146                    }
147                    
148                    staticEnumerator.add(label, value);
149                }
150                
151                definition.setEnumerator(staticEnumerator);
152            }
153        }
154    }
155
156    /**
157     * Parses the validator.
158     * @param pluginName the plugin name.
159     * @param definition the element definition.
160     * @param definitionConfig the element definition configuration.
161     * @throws ConfigurationException if the configuration is not valid.
162     */
163    @SuppressWarnings("unchecked")
164    protected void _parseAndSetValidator(String pluginName, ElementDefinition definition, Configuration definitionConfig) throws ConfigurationException
165    {
166        Configuration validatorConfig = definitionConfig.getChild("validation", false);
167        
168        if (validatorConfig != null)
169        {
170            Configuration customValidator = validatorConfig.getChild("custom-validator", false);
171            String validatorClassName;
172
173            if (customValidator != null)
174            {
175                validatorClassName = customValidator.getAttribute("class");
176                
177                // Add the custom validator information to the element definition
178                definition.setCustomValidator(validatorClassName);
179                definition.setValidatorConfiguration(customValidator);
180            }
181            else
182            {
183                validatorClassName = DefaultValidator.class.getName();
184            }
185            
186            final String validatorRole = definition.getPath() + "$" + UUID.randomUUID().toString();
187            
188            try
189            {
190                Class validatorClass = Class.forName(validatorClassName);
191                _validatorManager.addComponent(pluginName, null, validatorRole, validatorClass, definitionConfig);
192            }
193            catch (Exception e)
194            {
195                throw new ConfigurationException("Unable to instantiate validator for class: " + validatorClassName, e);
196            }
197
198            // Will be affected later when validatorManager will be initialized
199            // in lookupComponents() call
200            _validatorsToLookup.put(validatorRole, definition);
201        }
202    }
203    
204    @Override
205    @SuppressWarnings("unchecked")
206    public void lookupComponents() throws Exception
207    {
208        super.lookupComponents();
209        
210        _validatorManager.initialize();
211        _enumeratorManager.initialize();
212        
213        for (Map.Entry<String, ElementDefinition> entry : _validatorsToLookup.entrySet())
214        {
215            String validatorRole = entry.getKey();
216            ElementDefinition definition = entry.getValue();
217            
218            try
219            {
220                definition.setValidator(_validatorManager.lookup(validatorRole));
221            }
222            catch (ComponentException e)
223            {
224                throw new Exception("Unable to lookup validator role: '" + validatorRole + "' for parameter: " + definition, e);
225            }
226        }
227        
228        for (Map.Entry<String, ElementDefinition> entry : _enumeratorsToLookup.entrySet())
229        {
230            String enumeratorRole = entry.getKey();
231            ElementDefinition definition = entry.getValue();
232            
233            try
234            {
235                definition.setEnumerator(_enumeratorManager.lookup(enumeratorRole));
236            }
237            catch (ComponentException e)
238            {
239                throw new Exception("Unable to lookup enumerator role: '" + enumeratorRole + "' for parameter: " + definition, e);
240            }
241        }
242    }
243}