001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.jsp.util;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.file.types.CmsResourceTypeFolderSubSitemap;
034import org.opencms.flex.CmsFlexController;
035import org.opencms.i18n.CmsLocaleManager;
036import org.opencms.json.JSONException;
037import org.opencms.json.JSONObject;
038import org.opencms.json.JSONTokener;
039import org.opencms.jsp.CmsJspResourceWrapper;
040import org.opencms.main.CmsException;
041import org.opencms.main.CmsLog;
042import org.opencms.main.OpenCms;
043import org.opencms.module.CmsModule;
044import org.opencms.util.CmsHtmlConverter;
045import org.opencms.util.CmsHtmlExtractor;
046import org.opencms.util.CmsRequestUtil;
047import org.opencms.util.CmsStringUtil;
048import org.opencms.util.CmsUUID;
049
050import java.io.UnsupportedEncodingException;
051import java.text.DateFormat;
052import java.text.ParseException;
053import java.util.ArrayList;
054import java.util.Collection;
055import java.util.Collections;
056import java.util.Date;
057import java.util.List;
058import java.util.Locale;
059import java.util.Map;
060
061import javax.servlet.ServletRequest;
062import javax.servlet.http.HttpServletRequest;
063import javax.servlet.jsp.PageContext;
064
065import org.apache.commons.beanutils.PropertyUtils;
066import org.apache.commons.logging.Log;
067
068import com.google.common.collect.Maps;
069
070/**
071 * Provides utility methods to be used as functions from a JSP with the EL.<p>
072 *
073 * @since 7.0.2
074 *
075 * @see CmsJspContentAccessBean
076 */
077public final class CmsJspElFunctions {
078
079    /** Logger instance for this class. */
080    private static final Log LOG = CmsLog.getLog(CmsJspElFunctions.class);
081
082    /**
083     * Hide the public constructor.<p>
084     */
085    private CmsJspElFunctions() {
086
087        // NOOP
088    }
089
090    /**
091     * Extends the given list by adding the provided object.<p>
092     *
093     * @param list the list to extend
094     * @param value the value to add to the list
095     */
096    public static void addToList(List<Object> list, Object value) {
097
098        list.add(value);
099    }
100
101    /**
102     * Returns an OpenCms user context created from an Object.<p>
103     *
104     * <ul>
105     * <li>If the input is already a {@link CmsObject}, it is casted and returned unchanged.
106     * <li>If the input is a {@link ServletRequest}, the OpenCms user context is read from the request context.
107     * <li>If the input is a {@link PageContext}, the OpenCms user context is read from the request of the page context.
108     * <li>Otherwise the input is converted to a String which should be a user name, and creation of a OpenCms
109     * user context with this name is attempted. Please note that this will only work if the user name is
110     * either the "Guest" user or the "Export" user.
111     * <li>If no valid OpenCms user context could be created with all of the above, then a new user context for
112     * the "Guest" user is created.
113     * </ul>
114     *
115     * @param input the input to create an OpenCms user context from
116     *
117     * @return an OpenCms user context created from an Object
118     */
119    public static CmsObject convertCmsObject(Object input) {
120
121        CmsObject result;
122        if (input instanceof CmsObject) {
123            result = (CmsObject)input;
124        } else if (input instanceof ServletRequest) {
125            result = CmsFlexController.getCmsObject((ServletRequest)input);
126        } else if (input instanceof PageContext) {
127            result = CmsFlexController.getCmsObject(((PageContext)input).getRequest());
128        } else {
129            try {
130                // try to use the given name as user name
131                result = OpenCms.initCmsObject(String.valueOf(input));
132                // try to set the right site root
133                ServletRequest req = convertRequest(input);
134                if (req instanceof HttpServletRequest) {
135                    result.getRequestContext().setSiteRoot(
136                        OpenCms.getSiteManager().matchRequest((HttpServletRequest)req).getSiteRoot());
137                }
138            } catch (CmsException e) {
139                LOG.warn(e.getLocalizedMessage(), e);
140                result = null;
141            }
142        }
143        if (result == null) {
144            try {
145                result = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest());
146                // try to set the right site root
147                ServletRequest req = convertRequest(input);
148                if (req instanceof HttpServletRequest) {
149                    result.getRequestContext().setSiteRoot(
150                        OpenCms.getSiteManager().matchRequest((HttpServletRequest)req).getSiteRoot());
151                }
152            } catch (CmsException e1) {
153                // this should never fail since we can always create a "Guest" user
154            }
155        }
156        return result;
157    }
158
159    /**
160     * Returns a Date created from an Object.<p>
161     *
162     * <ul>
163     * <li>The Object is first checked if it is a {@link Date} already, if so it is casted and returned unchanged.
164     * <li>If not, the input is checked if it is a {@link Long}, and if so the Date is created from the Long value.
165     * <li>If it's not a Date and not a Long, the Object is transformed to a String and then it's tried
166     * to parse a Long out of the String.
167     * <li>If this fails, it is tried to parse as a Date using the
168     * default date formatting.
169     * <li>If this also fails, a new Date is returned that has been initialized with 0.<p>
170     * </ul>
171     *
172     * @param input the Object to create a Date from
173     *
174     * @return a Date created from the given Object
175     */
176    public static Date convertDate(Object input) {
177
178        Date result;
179        if (input instanceof Date) {
180            result = (Date)input;
181        } else if (input instanceof Long) {
182            result = new Date(((Long)input).longValue());
183        } else {
184            String str = String.valueOf(input);
185            try {
186                // treat the input as a String
187                long l = Long.parseLong(str);
188                result = new Date(l);
189            } catch (NumberFormatException e) {
190                try {
191                    // try to parse String as a Date
192                    result = DateFormat.getDateInstance().parse(str);
193                } catch (ParseException e1) {
194                    result = null;
195                }
196                if (result == null) {
197                    // use default date if parsing fails
198                    result = new Date(0);
199                }
200            }
201        }
202        return result;
203    }
204
205    /**
206     * Returns a Double created from an Object.<p>
207     *
208     * <ul>
209     * <li>If the input already is a {@link java.lang.Number}, this number is returned as Double.
210     * <li>If the input is of type {@link A_CmsJspValueWrapper}, the wrapper is converted to a Double using {@link A_CmsJspValueWrapper#getToDouble()}.
211     * <li>If the input is a String, it is converted to a Double.
212     * <li>Otherwise <code>null</code> is returned.
213     * </li>
214     *
215     * @param input the Object to create a Double from
216     *
217     * @return a Double created from the given Object
218     */
219    public static Double convertDouble(Object input) {
220
221        Double result = null;
222        if (input instanceof Double) {
223            result = (Double)input;
224        } else if (input instanceof Number) {
225            result = Double.valueOf(((Number)input).doubleValue());
226        } else if (input instanceof A_CmsJspValueWrapper) {
227            result = ((A_CmsJspValueWrapper)input).getToDouble();
228        } else {
229            if (input != null) {
230                String str = String.valueOf(input);
231                // support both "10,0" and "10.0" comma style
232                str = str.replace(',', '.');
233                try {
234                    result = Double.valueOf(str);
235                } catch (NumberFormatException e) {
236                    LOG.info(e.getLocalizedMessage());
237                }
238            }
239        }
240        return result;
241    }
242
243    /**
244     * Returns a list of attribute values specified by the attribute name of the items of the given list.<p>
245     *
246     * @param input the list of objects to obtain the attribute values from
247     * @param attributeName the name of the attribute to obtain
248     * @return a list of attributes specified by the attribute name of the items of the given list
249     */
250    public static List<Object> convertList(List<Object> input, String attributeName) {
251
252        List<Object> result = new ArrayList<Object>(input.size());
253
254        for (Object item : input) {
255            try {
256                result.add(PropertyUtils.getProperty(item, attributeName));
257            } catch (Exception e) {
258                // specified attribute is not implemented, return empty list
259                return Collections.emptyList();
260            }
261        }
262
263        return result;
264    }
265
266    /**
267     * Returns a Locale created from an Object.<p>
268     *
269     * <ul>
270     * <li>The Object is first checked if it is a {@link Locale} already, if so it is casted and returned.
271     * <li>If not, the input is transformed to a String and then a Locale lookup with this String is done.
272     * <li>If the locale lookup fails, the OpenCms default locale is returned.
273     * </ul>
274     *
275     * @param input the Object to create a Locale from
276     *
277     * @return a Locale created from the given Object
278     */
279    public static Locale convertLocale(Object input) {
280
281        Locale locale;
282        if (input instanceof Locale) {
283            locale = (Locale)input;
284        } else {
285            locale = CmsLocaleManager.getLocale(String.valueOf(input));
286        }
287        return locale;
288    }
289
290    /**
291     * Returns a resource created from an Object.<p>
292     *
293     * <ul>
294     * <li>If the input is already a {@link CmsResource}, it is casted to the resource and returned unchanged.
295     * <li>If the input is a String, the given OpenCms context is used to read a resource with this name from the VFS.
296     * <li>If the input is a {@link CmsUUID}, the given OpenCms context is used to read a resource with
297     * this UUID from the VFS.
298     * <li>Otherwise the input is converted to a String, and then the given OpenCms context is used to read
299     * a resource with this name from the VFS.
300     * </ul>
301     *
302     * @param cms the current OpenCms user context
303     * @param input the input to create a resource from
304     *
305     * @return a resource created from the given Object
306     *
307     * @throws CmsException in case of errors accessing the OpenCms VFS for reading the resource
308     */
309    public static CmsResource convertRawResource(CmsObject cms, Object input) throws CmsException {
310
311        CmsResource result;
312        CmsResourceFilter filter = CmsResourceFilter.ignoreExpirationOffline(cms);
313        if (input instanceof CmsResource) {
314            // input is already a resource
315            result = (CmsResource)input;
316        } else if (input instanceof String) {
317            if (CmsUUID.isValidUUID((String)input)) {
318                // input is a UUID as String
319                result = cms.readResource(CmsUUID.valueOf((String)input), filter);
320            } else {
321                // input is a path as String
322                result = cms.readResource(cms.getRequestContext().removeSiteRoot((String)input), filter);
323            }
324        } else if (input instanceof CmsUUID) {
325            // input is a UUID
326            result = cms.readResource((CmsUUID)input, filter);
327        } else {
328            // input seems not really to make sense, try to use it like a String
329            result = cms.readResource(String.valueOf(input), filter);
330        }
331        return result;
332    }
333
334    /**
335     * Tries to convert the given input object into a request.<p>
336     *
337     * This is only possible if the input object is already a request
338     * or if it is a page context.<p>
339     *
340     * If everything else, this method returns <code>null</code>.<p>
341     *
342     * @param input the input object to convert to a request
343     *
344     * @return a request object, or <code>null</code>
345     */
346    public static ServletRequest convertRequest(Object input) {
347
348        ServletRequest req = null;
349        if (input instanceof ServletRequest) {
350            req = (ServletRequest)input;
351        } else if (input instanceof PageContext) {
352            req = ((PageContext)input).getRequest();
353        }
354        return req;
355    }
356
357    /**
358     * Returns a resource wrapper created from the input.
359     *
360     * The wrapped result of {@link #convertRawResource(CmsObject, Object)} is returned.
361     *
362     * @param cms the current OpenCms user context
363     * @param input the input to create a resource from
364     *
365     * @return a resource wrapper created from the given Object
366     *
367     * @throws CmsException in case of errors accessing the OpenCms VFS for reading the resource
368     */
369    public static CmsJspResourceWrapper convertResource(CmsObject cms, Object input) throws CmsException {
370
371        CmsJspResourceWrapper result;
372        if (input instanceof CmsResource) {
373            result = CmsJspResourceWrapper.wrap(cms, (CmsResource)input);
374        } else {
375            result = CmsJspResourceWrapper.wrap(cms, convertRawResource(cms, input));
376        }
377        return result;
378    }
379
380    /**
381     * Returns a list of resource wrappers created from the input list of resources.
382     *
383     * @param cms the current OpenCms user context
384     * @param list the list to create the resource wrapper list from
385     *
386     * @return the list of wrapped resources.
387     */
388    public static List<CmsJspResourceWrapper> convertResourceList(CmsObject cms, List<CmsResource> list) {
389
390        List<CmsJspResourceWrapper> result = new ArrayList<CmsJspResourceWrapper>(list.size());
391        for (CmsResource res : list) {
392            result.add(CmsJspResourceWrapper.wrap(cms, res));
393        }
394        return result;
395    }
396
397    /**
398     * Returns a CmsUUID created from an Object.<p>
399     *
400     * <ul>
401     * <li>The Object is first checked if it is a {@link CmsUUID} already, if so it is casted and returned.
402     * <li>If not, the input is transformed to a byte[] and a new {@link CmsUUID} is created with this byte[].
403     * <li>Otherwise the input is casted to a String and a new {@link CmsUUID} is created with this String.
404     * </ul>
405     *
406     * @param input the Object to create a CmsUUID from
407     *
408     * @return a CmsUUID created from the given Object
409     */
410    public static CmsUUID convertUUID(Object input) {
411
412        CmsUUID uuid;
413        if (input instanceof CmsUUID) {
414            uuid = (CmsUUID)input;
415        } else if (input instanceof byte[]) {
416            uuid = new CmsUUID((byte[])input);
417        } else {
418            uuid = new CmsUUID(String.valueOf(input));
419        }
420        return uuid;
421    }
422
423    /**
424     * Returns a newly created, empty List object.<p>
425     *
426     * There is no way to create an empty list using standard JSTL methods,
427     * hence this function.<p>
428     *
429     * @return a newly created, empty List object
430     */
431    public static List<Object> createList() {
432
433        return new ArrayList<Object>();
434    }
435
436    /**
437     * Returns the current OpenCms user context from the given page context.<p>
438     *
439     * @param input the input to create a CmsObject from
440     *
441     * @return the current OpenCms user context from the given page context
442     */
443    public static CmsObject getCmsObject(Object input) {
444
445        return convertCmsObject(input);
446    }
447
448    /**
449     * Returns the size of the given list.<p>
450     *
451     * @param input the list of objects to obtain the size from
452     * @return the size of the given list
453     */
454    public static Integer getListSize(Collection<Object> input) {
455
456        if (input != null) {
457            return Integer.valueOf(input.size());
458        }
459        // input was null
460        return Integer.valueOf(0);
461    }
462
463    /**
464     * Returns a parameter value from the module parameters.<p>
465     *
466     * @param name the name of the module
467     * @param key the parameter to return the value for
468     * @return the parameter value from the module parameters, or <code>null</code> if the parameter is not set
469     */
470    public static String getModuleParam(String name, String key) {
471
472        CmsModule module = OpenCms.getModuleManager().getModule(name);
473        if (module != null) {
474            return module.getParameter(key);
475        }
476        return null;
477    }
478
479    /**
480     * Returns the current request URI.<p>
481     *
482     * For OpenCms 10.5, this is the same as using <code>${cms.requestContext.uri}</code> on a JSP.<p>
483     *
484     * @param input the request convertible object to get the navigation URI from
485     *
486     * @return the current navigation URI
487     *
488     * @deprecated On a JSP use <code>${cms.requestContext.uri}</code> instead.
489     */
490    @Deprecated
491    public static String getNavigationUri(Object input) {
492
493        ServletRequest req = convertRequest(input);
494        if (req == null) {
495            return null;
496        }
497        return getCmsObject(input).getRequestContext().getUri();
498    }
499
500    /**
501     * Returns the link without parameters from a String that is formatted for a GET request.<p>
502     *
503     * @param url the URL to remove the parameters from
504     * @return the link without parameters
505     */
506    public static String getRequestLink(String url) {
507
508        return CmsRequestUtil.getRequestLink(url);
509    }
510
511    /**
512     * Returns the value of a parameter from a String that is formatted for a GET request.<p>
513     *
514     * @param url the URL to get the parameter value from
515     * @param paramName the request parameter name
516     * @return the value of the parameter
517     */
518    public static String getRequestParam(String url, String paramName) {
519
520        Map<String, String[]> params = Collections.emptyMap();
521        if (CmsStringUtil.isNotEmpty(url)) {
522            int pos = url.indexOf(CmsRequestUtil.URL_DELIMITER);
523            if (pos >= 0) {
524                params = CmsRequestUtil.createParameterMap(url.substring(pos + 1));
525            }
526        }
527        String[] result = params.get(paramName);
528        if (result != null) {
529            return result[0];
530        }
531        return null;
532    }
533
534    /**
535     * Returns a JSP / EL VFS access bean.<p>
536     *
537     * @param input the Object to create a CmsObject from
538     *
539     * @return a JSP / EL VFS access bean
540     */
541    public static CmsJspVfsAccessBean getVfsAccessBean(Object input) {
542
543        return CmsJspVfsAccessBean.create(CmsJspElFunctions.convertCmsObject(input));
544    }
545
546    /**
547     * Returns whether the given resource is a sub sitemap folder.<p>
548     *
549     * @param resource the resource to check
550     *
551     * @return <code>true</code> if the given resource is a sub sitemap folder
552     */
553    public static boolean isSubSitemap(CmsResource resource) {
554
555        return (resource != null) && CmsResourceTypeFolderSubSitemap.isSubSitemap(resource);
556    }
557
558    /**
559     * Returns whether the given value is an instance of {@link A_CmsJspValueWrapper}.<p>
560     *
561     * @param value the value object to check
562     *
563     * @return <code>true</code> if the given value is an instance of {@link A_CmsJspValueWrapper}
564     */
565    public static boolean isWrapper(Object value) {
566
567        return (value instanceof A_CmsJspValueWrapper);
568    }
569
570    /**
571     * Parses the JSON String and returns the requested value.<p>
572     *
573     * @param maybeJsonString the JSON string
574     * @param key the key
575     *
576     * @return the json value string
577     */
578    public static String jsonGetString(Object maybeJsonString, Object key) {
579
580        try {
581            if (maybeJsonString == null) {
582                return null;
583            }
584            String jsonString = (String)maybeJsonString;
585            if (CmsStringUtil.isEmptyOrWhitespaceOnly(jsonString)) {
586                return null;
587            }
588            JSONObject json = new JSONObject(jsonString);
589            String keyString = (String)key;
590            return json.optString(keyString);
591        } catch (Exception e) {
592            return null;
593        }
594    }
595
596    /**
597     * Converts a string (which is assumed to contain a JSON object whose values are strings only) to a map, for use in JSPs.<p>
598     *
599     * If the input can't be interpreted as JSON, an empty map is returned.
600     *
601     * @param jsonString the JSON string
602     * @return the map with the keys/values from the JSON
603     */
604    public static Map<String, String> jsonToMap(String jsonString) {
605
606        Map<String, String> result = Maps.newHashMap();
607        if (jsonString != null) {
608            try {
609                JSONObject json = new JSONObject(jsonString);
610                for (String key : json.keySet()) {
611                    String value = json.optString(key);
612                    if (value != null) {
613                        result.put(key, value);
614                    }
615                }
616            } catch (Exception e) {
617                LOG.warn(e.getLocalizedMessage(), e);
618            }
619        }
620        return result;
621    }
622
623    /**
624     * Looks up the given key from the map that is passed as a String, and returns either the
625     * element found or the empty String.<p>
626     *
627     * The map String must have the form <code>"key1:value1|key2:value2"</code> etc.<p>
628     *
629     * @param key the key to look up
630     * @param map the map represented as a String
631     * @return the element found in the map with the given key, or the empty String
632     */
633    public static String lookup(String key, String map) {
634
635        return lookup(key, map, "");
636    }
637
638    /**
639     * Looks up the given key from the map that is passed as a String, and returns either the
640     * element found or the default value.<p>
641     *
642     * The map String must have the form <code>"key1:value1|key2:value2"</code> etc.<p>
643     *
644     * @param key the key to look up
645     * @param map the map represented as a String
646     * @param defaultValue the default value
647     * @return the element found in the map with the given key, or the default value
648     */
649    public static String lookup(String key, String map, String defaultValue) {
650
651        Map<String, String> values = CmsStringUtil.splitAsMap(map, "|", ":");
652        String result = values.get(key);
653        if (CmsStringUtil.isEmptyOrWhitespaceOnly(result)) {
654            return defaultValue;
655        }
656        return result;
657    }
658
659    /**
660     * Calculates the next largest integer for the given number parameter.<p>
661     *
662     * Note that the result is an Object of type {@link java.lang.Long},
663     * so in case the parameter can not be converted to a number, <code>null</code> is returned.<p>
664     *
665     * @param param an Object that will be converted to a number
666     *
667     * @return the next largest integer for the given number parameter
668     */
669    public static Long mathCeil(Object param) {
670
671        Long result = null;
672        if ((param instanceof Long) || (param instanceof Integer)) {
673            result = Long.valueOf(((Number)param).longValue());
674        } else {
675            Double d = convertDouble(param);
676            if (d != null) {
677                result = Long.valueOf((long)Math.ceil(d.doubleValue()));
678            }
679        }
680        return result;
681    }
682
683    /**
684     * Calculates the next smallest integer for the given number parameter.<p>
685     *
686     * Note that the result is an Object of type {@link java.lang.Long},
687     * so in case the parameter can not be converted to a number, <code>null</code> is returned.<p>
688     *
689     * @param param an Object that will be converted to a number
690     *
691     * @return the next smallest integer for the given number parameter
692     */
693    public static Long mathFloor(Object param) {
694
695        Long result = null;
696        if ((param instanceof Long) || (param instanceof Integer)) {
697            result = Long.valueOf(((Number)param).longValue());
698        } else {
699            Double d = convertDouble(param);
700            if (d != null) {
701                result = Long.valueOf((long)Math.floor(d.doubleValue()));
702            }
703        }
704        return result;
705    }
706
707    /**
708     * Calculates the next integer for the given number parameter by rounding.<p>
709     *
710     * Note that the result is an Object of type {@link java.lang.Long},
711     * so in case the parameter can not be converted to a number, <code>null</code> is returned.<p>
712     *
713     * @param param an Object that will be converted to a number
714     *
715     * @return the next integer for the given number parameter calculated by rounding
716     */
717    public static Long mathRound(Object param) {
718
719        Long result = null;
720        if ((param instanceof Long) || (param instanceof Integer)) {
721            result = Long.valueOf(((Number)param).longValue());
722        } else {
723            Double d = convertDouble(param);
724            if (d != null) {
725                result = Long.valueOf(Math.round(d.doubleValue()));
726            }
727        }
728        return result;
729    }
730
731    /**
732     * Parses JSON object.
733     *
734     * @param value
735     * @return
736     */
737    public static Object parseJson(String value) {
738
739        value = value.trim();
740        JSONTokener tok = new JSONTokener(value);
741        try {
742            return tok.nextValue();
743        } catch (JSONException e) {
744            return null;
745        }
746    }
747
748    /**
749     * Repairs the given HTML input by adding potentially missing closing tags.<p>
750     *
751     * @param input the HTML input
752     *
753     * @return the repaired HTML or an empty string in case of errors
754     */
755    public static String repairHtml(String input) {
756
757        CmsHtmlConverter converter = new CmsHtmlConverter();
758        String result = converter.convertToStringSilent(input);
759        return result == null ? "" : result;
760    }
761
762    /**
763     * Strips all HTML markup from the given input.<p>
764     *
765     * <ul>
766     * <li>In case the input is an instance of {@link CmsJspContentAccessValueWrapper}, an optimized
767     * method is used for the HTML stripping.
768     * <li>Otherwise the input is converted to a String and this String is stripped.
769     * </ul>
770     *
771     * @param input the input to Strip from HTML
772     *
773     * @return the given input with all HTML stripped.
774     */
775    public static String stripHtml(Object input) {
776
777        if (input instanceof CmsJspContentAccessValueWrapper) {
778            CmsJspContentAccessValueWrapper wrapper = (CmsJspContentAccessValueWrapper)input;
779            if (wrapper.getExists()) {
780                return wrapper.getContentValue().getPlainText(wrapper.getCmsObject());
781            } else {
782                return "";
783            }
784        } else {
785            try {
786                return CmsHtmlExtractor.extractText(
787                    String.valueOf(input),
788                    OpenCms.getSystemInfo().getDefaultEncoding());
789            } catch (org.htmlparser.util.ParserException e) {
790                LOG.error(e.getLocalizedMessage(), e);
791                return "";
792            } catch (UnsupportedEncodingException e) {
793                LOG.error(e.getLocalizedMessage(), e);
794                return "";
795            }
796        }
797    }
798
799    /**
800     * Converts the given Object to a (Double) number, falling back to a default if this is not possible.<p>
801     *
802     * In case even the default can not be converted to a number, <code>-1.0</code> is returned.<p>
803     *
804     * @param input the Object to convert to a (Double) number
805     * @param def the fall back default value in case the conversion is not possible
806     *
807     * @return the given Object converted to a (Double) number
808     */
809    public static Double toNumber(Object input, Object def) {
810
811        Double result = convertDouble(input);
812        if (result == null) {
813            result = convertDouble(def);
814        }
815        if (result == null) {
816            result = Double.valueOf(-1.0);
817        }
818        return result;
819    }
820
821    /**
822     * Returns a substring of the source, which is at most length characters long.<p>
823     *
824     * If a char is cut, <code>" ..."</code> is appended to the result.<p>
825     *
826     * @param input the string to trim
827     * @param length the maximum length of the string to be returned
828     *
829     * @return a substring of the source, which is at most length characters long
830     *
831     * @see CmsStringUtil#trimToSize(String, int, String)
832     */
833    public static String trimToSize(String input, int length) {
834
835        return CmsStringUtil.trimToSize(input, length, " ...");
836    }
837
838    /**
839     * Validates a value against a regular expression.<p>
840     *
841     * @param value the value
842     * @param regex the regex
843     *
844     * @return <code>true</code> if the value satisfies the validation
845     */
846    public static boolean validateRegex(String value, String regex) {
847
848        return CmsStringUtil.validateRegex(value, regex, true);
849    }
850}