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.regex.Pattern;
021
022import org.apache.avalon.framework.configuration.Configurable;
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.configuration.ConfigurationException;
025import org.apache.avalon.framework.logger.AbstractLogEnabled;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
030import org.apache.cocoon.xml.XMLUtils;
031import org.slf4j.LoggerFactory;
032import org.xml.sax.ContentHandler;
033import org.xml.sax.SAXException;
034
035import org.ametys.core.util.JSONUtils;
036import org.ametys.runtime.i18n.I18nizableText;
037import org.ametys.runtime.plugin.component.PluginAware;
038
039
040/**
041 * This default implementation validates the following configurable stuff:
042 * <ul>
043 *  <li>mandatory: check the parameter is set</li>
044 *  <li>regexp: check the string parameter matches a regexp</li>
045 * </ul> 
046 */
047public class DefaultValidator extends AbstractLogEnabled implements Validator, Configurable, PluginAware, Serviceable
048{
049    /** Is the value mandatory ? */
050    protected boolean _isMandatory;
051    /** Does the value need to match a regexp */
052    protected Pattern _regexp;
053    /** The error text to display if regexp fails */
054    protected I18nizableText _invalidText;
055    /** The plugin name */
056    protected String _pluginName;
057    
058    /** The service manager */
059    protected ServiceManager _smanager;
060    
061    private JSONUtils _jsonUtils;
062    
063    /**
064     * Default constructor for avalon
065     */
066    public DefaultValidator()
067    {
068        // empty
069    }
070    
071    /**
072     * Manual constructor
073     * @param regexp The regexp to check or null
074     * @param mandatory Is the value mandatory 
075     */
076    public DefaultValidator(String regexp, boolean mandatory)
077    {
078        _isMandatory = mandatory;
079        if (regexp != null)
080        {
081            _regexp = Pattern.compile(regexp);
082        }
083        enableLogging(new SLF4JLoggerAdapter(LoggerFactory.getLogger(this.getClass())));
084    }
085    
086    /**
087     * Manual constructor
088     * @param regexp The regexp to check or null
089     * @param invalidText The error text to display
090     * @param mandatory Is the value mandatory 
091     */
092    public DefaultValidator(String regexp, I18nizableText invalidText, boolean mandatory)
093    {
094        _isMandatory = mandatory;
095        if (regexp != null)
096        {
097            _regexp = Pattern.compile(regexp);
098        }
099        _invalidText = invalidText;
100        
101        enableLogging(new SLF4JLoggerAdapter(LoggerFactory.getLogger(this.getClass())));
102    }
103    
104    @Override
105    public void service(ServiceManager smanager) throws ServiceException
106    {
107        _smanager = smanager;
108    }
109    
110    @Override
111    public void setPluginInfo(String pluginName, String featureName, String id)
112    {
113        _pluginName = pluginName;
114    }
115    
116    @Override
117    public void configure(Configuration configuration) throws ConfigurationException
118    {
119        Configuration validatorConfig = configuration.getChild("validation");
120        
121        _isMandatory = validatorConfig.getChild("mandatory", false) != null;
122
123        String regexp = validatorConfig.getChild("regexp").getValue(null);
124        if (regexp != null)
125        {
126            _regexp = Pattern.compile(regexp);
127        }
128        
129        Configuration textConfig = validatorConfig.getChild("invalidText", false);
130        if (textConfig != null)
131        {
132            _invalidText = I18nizableText.parseI18nizableText(textConfig, "plugin." + _pluginName);
133        }
134    }
135    
136    @Override
137    public Map<String, Object> toJson()
138    {
139        Map<String, Object> jsonObject = new HashMap<>();
140        
141        jsonObject.put("mandatory", _isMandatory);
142        
143        if (_regexp != null)
144        {
145            jsonObject.put("regexp", _regexp.toString());
146        }
147        
148        if (_invalidText != null)
149        {
150            jsonObject.put("invalidText", _invalidText);
151        }
152        return jsonObject;
153    }
154    
155    /**
156     * SAX the JSON configuration of this validator 
157     * @param handler The content handlet to sax into
158     * @throws SAXException if an error occurred
159     */
160    protected void saxJsonConfig (ContentHandler handler) throws SAXException
161    {
162        if (_jsonUtils == null)
163        {
164            try
165            {
166                // lazy lookup the json utils
167                _jsonUtils = (JSONUtils) _smanager.lookup(JSONUtils.ROLE);
168                XMLUtils.createElement(handler, "config", _jsonUtils.convertObjectToJson(toJson()));
169            }
170            catch (ServiceException e)
171            {
172                // FIXME RUNTIME-2013 Validators and enumerators can not use others components such as JSONUtils
173                getLogger().warn("Unable to retrieve the component JSONUtils", e);
174            }
175        }
176    }
177    
178    @Override
179    public void saxConfiguration(ContentHandler handler) throws SAXException
180    {
181        XMLUtils.createElement(handler, "mandatory", Boolean.toString(_isMandatory));
182        
183        if (_regexp != null)
184        {
185            XMLUtils.createElement(handler, "regexp", _regexp.toString());
186        }
187        
188        if (_invalidText != null)
189        {
190            _invalidText.toSAX(handler, "invalidText");
191        }
192        
193        // FIXME RUNTIME-2015 The configuration of a validator as JSON object should be pass to the field configuration
194        // but FIXME RUNTIME-2013 Validators and enumerators can not use others components such as JSONUtils
195        // saxJsonConfig(handler);
196    }
197    
198    @Override
199    public Map<String, Object> getConfiguration()
200    {
201        Map<String, Object> configuration = new HashMap<>();
202        
203        configuration.put("mandatory", Boolean.valueOf(_isMandatory));
204        
205        if (_regexp != null)
206        {
207            configuration.put("regexp", _regexp);
208        }
209    
210        if (_invalidText != null)
211        {
212            configuration.put("invalidText", _invalidText);
213        }
214        
215        return configuration;
216    }
217    
218    @Override
219    public void validate(Object value, Errors errors)
220    {
221        boolean isArray = value != null && value.getClass().isArray();
222        if (isArray)
223        {
224            validateArrayValues((Object[]) value, errors); 
225        }
226        else
227        {
228            validateSingleValue (value, errors);
229        }
230    }
231    
232    /**
233     * Validates a single value.
234     * @param value the value to validate (can be <code>null</code>).
235     * @param errors the structure to populate if the validation failed.
236     */
237    protected void validateSingleValue (Object value, Errors errors)
238    {
239        if (_isMandatory && (value == null || value.toString().length() == 0))
240        {
241            if (getLogger().isDebugEnabled())
242            {
243                getLogger().debug("The validator refused a missing or empty value for a mandatory parameter");
244            }
245            
246            errors.addError(new I18nizableText("plugin.core-ui", "PLUGINS_CORE_UI_DEFAULT_VALIDATOR_MANDATORY"));
247        }
248        
249        if (_regexp != null && value != null && value.toString().length() != 0 && !_regexp.matcher(value.toString()).matches())
250        {
251            if (getLogger().isDebugEnabled())
252            {
253                getLogger().debug("The validator refused a value for a parameter that should respect a regexep");
254            }
255
256            errors.addError(new I18nizableText("plugin.core-ui", "PLUGINS_CORE_UI_DEFAULT_VALIDATOR_PATTERN_FAILED"));
257        }
258    }
259    
260    /**
261     * Validates a array of values.
262     * @param values the values to validate
263     * @param errors the structure to populate if the validation failed.
264     */
265    protected void validateArrayValues (Object[] values, Errors errors)
266    {
267        if (_isMandatory && (values == null || values.length == 0))
268        {
269            if (getLogger().isDebugEnabled())
270            {
271                getLogger().debug("The validator refused a missing or empty value for a mandatory parameter");
272            }
273            
274            errors.addError(new I18nizableText("kernel", "PLUGINS_CORE_UI_DEFAULT_VALIDATOR_MANDATORY"));
275        }
276        
277        if (_regexp != null && values != null && !_matchRegexp(values))
278        {
279            if (getLogger().isDebugEnabled())
280            {
281                getLogger().debug("The validator refused a value for a parameter that should respect a regexep");
282            }
283
284            errors.addError(new I18nizableText("kernel", "PLUGINS_CORE_UI_DEFAULT_VALIDATOR_PATTERN_FAILED"));
285        }
286    }
287    
288    private boolean _matchRegexp (Object[] values)
289    {
290        for (Object value : values)
291        {
292            if (!_regexp.matcher(value.toString()).matches())
293            {
294                return false;
295            }
296        }
297        return true;
298    }
299}