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