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.DefaultElementDefinition;
043import org.ametys.runtime.model.type.ElementType;
044
045
046/**
047 * This class handles all needed to use typed parameters
048 * @deprecated All of this helper methods are now in the new Parameter API classes
049 */
050@Deprecated
051public final class ParameterHelper
052{
053    /**
054     * Enumeration of supported types
055     * @deprecated Use {@link ElementType}
056     */
057    @Deprecated
058    public static enum ParameterType
059    {
060        /** boolean values */
061        BOOLEAN,
062        /** string values */
063        STRING,
064        /** password values */
065        PASSWORD,
066        /** long values */
067        LONG,
068        /** double values */
069        DOUBLE,
070        /** date values */
071        DATE,
072        /** binary values */
073        BINARY,
074        /** datasource values */
075        DATASOURCE
076    }
077    
078    /**
079     * 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'. 
080     */
081    private static DateTimeFormatter __ISO_OFFSET_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withResolverStyle(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
082
083    // Logger for traces
084    private static Logger _logger = LoggerFactory.getLogger(ParameterHelper.class);
085
086    private ParameterHelper ()
087    {
088        // empty
089    }
090    
091    /**
092     * 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'. 
093     * This formatter is similar to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} but force 3-digits milliseconds.
094     * @return ISO date-time formatter
095     */
096    public static DateTimeFormatter getISODateTimeFormatter()
097    {
098        return __ISO_OFFSET_DATE_TIME;
099    }
100    
101    /**
102     * Return the readable name of a type
103     * 
104     * @param type Type to convert
105     * @return Returns the name of the type
106     * @throws IllegalArgumentException If the type is unknwon
107     */
108    public static String typeToString(ParameterType type)
109    {
110        return type.name().toLowerCase();
111    }
112    
113    /**
114     * Convert a string containing a type to its value
115     * 
116     * @param type Name of the type
117     * @return Type
118     * @throws IllegalArgumentException if the type is unknown
119     */
120    public static ParameterType stringToType(String type)
121    {
122        try
123        {
124            return ParameterType.valueOf(type.toUpperCase());
125        }
126        catch (IllegalArgumentException e)
127        {
128            throw new IllegalArgumentException("The type '" + type + "' is unknown for config parameters");
129        }
130    }
131    
132    /**
133     * Cast a untyped value (string) to an object of the type
134     * 
135     * @param value Value to cast
136     * @param type Type to cast value in
137     * @return An object of the type 'type' with value 'value', or null if type
138     *         is unknown or value cannot be cast
139     */
140    public static Object castValue(String value, ParameterType type)
141    {
142        
143        if (value == null)
144        {
145            return null;
146        }
147
148        try
149        {
150            if (type == ParameterType.BOOLEAN)
151            {
152                return new Boolean(value);
153            }
154            else if (type == ParameterType.STRING)
155            {
156                return value;
157            }
158            else if (type == ParameterType.PASSWORD)
159            {
160                return value;
161            }
162            else if (type == ParameterType.DATASOURCE)
163            {
164                return value;
165            }
166            else if (type == ParameterType.LONG)
167            {
168                return new Long(value);
169            }
170            else if (type == ParameterType.DOUBLE)
171            {
172                return new Double(value);
173            }
174            else if (type == ParameterType.DATE)
175            {
176                return DateUtils.parse(value);
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    public static Map<String, Object> toJSON(Parameter parameter) throws ProcessingException
416    {
417        ElementDefinition definition = new DefaultElementDefinition();
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}