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.ZonedDateTime;
020import java.time.chrono.IsoChronology;
021import java.time.format.DateTimeFormatter;
022import java.time.format.ResolverStyle;
023import java.util.ArrayList;
024import java.util.Collection;
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;
040import org.ametys.runtime.model.DefinitionContext;
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                return DateUtils.parse(value);
176            }
177            else if (type == ParameterType.BINARY)
178            {
179                return null;
180            }
181        }
182        catch (Exception nfe)
183        {
184            if (value.length() != 0)
185            {
186                _logger.error("Cannot cast value '" + value + "' into type '" + typeToString(type) + "'. Null object will be used.", nfe);
187            }
188            else if (_logger.isDebugEnabled())
189            {
190                _logger.debug("Failed to cast empty string to type '" + typeToString(type) + "'. Null object will be used.", nfe);
191            }
192        }
193        return null;
194    }
195    
196
197    /**
198     * Converts known types to string
199     * 
200     * @param value Typed value
201     * @return String readable by the config bean
202     * @throws IllegalArgumentException if the object is a InputStream
203     */
204    public static String valueToString(Object value)
205    {
206        if (value == null)
207        {
208            return null;
209        }
210
211        if (value instanceof Date)
212        {
213            ZonedDateTime zdt = DateUtils.asZonedDateTime((Date) value, null);           
214            return zdt.format(getISODateTimeFormatter());
215        }
216        
217        if (value instanceof InputStream)
218        {
219            throw new IllegalArgumentException("The object to convert is an input stream");
220        }
221
222        return value.toString();
223    }
224    
225    /**
226     * SAX a parameter
227     * @param handler The content handler where to SAX
228     * @param parameter The parameter to SAX
229     * @param value The parameter value. Can be null.
230     * @throws SAXException If an error occurred while SAXing
231     * @throws ProcessingException If an error occurred
232     */
233    public static void toSAXParameter (ContentHandler handler, Parameter parameter, Object value) throws SAXException, ProcessingException
234    {
235        AttributesImpl parameterAttr = new AttributesImpl();
236        parameterAttr.addAttribute("", "plugin", "plugin", "CDATA", parameter.getPluginName());
237        XMLUtils.startElement(handler, parameter.getId(), parameterAttr);
238        
239        toSAXParameterInternal(handler, parameter, value);
240        
241        XMLUtils.endElement(handler, parameter.getId());
242    }
243
244    /**
245     * SAX a parameter except the root tag
246     * @param handler The content handler where to SAX
247     * @param parameter The parameter to SAX
248     * @param value The parameter value. Can be null.
249     * @throws SAXException If an error occurred while SAXing
250     * @throws ProcessingException If an error occurred
251     */
252    public static void toSAXParameterInternal(ContentHandler handler, Parameter parameter, Object value) throws SAXException, ProcessingException
253    {
254        parameter.getLabel().toSAX(handler, "label");
255        parameter.getDescription().toSAX(handler, "description");
256        
257        XMLUtils.createElement(handler, "type", ParameterHelper.typeToString((ParameterType) parameter.getType()));
258        
259        Object defaultValue = parameter.getDefaultValue();
260        
261        if (defaultValue != null)
262        {
263            XMLUtils.createElement(handler, "default-value", ParameterHelper.valueToString(defaultValue));
264        }
265        
266        if (value != null)
267        {
268            XMLUtils.createElement(handler, "value", ParameterHelper.valueToString(value));
269        }
270        
271        if (parameter.getWidget() != null)
272        {
273            XMLUtils.createElement(handler, "widget", parameter.getWidget());
274        }
275        
276        Map<String, I18nizableText> widgetParameters = parameter.getWidgetParameters();
277        if (widgetParameters.size() > 0)
278        {
279            XMLUtils.startElement(handler, "widget-params");
280            for (String paramName : widgetParameters.keySet())
281            {
282                XMLUtils.startElement(handler, paramName);
283                widgetParameters.get(paramName).toSAX(handler);
284                XMLUtils.endElement(handler, paramName);
285            }
286            XMLUtils.endElement(handler, "widget-params");
287        }
288        
289        Enumerator enumerator = parameter.getEnumerator();
290        if (enumerator != null)
291        {
292            toSAXEnumerator(handler, enumerator);
293        }
294        
295        Validator validator = parameter.getValidator();
296        toSAXValidator(handler, validator);
297    }
298    
299    /**
300     * SAX parameter enumerator
301     * @param handler The content handler where to SAX
302     * @param enumerator The enumerator to SAX
303     * @throws SAXException If an error occurred to SAX
304     * @throws ProcessingException If an error occurred
305     */
306    public static void toSAXEnumerator (ContentHandler handler, Enumerator enumerator) throws SAXException, ProcessingException
307    {
308        XMLUtils.startElement(handler, "enumeration");
309        
310        try
311        {
312            for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet())
313            {
314                String valueAsString = ParameterHelper.valueToString(entry.getKey());
315                I18nizableText label = entry.getValue();
316
317                // Generate option
318                AttributesImpl attrs = new AttributesImpl();
319                attrs.addCDATAAttribute("value", valueAsString);
320                
321                XMLUtils.startElement(handler, "option", attrs);
322                
323                if (label != null)
324                {
325                    label.toSAX(handler);
326                }
327                else
328                {
329                    XMLUtils.data(handler, valueAsString);
330                }
331                
332                XMLUtils.endElement(handler, "option");
333            }
334        }
335        catch (Exception e)
336        {
337            throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e);
338        }
339
340        XMLUtils.endElement(handler, "enumeration");
341    }
342    
343    /**
344     * SAX parameter validator
345     * @param handler The content handler where to SAX
346     * @param validator The validator to SAX
347     * @throws SAXException If an error occurred while SAXing
348     */
349    public static void toSAXValidator (ContentHandler handler, Validator validator) throws SAXException
350    {
351        if (validator != null)
352        {
353            XMLUtils.startElement(handler, "validation");
354            
355            Map<String, Object> configuration = validator.getConfiguration();
356            
357            for (Map.Entry<String, Object> entry : configuration.entrySet())
358            {
359                _saxConfigurationObject(handler, entry.getKey(), entry.getValue());
360            }
361            
362            XMLUtils.endElement(handler, "validation");
363        }
364    }
365    
366    @SuppressWarnings("unchecked")
367    private static void _saxConfigurationObject(ContentHandler handler, String name, Object value) throws SAXException
368    {
369        if (value instanceof I18nizableText)
370        {
371            ((I18nizableText) value).toSAX(handler, name);
372        }
373        else if (value instanceof Collection)
374        {
375            for (Object item : (Collection) value)
376            {
377                if (item != null)
378                {
379                    _saxConfigurationObject(handler, name, item);
380                }
381            }
382        }
383        else if (value instanceof Map)
384        {
385            XMLUtils.startElement(handler, name);
386            for (Map.Entry<String, Object> subEntry : ((Map<String, Object>) value).entrySet())
387            {
388                _saxConfigurationObject(handler, subEntry.getKey(), subEntry.getValue());
389            }
390            XMLUtils.endElement(handler, name);
391        }
392        else if (value instanceof Object[])
393        {
394            for (Object item : (Object[]) value)
395            {
396                if (item != null)
397                {
398                    _saxConfigurationObject(handler, name, item);
399                }
400            }
401        }
402        else
403        {
404            XMLUtils.createElement(handler, name, String.valueOf(value));
405        }
406    }
407    
408    /**
409     * Convert the parameter in a JSON map
410     * @param parameter The parameter to convert
411     * @return The Parameter as a map
412     * @throws ProcessingException If an error occurred when converting the parameter
413     */
414    @SuppressWarnings("unchecked")
415    public static Map<String, Object> toJSON(Parameter parameter) throws ProcessingException
416    {
417        ElementDefinition definition = new ElementDefinition();
418        definition.setName(parameter.getId());
419        definition.setLabel(parameter.getLabel());
420        definition.setDescription(parameter.getDescription());
421        definition.setPluginName(parameter.getPluginName());
422        definition.setValidator(parameter.getValidator());
423        definition.setWidget(parameter.getWidget());
424        definition.setWidgetParameters(parameter.getWidgetParameters());
425        
426        Map<String, Object> result = definition.toJSON(DefinitionContext.newInstance());
427
428        // Put type from ParameterHelper.ParameterType to not convert from the old to the new API
429        result.put("type", parameter.getType().name().replaceAll("_", "-"));
430        if (parameter.getDefaultValue() != null)
431        {
432            result.put("default-value", parameter.getDefaultValue());
433        }
434        
435        // Manage enumerator from ParameterHelper because enumerator class is not the same from Parameter to ElementDefintion
436        if (parameter.getEnumerator() != null)
437        {
438            Enumerator enumerator = parameter.getEnumerator();
439            List<Map<String, Object>> enumeration = new ArrayList<>();
440            
441            try
442            {
443                Map<Object, I18nizableText> entries = enumerator.getEntries();
444                for (Object entryKey : entries.keySet())
445                {
446                    Map<String, Object> option = new HashMap<>();
447                    option.put("value", ParameterHelper.valueToString(entryKey));
448                    option.put("label", entries.get(entryKey));
449                    enumeration.add(option);
450                }
451            }
452            catch (Exception e)
453            {
454                throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e);
455            }
456            
457            result.put("enumeration", enumeration);
458            result.put("enumerationConfig", enumerator.getConfiguration());
459        }
460        
461        return result;
462    }
463}