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