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 GmbH & Co. KG, 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;
029
030import org.opencms.file.CmsProperty;
031import org.opencms.flex.CmsFlexController;
032import org.opencms.i18n.CmsEncoder;
033import org.opencms.main.CmsException;
034import org.opencms.main.CmsLog;
035import org.opencms.staticexport.CmsLinkManager;
036import org.opencms.util.CmsStringUtil;
037
038import java.util.HashMap;
039import java.util.Locale;
040import java.util.Map;
041
042import javax.servlet.ServletRequest;
043import javax.servlet.jsp.JspException;
044import javax.servlet.jsp.tagext.TagSupport;
045
046import org.apache.commons.lang3.LocaleUtils;
047import org.apache.commons.logging.Log;
048
049/**
050 * Provides access to the properties of a resource in the OpenCms VFS .<p>
051 *
052 * Of particular importance is the setting of the <code>file</code> attribute,
053 * which can take the following values.<p>
054 *
055 * This attribute allows you to specify where to search for the property.<BR>
056 * The following values are supported:
057 * </P>
058 * <DL>
059 *   <DT><b>uri</b> (default)</DT>
060 *   <DD>  Look up  the property on the file with the
061 *   uri requested by the user.</DD>
062 *   <DT><b>search.uri</b> or <b>search</b></DT>
063 *   <DD>Look up the property by also checking all parent folders for the property,
064 *   starting with the file with uri requested by the user and
065 *   going "upward" if the property was not found there.</DD>
066 *   <DT><b>element.uri</b></DT>
067 *   <DD>Look up the property on the currently
068 *   processed sub - element. This is useful in templates or other pages that
069 *   consist of many elements.</DD>
070 *   <DT><b>search.element.uri</b></DT>
071 *   <DD>Look up the property by also checking all parent folders for the
072 *   property, starting with the file with the currently processed sub -
073 *   element and going "upward" if the property was not found there.</DD>
074 *   <DT>sitemap</DT>
075 *   <DD>reads from the current sitemap entry</DD>
076 *   <DT>search.sitemap</DT>
077 *   <DD>Look up the property by also checking all parent sitemap entries
078 *   for the property, starting with the current sitemap entry and
079 *   going "upward" if the property was not found there.</DD>
080 *   <DT>container</DT>
081 *   <DD>reads from the current container element</DD>
082 *   <DT><B>{some-file-uri}</B></DT>
083 *   <DD>Look up the property on that exact file
084 *   uri in the OpenCms VFS,<EM> fallback if no other valid option is
085 *   selected for the file attribute.</EM></DD>
086 * </DL>
087 *
088 * <P>There are also some deprecated options for the "file" value that are
089 * still supported but should not longer be used:</P>
090 * <DL>
091 *   <DT>parent</DT>
092 *   <DD>same as <STRONG>uri</STRONG></DD>
093 *   <DT>search-parent</DT>
094 *   <DD>same as <STRONG>search.uri</STRONG></DD>
095 *   <DT>this</DT>
096 *   <DD>same as <STRONG>element.uri</STRONG></DD>
097 *   <DT>search-this</DT>
098 *   <DD>same as <STRONG>search.element.uri</STRONG></DD>
099 * </DL>
100 *
101 * @since 6.0.0
102 */
103public class CmsJspTagProperty extends TagSupport {
104
105    /** Tells for which resource properties should be looked up (with or without searching), depending on the {@link FileUse}. */
106    public static class CmsPropertyAction {
107
108        /** The VFS site path of the resource for which the properties should be read. */
109        private String m_vfsUri;
110        /** A flag, indicating if the property should be searched or not. */
111        private boolean m_search;
112
113        /**
114         * Default constructor.
115         * @param req the current servlet request.
116         * @param action the action to perform.
117         */
118        public CmsPropertyAction(ServletRequest req, String action) {
119
120            CmsFlexController controller = CmsFlexController.getController(req);
121
122            FileUse useAction = FileUse.URI;
123            if (action != null) {
124                // if action is set overwrite default
125                useAction = FileUse.parse(action);
126            }
127
128            if (useAction != null) {
129                switch (useAction) {
130                    case URI:
131                    case PARENT:
132                        // read properties of parent (i.e. top requested) file
133                        m_vfsUri = controller.getCmsObject().getRequestContext().getUri();
134                        break;
135                    case SEARCH:
136                    case SEARCH_URI:
137                    case SEARCH_PARENT:
138                        // try to find property on parent file and all parent folders
139                        m_vfsUri = controller.getCmsObject().getRequestContext().getUri();
140                        m_search = true;
141                        break;
142                    case ELEMENT_URI:
143                    case THIS:
144                        // read properties of this file
145                        m_vfsUri = controller.getCurrentRequest().getElementUri();
146                        break;
147                    case SEARCH_ELEMENT_URI:
148                    case SEARCH_THIS:
149                        // try to find property on this file and all parent folders
150                        m_vfsUri = controller.getCurrentRequest().getElementUri();
151                        m_search = true;
152                        break;
153                    default:
154                        // just to prevent the warning since all cases are handled
155                }
156            } else {
157                // read properties of the file named in the attribute
158                m_vfsUri = CmsLinkManager.getAbsoluteUri(action, controller.getCurrentRequest().getElementUri());
159                m_search = false;
160            }
161        }
162
163        /**
164         * Returns the VFS site path of the resource for which the properties should be read.
165         *
166         * @return the VFS site path of the resource for which the properties should be read.
167         */
168        public String getVfsUri() {
169
170            return m_vfsUri;
171        }
172
173        /**
174         * Returns <code>true</code> if it should be searched for the property, otherwise <code>false</code>.
175         * @return <code>true</code> if it should be searched for the property, otherwise <code>false</code>.
176         */
177        public boolean isSearch() {
178
179            return m_search;
180        }
181    }
182
183    /** Constants for <code>file</code> attribute interpretation. */
184    private enum FileUse {
185
186        /** Use element uri. */
187        ELEMENT_URI("element.uri"),
188        /** Use parent (same as {@link #URI}). */
189        PARENT("parent"),
190        /** Use search (same as {@link #SEARCH_URI}). */
191        SEARCH("search"),
192        /** Use search element uri. */
193        SEARCH_ELEMENT_URI("search.element.uri"),
194        /** Use search parent (same as {@link #SEARCH_URI}). */
195        SEARCH_PARENT("search-parent"),
196        /** Use seach this (same as {@link #SEARCH_ELEMENT_URI}). */
197        SEARCH_THIS("search-this"),
198        /** Use search uri. */
199        SEARCH_URI("search.uri"),
200        /** Use sitemap entries. */
201        /** Use this (same as {@link #ELEMENT_URI}). */
202        THIS("this"),
203        /** Use uri. */
204        URI("uri");
205
206        /** Property name. */
207        private String m_name;
208
209        /** Constructor.<p>
210         * @param name the string representation of the constant
211         **/
212        private FileUse(String name) {
213
214            m_name = name;
215        }
216
217        /**
218         * Parses a string into an enumeration element.<p>
219         *
220         * @param name the name of the element
221         *
222         * @return the element with the given name or <code>null</code> if not found
223         */
224        public static FileUse parse(String name) {
225
226            for (FileUse fileUse : FileUse.values()) {
227                if (fileUse.getName().equals(name)) {
228                    return fileUse;
229                }
230            }
231            return null;
232        }
233
234        /**
235         * Returns the name.<p>
236         *
237         * @return the name
238         */
239        public String getName() {
240
241            return m_name;
242        }
243    }
244
245    /** The log object for this class. */
246    private static final Log LOG = CmsLog.getLog(CmsJspTagProperty.class);
247
248    /** Serial version UID required for safe serialization. */
249    private static final long serialVersionUID = -4040833541258687977L;
250
251    /** The default value. */
252    private String m_defaultValue;
253
254    /** The locale for which the property should be read. */
255    private Locale m_locale;
256
257    /** Indicates if HTML should be escaped. */
258    private boolean m_escapeHtml;
259
260    /** The file to read the property from. */
261    private String m_propertyFile;
262
263    /** The name of the property to read. */
264    private String m_propertyName;
265
266    /**
267     * Internal action method.<p>
268     *
269     * @param action the search action
270     * @param req the current request
271     *
272     * @return String the value of the property or <code>null</code> if not found (and no defaultValue provided)
273     *
274     * @throws CmsException if something goes wrong
275     */
276    public static Map<String, String> propertiesTagAction(String action, ServletRequest req) throws CmsException {
277
278        CmsFlexController controller = CmsFlexController.getController(req);
279
280        // now read the property from the VFS
281        Map<String, String> value = new HashMap<String, String>();
282        CmsPropertyAction propertyAction = new CmsPropertyAction(req, action);
283        if (null != propertyAction.getVfsUri()) {
284            value = CmsProperty.toMap(
285                controller.getCmsObject().readPropertyObjects(propertyAction.getVfsUri(), propertyAction.isSearch()));
286        }
287        return value;
288    }
289
290    /**
291     * Internal action method.<p>
292     *
293     * @param property the property to look up
294     * @param action the search action
295     * @param defaultValue the default value
296     * @param escape if the result html should be escaped or not
297     * @param req the current request
298     *
299     * @return the value of the property or <code>null</code> if not found (and no defaultValue was provided)
300     *
301     * @throws CmsException if something goes wrong
302     */
303    public static String propertyTagAction(
304        String property,
305        String action,
306        String defaultValue,
307        boolean escape,
308        ServletRequest req)
309    throws CmsException {
310
311        return propertyTagAction(property, action, defaultValue, escape, req, null);
312    }
313
314    /**
315     * Internal action method.<p>
316     *
317     * @param property the property to look up
318     * @param action the search action
319     * @param defaultValue the default value
320     * @param escape if the result html should be escaped or not
321     * @param req the current request
322     * @param locale the locale for which the property should be read
323     *
324     * @return the value of the property or <code>null</code> if not found (and no defaultValue was provided)
325     *
326     * @throws CmsException if something goes wrong
327     */
328    public static String propertyTagAction(
329        String property,
330        String action,
331        String defaultValue,
332        boolean escape,
333        ServletRequest req,
334        Locale locale)
335    throws CmsException {
336
337        CmsFlexController controller = CmsFlexController.getController(req);
338        String value = null;
339        CmsPropertyAction propertyAction = new CmsPropertyAction(req, action);
340        if (null != propertyAction.getVfsUri()) {
341            value = controller.getCmsObject().readPropertyObject(
342                propertyAction.getVfsUri(),
343                property,
344                propertyAction.isSearch(),
345                locale).getValue();
346        }
347        if (value == null) {
348            value = defaultValue;
349        }
350        if (escape) {
351            // HTML escape the value
352            value = CmsEncoder.escapeHtml(value);
353        }
354        return value;
355    }
356
357    /**
358     * @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
359     */
360    @Override
361    public int doEndTag() {
362
363        release();
364        return EVAL_PAGE;
365    }
366
367    /**
368     * @return SKIP_BODY
369     * @see javax.servlet.jsp.tagext.Tag#doStartTag()
370     */
371    @Override
372    public int doStartTag() throws JspException {
373
374        ServletRequest req = pageContext.getRequest();
375
376        // This will always be true if the page is called through OpenCms
377        if (CmsFlexController.isCmsRequest(req)) {
378
379            try {
380                String prop = propertyTagAction(getName(), getFile(), m_defaultValue, m_escapeHtml, req, m_locale);
381                // Make sure that no null String is returned
382                if (prop == null) {
383                    prop = "";
384                }
385                pageContext.getOut().print(prop);
386
387            } catch (Exception ex) {
388                if (LOG.isErrorEnabled()) {
389                    LOG.error(Messages.get().getBundle().key(Messages.ERR_PROCESS_TAG_1, "property"), ex);
390                }
391                throw new javax.servlet.jsp.JspException(ex);
392            }
393        }
394        return SKIP_BODY;
395    }
396
397    /**
398     * Returns the default value.<p>
399     *
400     * @return the default value
401     */
402    public String getDefault() {
403
404        return m_defaultValue != null ? m_defaultValue : "";
405    }
406
407    /**
408     * The value of the escape html flag.<p>
409     *
410     * @return the value of the escape html flag
411     */
412    public String getEscapeHtml() {
413
414        return "" + m_escapeHtml;
415    }
416
417    /**
418     * Returns the file name.<p>
419     *
420     * @return the file name
421     */
422    public String getFile() {
423
424        return m_propertyFile != null ? m_propertyFile : "parent";
425    }
426
427    /**
428     * Returns the property name.<p>
429     *
430     * @return String the property name
431     */
432    public String getName() {
433
434        return m_propertyName != null ? m_propertyName : "";
435    }
436
437    /**
438     * @see javax.servlet.jsp.tagext.Tag#release()
439     */
440    @Override
441    public void release() {
442
443        super.release();
444        m_propertyFile = null;
445        m_propertyName = null;
446        m_defaultValue = null;
447        m_escapeHtml = false;
448    }
449
450    /**
451     * Sets the default value.<p>
452     *
453     * This is used if a selected property is not found.<p>
454     *
455     * @param def the default value
456     */
457    public void setDefault(String def) {
458
459        if (def != null) {
460            m_defaultValue = def;
461        }
462    }
463
464    /**
465     * Set the escape html flag.<p>
466     *
467     * @param value should be <code>"true"</code> or <code>"false"</code> (all values other then <code>"true"</code> are
468     * considered to be false)
469     */
470    public void setEscapeHtml(String value) {
471
472        if (value != null) {
473            m_escapeHtml = Boolean.valueOf(value.trim()).booleanValue();
474        }
475    }
476
477    /**
478     * Sets the file name.<p>
479     *
480     * @param file the file name
481     */
482    public void setFile(String file) {
483
484        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(file)) {
485            m_propertyFile = file;
486        }
487    }
488
489    /**
490     * Sets the locale for which the property should be read.
491     *
492     * @param locale the locale for which the property should be read.
493     */
494    public void setLocale(String locale) {
495
496        try {
497            m_locale = LocaleUtils.toLocale(locale);
498        } catch (IllegalArgumentException e) {
499            LOG.error(Messages.get().getBundle().key(Messages.ERR_TAG_INVALID_LOCALE_1, "cms:property"), e);
500            m_locale = null;
501        }
502    }
503
504    /**
505     * Sets the property name.<p>
506     *
507     * @param name the property name to set
508     */
509    public void setName(String name) {
510
511        if (name != null) {
512            m_propertyName = name;
513        }
514    }
515
516}