001/*
002 *  Copyright 2012 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.parameter;
017
018import java.util.HashMap;
019import java.util.Map;
020
021import org.apache.avalon.framework.component.ComponentException;
022import org.apache.avalon.framework.configuration.Configuration;
023import org.apache.avalon.framework.configuration.ConfigurationException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.commons.lang3.StringUtils;
026
027import org.ametys.runtime.i18n.I18nizableText;
028import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
029
030/**
031 * {@link Parameter} parser from an XML configuration.
032 * @param <P> the actual type of parameter.
033 * @param <T> the actual type of parameter type.
034 */
035public abstract class AbstractParameterParser<P extends Parameter<T>, T>
036{
037    /** The enumerators component manager. */
038    protected ThreadSafeComponentManager<Enumerator> _enumeratorManager;
039    /** The validators component manager. */
040    protected ThreadSafeComponentManager<Validator> _validatorManager;
041    private final Map<P, String> _validatorsToLookup = new HashMap<>();
042    private final Map<P, String> _enumeratorsToLookup = new HashMap<>();
043
044    /**
045     * Creates an AbstractParameterParser.
046     * @param enumeratorManager the enumerator component manager.
047     * @param validatorManager the validator component manager.
048     */
049    public AbstractParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
050    {
051        _enumeratorManager = enumeratorManager;
052        _validatorManager = validatorManager;
053    }
054
055    /**
056     * Parses a parameter from a XML configuration.
057     * @param manager the service manager.
058     * @param pluginName the plugin name declaring this parameter.
059     * @param parameterConfig the XML configuration.
060     * @return the parsed parameter.
061     * @throws ConfigurationException if the configuration is not valid.
062     */
063    public P parseParameter(ServiceManager manager, String pluginName, Configuration parameterConfig) throws ConfigurationException
064    {
065        P parameter = _createParameter(parameterConfig);
066        String parameterId = _parseId(parameterConfig);
067        
068        parameter.setId(parameterId);
069        parameter.setPluginName(pluginName);
070        parameter.setLabel(_parseI18nizableText(parameterConfig, pluginName, "label"));
071        parameter.setDescription(_parseI18nizableText(parameterConfig, pluginName, "description"));
072        parameter.setType(_parseType(parameterConfig));
073        parameter.setWidget(_parseWidget(parameterConfig));
074        parameter.setWidgetParameters(_parseWidgetParameters(parameterConfig, pluginName));
075        _parseAndSetEnumerator(pluginName, parameter, parameterId, parameterConfig);
076        _parseAndSetValidator(pluginName, parameter, parameterId, parameterConfig);
077        parameter.setDefaultValue(_parseDefaultValue(parameterConfig, parameter));
078        
079        _additionalParsing(manager, pluginName, parameterConfig, parameterId, parameter);
080        
081        return parameter;
082    }
083
084    /**
085     * Retrieves local validators and enumerators components and set them into
086     * previous parameter parsed.
087     * @throws Exception if an error occurs.
088     */
089    public void lookupComponents() throws Exception
090    {
091        _validatorManager.initialize();
092        _enumeratorManager.initialize();
093        
094        for (Map.Entry<P, String> entry : _validatorsToLookup.entrySet())
095        {
096            P parameter = entry.getKey();
097            String validatorRole = entry.getValue();
098            
099            try
100            {
101                parameter.setValidator(_validatorManager.lookup(validatorRole));
102            }
103            catch (ComponentException e)
104            {
105                throw new Exception("Unable to lookup validator role: '" + validatorRole + "' for parameter: " + parameter, e);
106            }
107        }
108        
109        for (Map.Entry<P, String> entry : _enumeratorsToLookup.entrySet())
110        {
111            P parameter = entry.getKey();
112            String enumeratorRole = entry.getValue();
113            
114            try
115            {
116                parameter.setEnumerator(_enumeratorManager.lookup(enumeratorRole));
117            }
118            catch (ComponentException e)
119            {
120                throw new Exception("Unable to lookup enumerator role: '" + enumeratorRole + "' for parameter: " + parameter, e);
121            }
122        }
123    }
124
125    /**
126     * Create the parameter to populate it.
127     * @param parameterConfig the parameter configuration to use.
128     * @return the parameter instantiated.
129     * @throws ConfigurationException if the configuration is not valid.
130     */
131    protected abstract P _createParameter(Configuration parameterConfig) throws ConfigurationException;
132    
133    /**
134     * Parses the id.
135     * @param parameterConfig the parameter configuration to use.
136     * @return the id.
137     * @throws ConfigurationException if the configuration is not valid.
138     */
139    protected abstract String _parseId(Configuration parameterConfig) throws ConfigurationException;
140
141    /**
142     * Parses an i18n text.
143     * @param config the configuration to use.
144     * @param pluginName the current plugin name.
145     * @param name the child name.
146     * @return the i18n text.
147     * @throws ConfigurationException if the configuration is not valid.
148     */
149    protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException
150    {
151        return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + pluginName);
152    }
153
154    /**
155     * Parses the type.
156     * @param parameterConfig the parameter configuration to use.
157     * @return the type.
158     * @throws ConfigurationException if the configuration is not valid.
159     */
160    protected abstract T _parseType(Configuration parameterConfig) throws ConfigurationException;
161
162    /**
163     * Parses the widget.
164     * @param parameterConfig the parameter configuration to use.
165     * @return the widget or <code>null</code> if none defined.
166     * @throws ConfigurationException if the configuration is not valid.
167     */
168    protected String _parseWidget(Configuration parameterConfig) throws ConfigurationException
169    {
170        return parameterConfig.getChild("widget").getValue(null);
171    }
172    
173    /**
174     * Parses the widget's parameters
175     * @param parameterConfig the parameter configuration to use.
176     * @param pluginName the current plugin name.
177     * @return the widget's parameters in a Map
178     * @throws ConfigurationException if the configuration is not valid.
179     */
180    protected Map<String, I18nizableText> _parseWidgetParameters(Configuration parameterConfig, String pluginName) throws ConfigurationException
181    {
182        Map<String, I18nizableText> widgetParams = new HashMap<>();
183        
184        Configuration widgetParamsConfig = parameterConfig.getChild("widget-params", false);
185        if (widgetParamsConfig != null)
186        {
187            Configuration[] params = widgetParamsConfig.getChildren("param");
188            for (Configuration paramConfig : params)
189            {
190                boolean i18nSupported = paramConfig.getAttributeAsBoolean("i18n", false);
191                if (i18nSupported)
192                {
193                    String catalogue = paramConfig.getAttribute("catalogue", null);
194                    
195                    if (catalogue == null)
196                    {
197                        catalogue = "plugin." + pluginName;
198                    }
199                    
200                    widgetParams.put(paramConfig.getAttribute("name"), new I18nizableText(catalogue, paramConfig.getValue()));
201                }
202                else
203                {
204                    widgetParams.put(paramConfig.getAttribute("name"), new I18nizableText(paramConfig.getValue("")));
205                }
206            }
207        }
208        
209        return widgetParams;
210    }
211
212    /**
213     * Parses the enumerator.
214     * @param pluginName the plugin name.
215     * @param parameter the parameter.
216     * @param parameterId the parameter id.
217     * @param parameterConfig the parameter configuration.
218     * @throws ConfigurationException if the configuration is not valid.
219     */
220    @SuppressWarnings("unchecked")
221    protected void _parseAndSetEnumerator(String pluginName, P parameter, String parameterId, Configuration parameterConfig) throws ConfigurationException
222    {
223        Configuration enumeratorConfig = parameterConfig.getChild("enumeration", false);
224        
225        if (enumeratorConfig != null)
226        {
227            Configuration customEnumerator = enumeratorConfig.getChild("custom-enumerator", false);
228            
229            if (customEnumerator != null)
230            {
231                String enumeratorClassName = customEnumerator.getAttribute("class");
232                
233                try
234                {
235                    Class enumeratorClass = Class.forName(enumeratorClassName);
236                    _enumeratorManager.addComponent(pluginName, null, parameterId, enumeratorClass, parameterConfig);
237                }
238                catch (Exception e)
239                {
240                    throw new ConfigurationException("Unable to instantiate enumerator for class: " + enumeratorClassName, e);
241                }
242
243                // This enumerator will be affected later when validatorManager
244                // will be initialized in lookupComponents() call
245                _enumeratorsToLookup.put(parameter, parameterId);
246                
247            }
248            else
249            {
250                StaticEnumerator staticEnumerator = new StaticEnumerator();
251                
252                for (Configuration entryConfig : enumeratorConfig.getChildren("entry"))
253                {
254                    String value = entryConfig.getChild("value").getValue("");
255                    I18nizableText label = null;
256                    
257                    if (entryConfig.getChild("label", false) != null)
258                    {
259                        label = _parseI18nizableText(entryConfig, pluginName, "label");
260                    }
261                    
262                    staticEnumerator.add(label, value);
263                }
264                
265                parameter.setEnumerator(staticEnumerator);
266            }
267        }
268    }
269
270    /**
271     * Parses the validator.
272     * @param pluginName the plugin name.
273     * @param parameter the parameter.
274     * @param parameterId the parameter id.
275     * @param parameterConfig the parameter configuration.
276     * @throws ConfigurationException if the configuration is not valid.
277     */
278    @SuppressWarnings("unchecked")
279    protected void _parseAndSetValidator(String pluginName, P parameter, String parameterId, Configuration parameterConfig) throws ConfigurationException
280    {
281        Configuration validatorConfig = parameterConfig.getChild("validation", false);
282        
283        if (validatorConfig != null)
284        {
285            String validatorClassName = StringUtils.defaultIfBlank(validatorConfig.getChild("custom-validator").getAttribute("class", ""), DefaultValidator.class.getName());
286            
287            try
288            {
289                Class validatorClass = Class.forName(validatorClassName);
290                _validatorManager.addComponent(pluginName, null, parameterId, validatorClass, parameterConfig);
291            }
292            catch (Exception e)
293            {
294                throw new ConfigurationException("Unable to instantiate validator for class: " + validatorClassName, e);
295            }
296
297            // Will be affected later when validatorManager will be initialized
298            // in lookupComponents() call
299            _validatorsToLookup.put(parameter, parameterId);
300        }
301    }
302
303    /**
304     * Parses the default value.
305     * @param parameterConfig the parameter configuration.
306     * @param parameter the parameter.
307     * @return the default value or <code>null</code> if none defined.
308     * @throws ConfigurationException if the configuration is not valid.
309     */
310    protected abstract Object _parseDefaultValue(Configuration parameterConfig, P parameter) throws ConfigurationException;
311
312    /**
313     * Called for additional parsing.<br>
314     * Default implementation does nothing.
315     * @param manager the sservice manager.
316     * @param pluginName the plugin name.
317     * @param parameterConfig the parameter configuration.
318     * @param parameterId the parameter id.
319     * @param parameter the parameter to populate.
320     * @throws ConfigurationException if the configuration is not valid.
321     */
322    protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration parameterConfig, String parameterId, P parameter) throws ConfigurationException
323    {
324        // Nothing to do
325    }
326}