001/*
002 *  Copyright 2014 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.io.InputStream;
019import java.time.LocalDateTime;
020import java.time.ZonedDateTime;
021import java.time.chrono.IsoChronology;
022import java.time.format.DateTimeFormatter;
023import java.time.format.ResolverStyle;
024import java.util.ArrayList;
025import java.util.Date;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.cocoon.ProcessingException;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035import org.xml.sax.ContentHandler;
036import org.xml.sax.SAXException;
037
038import org.ametys.core.util.DateUtils;
039import org.ametys.runtime.i18n.I18nizableText;
040
041
042/**
043 * This class handles all needed to use typed parameters
044 */
045public final class ParameterHelper
046{
047    /** Enumeration of supported types */
048    public static enum ParameterType
049    {
050        /** boolean values */
051        BOOLEAN,
052        /** string values */
053        STRING,
054        /** password values */
055        PASSWORD,
056        /** long values */
057        LONG,
058        /** double values */
059        DOUBLE,
060        /** date values */
061        DATE,
062        /** binary values */
063        BINARY,
064        /** datasource values */
065        DATASOURCE
066    }
067    
068    /**
069     * The ISO date-time formatter that formats or parses a date-time with an offset, such as '2011-12-03T10:15:30.000+01:00'. 
070     */
071    private static DateTimeFormatter __ISO_OFFSET_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withResolverStyle(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
072
073    // Logger for traces
074    private static Logger _logger = LoggerFactory.getLogger(ParameterHelper.class);
075
076    private ParameterHelper ()
077    {
078        // empty
079    }
080    
081    /**
082     * Get the ISO date-time formatter that formats or parses a date-time with an offset, such as '2011-12-03T10:15:30.000+01:00'. 
083     * This formatter is similar to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} but force 3-digits milliseconds.
084     * @return ISO date-time formatter
085     */
086    public static DateTimeFormatter getISODateTimeFormatter()
087    {
088        return __ISO_OFFSET_DATE_TIME;
089    }
090    
091    /**
092     * Return the readable name of a type
093     * 
094     * @param type Type to convert
095     * @return Returns the name of the type
096     * @throws IllegalArgumentException If the type is unknwon
097     */
098    public static String typeToString(ParameterType type)
099    {
100        return type.name().toLowerCase();
101    }
102    
103    /**
104     * Convert a string containing a type to its value
105     * 
106     * @param type Name of the type
107     * @return Type
108     * @throws IllegalArgumentException if the type is unknown
109     */
110    public static ParameterType stringToType(String type)
111    {
112        try
113        {
114            return ParameterType.valueOf(type.toUpperCase());
115        }
116        catch (IllegalArgumentException e)
117        {
118            throw new IllegalArgumentException("The type '" + type + "' is unknown for config parameters");
119        }
120    }
121    
122    /**
123     * Cast a untyped value (string) to an object of the type
124     * 
125     * @param value Value to cast
126     * @param type Type to cast value in
127     * @return An object of the type 'type' with value 'value', or null if type
128     *         is unknown or value cannot be cast
129     */
130    public static Object castValue(String value, ParameterType type)
131    {
132        
133        if (value == null)
134        {
135            return null;
136        }
137
138        try
139        {
140            if (type == ParameterType.BOOLEAN)
141            {
142                return new Boolean(value);
143            }
144            else if (type == ParameterType.STRING)
145            {
146                return value;
147            }
148            else if (type == ParameterType.PASSWORD)
149            {
150                return value;
151            }
152            else if (type == ParameterType.DATASOURCE)
153            {
154                return value;
155            }
156            else if (type == ParameterType.LONG)
157            {
158                return new Long(value);
159            }
160            else if (type == ParameterType.DOUBLE)
161            {
162                return new Double(value);
163            }
164            else if (type == ParameterType.DATE)
165            {
166                LocalDateTime ldt = LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME);
167                return DateUtils.asDate(ldt);
168            }
169            else if (type == ParameterType.BINARY)
170            {
171                return null;
172            }
173        }
174        catch (Exception nfe)
175        {
176            if (value.length() != 0)
177            {
178                _logger.error("Cannot cast value '" + value + "' into type '" + typeToString(type) + "'. Null object will be used.", nfe);
179            }
180            else if (_logger.isDebugEnabled())
181            {
182                _logger.debug("Failed to cast empty string to type '" + typeToString(type) + "'. Null object will be used.", nfe);
183            }
184        }
185        return null;
186    }
187    
188
189    /**
190     * Converts known types to string
191     * 
192     * @param value Typed value
193     * @return String readable by the config bean
194     * @throws IllegalArgumentException if the object is a InputStream
195     */
196    public static String valueToString(Object value)
197    {
198        if (value == null)
199        {
200            return null;
201        }
202
203        if (value instanceof Date)
204        {
205            ZonedDateTime zdt = DateUtils.asZonedDateTime((Date) value, null);           
206            return zdt.format(getISODateTimeFormatter());
207        }
208        
209        if (value instanceof InputStream)
210        {
211            throw new IllegalArgumentException("The object to convert is an input stream");
212        }
213
214        return value.toString();
215    }
216    
217    /**
218     * SAX a parameter
219     * @param handler The content handler where to SAX
220     * @param parameter The parameter to SAX
221     * @param value The parameter value. Can be null.
222     * @throws SAXException If an error occurred while SAXing
223     * @throws ProcessingException If an error occurred
224     */
225    public static void toSAXParameter (ContentHandler handler, Parameter parameter, Object value) throws SAXException, ProcessingException
226    {
227        AttributesImpl parameterAttr = new AttributesImpl();
228        parameterAttr.addAttribute("", "plugin", "plugin", "CDATA", parameter.getPluginName());
229        XMLUtils.startElement(handler, parameter.getId(), parameterAttr);
230        
231        toSAXParameterInternal(handler, parameter, value);
232        
233        XMLUtils.endElement(handler, parameter.getId());
234    }
235
236    /**
237     * SAX a parameter except the root tag
238     * @param handler The content handler where to SAX
239     * @param parameter The parameter to SAX
240     * @param value The parameter value. Can be null.
241     * @throws SAXException If an error occurred while SAXing
242     * @throws ProcessingException If an error occurred
243     */
244    public static void toSAXParameterInternal(ContentHandler handler, Parameter parameter, Object value) throws SAXException, ProcessingException
245    {
246        parameter.getLabel().toSAX(handler, "label");
247        parameter.getDescription().toSAX(handler, "description");
248        
249        XMLUtils.createElement(handler, "type", ParameterHelper.typeToString((ParameterType) parameter.getType()));
250        
251        Object defaultValue = parameter.getDefaultValue();
252        
253        if (defaultValue != null)
254        {
255            XMLUtils.createElement(handler, "default-value", ParameterHelper.valueToString(defaultValue));
256        }
257        
258        if (value != null)
259        {
260            XMLUtils.createElement(handler, "value", ParameterHelper.valueToString(value));
261        }
262        
263        if (parameter.getWidget() != null)
264        {
265            XMLUtils.createElement(handler, "widget", parameter.getWidget());
266        }
267        
268        Map<String, I18nizableText> widgetParameters = parameter.getWidgetParameters();
269        if (widgetParameters.size() > 0)
270        {
271            XMLUtils.startElement(handler, "widget-params");
272            for (String paramName : widgetParameters.keySet())
273            {
274                XMLUtils.startElement(handler, paramName);
275                widgetParameters.get(paramName).toSAX(handler);
276                XMLUtils.endElement(handler, paramName);
277            }
278            XMLUtils.endElement(handler, "widget-params");
279        }
280        
281        Enumerator enumerator = parameter.getEnumerator();
282        if (enumerator != null)
283        {
284            toSAXEnumerator(handler, enumerator);
285        }
286        
287        Validator validator = parameter.getValidator();
288        toSAXValidator(handler, validator);
289    }
290    
291    /**
292     * SAX parameter enumerator
293     * @param handler The content handler where to SAX
294     * @param enumerator The enumerator to SAX
295     * @throws SAXException If an error occurred to SAX
296     * @throws ProcessingException If an error occurred
297     */
298    public static void toSAXEnumerator (ContentHandler handler, Enumerator enumerator) throws SAXException, ProcessingException
299    {
300        XMLUtils.startElement(handler, "enumeration");
301        
302        try
303        {
304            for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet())
305            {
306                String valueAsString = ParameterHelper.valueToString(entry.getKey());
307                I18nizableText label = entry.getValue();
308
309                // Generate option
310                AttributesImpl attrs = new AttributesImpl();
311                attrs.addCDATAAttribute("value", valueAsString);
312                
313                XMLUtils.startElement(handler, "option", attrs);
314                
315                if (label != null)
316                {
317                    label.toSAX(handler);
318                }
319                else
320                {
321                    XMLUtils.data(handler, valueAsString);
322                }
323                
324                XMLUtils.endElement(handler, "option");
325            }
326        }
327        catch (Exception e)
328        {
329            throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e);
330        }
331
332        XMLUtils.endElement(handler, "enumeration");
333    }
334    
335    /**
336     * SAX parameter validator
337     * @param handler The content handler where to SAX
338     * @param validator The validator to SAX
339     * @throws SAXException If an error occurred while SAXing
340     */
341    public static void toSAXValidator (ContentHandler handler, Validator validator) throws SAXException
342    {
343        if (validator != null)
344        {
345            XMLUtils.startElement(handler, "validation");
346            validator.saxConfiguration(handler);
347            XMLUtils.endElement(handler, "validation");
348        }
349    }
350    
351    /**
352     * Convert the parameter in a JSON map
353     * @param parameter The parameter to convert
354     * @return The Parameter as a map
355     * @throws Exception If an error occured when converting the parameter
356     */
357    public static Map<String, Object> toJSON(Parameter parameter) throws Exception
358    {
359        Map<String, Object> result = new HashMap<>();
360        
361        result.put("id", parameter.getId());
362        result.put("type", parameter.getType());
363        result.put("default-value", parameter.getDefaultValue());
364        result.put("description", parameter.getDescription());
365        result.put("label", parameter.getLabel());
366        result.put("plugin", parameter.getPluginName());
367        
368        if (parameter.getValidator() != null)
369        {
370            result.put("validation", parameter.getValidator().toJson());
371        }
372        
373        if (parameter.getEnumerator() != null)
374        {
375            List<Map<String, Object>> enumeration = new ArrayList<>();
376            
377            Map<Object, I18nizableText> entries = parameter.getEnumerator().getEntries();
378            for (Object entryKey : entries.keySet())
379            {
380                Map<String, Object> option = new HashMap<>();
381                option.put("value", entryKey);
382                option.put("label", entries.get(entryKey));
383                enumeration.add(option);
384            }
385            
386            result.put("enumeration", enumeration);
387        }
388        
389        result.put("widget", parameter.getWidget());
390        result.put("widget-params", parameter.getWidgetParameters());
391        
392        return result;
393    }
394}