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.ade.contenteditor.CmsContentService;
031import org.opencms.file.CmsObject;
032import org.opencms.gwt.shared.CmsGwtConstants;
033import org.opencms.i18n.CmsLocaleManager;
034import org.opencms.json.JSONException;
035import org.opencms.main.CmsException;
036import org.opencms.util.CmsCollectionsGenericWrapper;
037import org.opencms.util.CmsConstantMap;
038import org.opencms.util.CmsMacroResolver;
039import org.opencms.util.CmsStringUtil;
040import org.opencms.xml.CmsXmlUtils;
041import org.opencms.xml.I_CmsXmlDocument;
042import org.opencms.xml.content.CmsXmlContent;
043import org.opencms.xml.types.I_CmsXmlContentValue;
044import org.opencms.xml.xml2json.CmsXmlContentTree;
045import org.opencms.xml.xml2json.renderer.CmsJsonRendererXmlContent;
046
047import java.util.ArrayList;
048import java.util.Collections;
049import java.util.HashMap;
050import java.util.Iterator;
051import java.util.List;
052import java.util.Locale;
053import java.util.Map;
054
055import org.apache.commons.collections.Transformer;
056
057import org.dom4j.Element;
058import org.dom4j.Node;
059
060/**
061 * Allows direct access to XML content values, with possible iteration of sub-nodes.<p>
062 *
063 * The implementation is optimized for performance and uses lazy initializing of the
064 * requested values as much as possible.<p>
065 *
066 * @since 7.0.2
067 *
068 * @see CmsJspContentAccessBean
069 * @see org.opencms.jsp.CmsJspTagContentAccess
070 */
071public final class CmsJspContentAccessValueWrapper extends A_CmsJspValueWrapper {
072
073    /**
074     * Provides a Map with Booleans that
075     * indicate if a nested sub value (xpath) for the current value is available in the XML content.<p>
076     */
077    public class CmsHasValueTransformer implements Transformer {
078
079        /**
080         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
081         */
082        @Override
083        public Object transform(Object input) {
084
085            return Boolean.valueOf(
086                getContentValue().getDocument().hasValue(createPath(input), getContentValue().getLocale()));
087        }
088    }
089
090    /**
091     * Provides a Map which lets the user a nested sub value from the current value,
092     * the input is assumed to be a String that represents an xpath in the XML content.<p>
093     */
094    public class CmsRdfaTransformer implements Transformer {
095
096        /**
097         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
098         */
099        @Override
100        public Object transform(Object input) {
101
102            if (isDirectEditEnabled(obtainCmsObject())) {
103                return CmsContentService.getRdfaAttributes(getContentValue(), String.valueOf(input));
104            } else {
105                return "";
106            }
107        }
108    }
109
110    /**
111     * Provides a Map which lets the user access nested sub value Lists directly below the current value,
112     * the input is assumed to be a String that represents an xpath in the XML content.<p>
113     */
114    public class CmsSubValueListTransformer implements Transformer {
115
116        /**
117         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
118         */
119        @Override
120        public Object transform(Object input) {
121
122            List<I_CmsXmlContentValue> values = getContentValue().getDocument().getSubValues(
123                createPath(input),
124                getContentValue().getLocale());
125            List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>();
126            Iterator<I_CmsXmlContentValue> i = values.iterator();
127            while (i.hasNext()) {
128                // must iterate values from XML content and create wrapper for each
129                I_CmsXmlContentValue value = i.next();
130                result.add(createWrapper(obtainCmsObject(), value, getContentValue(), value.getName()));
131            }
132            return result;
133        }
134    }
135
136    /**
137     * Provides a Map which lets the user access nested sub value Lists from the current value,
138     * the input is assumed to be a String that represents an xpath in the XML content.<p>
139     */
140    public class CmsValueListTransformer implements Transformer {
141
142        /**
143         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
144         */
145        @Override
146        public Object transform(Object input) {
147
148            List<I_CmsXmlContentValue> values = getContentValue().getDocument().getValues(
149                createPath(input),
150                getContentValue().getLocale());
151            List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>();
152            Iterator<I_CmsXmlContentValue> i = values.iterator();
153            while (i.hasNext()) {
154                // must iterate values from XML content and create wrapper for each
155                I_CmsXmlContentValue value = i.next();
156                result.add(createWrapper(obtainCmsObject(), value, getContentValue(), (String)input));
157            }
158            return result;
159        }
160    }
161
162    /**
163     * Provides a Map which returns a nested sub value from the current value.<p>
164     *
165     * The input is assumed to be a String that represents an xpath in the XML content.<p>
166     */
167    public class CmsValueTransformer implements Transformer {
168
169        /**
170         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
171         */
172        @Override
173        public Object transform(Object input) {
174
175            I_CmsXmlContentValue value = getContentValue().getDocument().getValue(
176                createPath(input),
177                getContentValue().getLocale());
178            return createWrapper(obtainCmsObject(), value, getContentValue(), (String)input);
179        }
180    }
181
182    /**
183     * Provides a Map which lets the user directly access sub-nodes of the XML represented by the current value,
184     * the input is assumed to be a String that represents an xpath in the XML content.<p>
185     */
186    public class CmsXmlValueTransformer implements Transformer {
187
188        /**
189         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
190         */
191        @Override
192        public Object transform(Object input) {
193
194            Node node = getContentValue().getElement().selectSingleNode(input.toString());
195            if (node != null) {
196                return node.getStringValue();
197            }
198            return "";
199        }
200    }
201
202    /**
203     * The null value info, used to generate RDFA and DND annotations for null values.<p>
204     */
205    protected static class NullValueInfo {
206
207        /** The content document. */
208        private I_CmsXmlDocument m_content;
209
210        /** The content locale. */
211        private Locale m_locale;
212
213        /** The parent value. */
214        private I_CmsXmlContentValue m_parentValue;
215
216        /** The value path name. */
217        private String m_valueName;
218
219        /**
220         * Constructor.<p>
221         *
222         * @param parentValue the parent value
223         * @param valueName the value path name
224         */
225        protected NullValueInfo(I_CmsXmlContentValue parentValue, String valueName) {
226
227            m_parentValue = parentValue;
228            m_valueName = valueName;
229        }
230
231        /**
232         * @param content the content document
233         * @param valueName the value path name
234         * @param locale the content locale
235         */
236        protected NullValueInfo(I_CmsXmlDocument content, String valueName, Locale locale) {
237
238            m_content = content;
239            m_valueName = valueName;
240            m_locale = locale;
241        }
242
243        /**
244         * Returns the content.<p>
245         *
246         * @return the content
247         */
248        public I_CmsXmlDocument getContent() {
249
250            return m_content;
251        }
252
253        /**
254         * Returns the locale.<p>
255         *
256         * @return the locale
257         */
258        public Locale getLocale() {
259
260            return m_locale;
261        }
262
263        /**
264         * Returns the parent value.<p>
265         *
266         * @return the parent value
267         */
268        public I_CmsXmlContentValue getParentValue() {
269
270            return m_parentValue;
271        }
272
273        /**
274         * Returns the value name.<p>
275         *
276         * @return the value name
277         */
278        public String getValueName() {
279
280            return m_valueName;
281        }
282
283    }
284
285    /** Constant for the null (non existing) value. */
286    protected static final CmsJspContentAccessValueWrapper NULL_VALUE_WRAPPER = new CmsJspContentAccessValueWrapper();
287
288    /** Attribute for used to cache the tree format of the XML content which is used to generate JSON. */
289    public static final String TEMP_XML2JSON_TREE = "xml2json.tree";
290
291    /** The wrapped XML content value. */
292    private I_CmsXmlContentValue m_contentValue;
293
294    /** Date series information generated from the wrapped data. */
295    private CmsJspDateSeriesBean m_dateSeries;
296
297    /** Calculated hash code. */
298    private int m_hashCode;
299
300    /** The lazy initialized Map that checks if a value is available. */
301    private Map<String, Boolean> m_hasValue;
302
303    /** The macro resolver used to resolve macros for this value. */
304    private CmsMacroResolver m_macroResolver;
305
306    /** The names of the sub elements. */
307    private List<String> m_names;
308
309    /** The null value info, used to generate RDFA and DND annotations for null values. */
310    private NullValueInfo m_nullValueInfo;
311
312    /** The current value transformed into a parameter map.*/
313    private Map<String, String> m_parameters;
314
315    /** The lazy initialized map of RDFA for nested sub values. */
316    private Map<String, String> m_rdfa;
317
318    /** The lazy initialized sub value list Map. */
319    private Map<String, List<CmsJspContentAccessValueWrapper>> m_subValueList;
320
321    /** The lazy initialized value Map. */
322    private Map<String, CmsJspContentAccessValueWrapper> m_value;
323
324    /** The lazy initialized value list Map. */
325    private Map<String, List<CmsJspContentAccessValueWrapper>> m_valueList;
326
327    /** The lazy initialized XML element Map. */
328    private Map<String, String> m_xml;
329
330    /**
331     * Private constructor, used for creation of NULL constant value, use factory method to create instances.<p>
332     *
333     * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String)
334     */
335    private CmsJspContentAccessValueWrapper() {
336
337        // cast needed to avoid compiler confusion with constructors
338        this((CmsObject)null, (I_CmsXmlContentValue)null);
339    }
340
341    /**
342     * Private constructor, use factory method to create instances.<p>
343     *
344     * Used to create a copy with macro resolving enabled.<p>
345     *
346     * @param base the wrapper base
347     * @param macroResolver the macro resolver to use
348     *
349     * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String)
350     */
351    private CmsJspContentAccessValueWrapper(CmsJspContentAccessValueWrapper base, CmsMacroResolver macroResolver) {
352
353        m_cms = base.m_cms;
354        m_contentValue = base.m_contentValue;
355        m_hashCode = base.m_hashCode;
356        m_hasValue = base.m_hasValue;
357        m_macroResolver = macroResolver;
358        m_value = base.m_value;
359        m_valueList = base.m_valueList;
360    }
361
362    /**
363     * Private constructor, use factory method to create instances.<p>
364     *
365     * @param cms the current users OpenCms context
366     * @param value the value to warp
367     *
368     * @see #createWrapper(CmsObject, I_CmsXmlContentValue,I_CmsXmlContentValue,String)
369     */
370    private CmsJspContentAccessValueWrapper(CmsObject cms, I_CmsXmlContentValue value) {
371
372        // a null value is used for constant generation
373        m_cms = cms;
374        m_contentValue = value;
375
376        if ((m_contentValue == null) || m_contentValue.isSimpleType()) {
377            // maps must all be static
378            m_hasValue = CmsConstantMap.CONSTANT_BOOLEAN_FALSE_MAP;
379            m_value = CmsJspContentAccessBean.CONSTANT_NULL_VALUE_WRAPPER_MAP;
380            m_valueList = CmsConstantMap.CONSTANT_EMPTY_LIST_MAP;
381        }
382    }
383
384    /**
385     * Factory method to create a new XML content value wrapper.<p>
386     *
387     * In case either parameter is <code>null</code>, the {@link #NULL_VALUE_WRAPPER} is returned.<p>
388     *
389     * @param cms the current users OpenCms context
390     * @param value the value to warp
391     * @param parentValue the parent value, required to set the null value info
392     * @param valueName the value path name
393     *
394     * @return a new content value wrapper instance, or <code>null</code> if any parameter is <code>null</code>
395     */
396    public static CmsJspContentAccessValueWrapper createWrapper(
397        CmsObject cms,
398        I_CmsXmlContentValue value,
399        I_CmsXmlContentValue parentValue,
400        String valueName) {
401
402        if ((value != null) && (cms != null)) {
403            return new CmsJspContentAccessValueWrapper(cms, value);
404        }
405        if ((parentValue != null) && (valueName != null) && (cms != null)) {
406            CmsJspContentAccessValueWrapper wrapper = new CmsJspContentAccessValueWrapper();
407            wrapper.m_nullValueInfo = new NullValueInfo(parentValue, valueName);
408            wrapper.m_cms = cms;
409            return wrapper;
410        }
411        // if no value is available,
412        return NULL_VALUE_WRAPPER;
413    }
414
415    /**
416     * Factory method to create a new XML content value wrapper.<p>
417     *
418     * In case either parameter is <code>null</code>, the {@link #NULL_VALUE_WRAPPER} is returned.<p>
419     *
420     * @param cms the current users OpenCms context
421     * @param value the value to warp
422     * @param content the content document, required to set the null value info
423     * @param valueName the value path name
424     * @param locale the selected locale
425     *
426     * @return a new content value wrapper instance, or <code>null</code> if any parameter is <code>null</code>
427     */
428    public static CmsJspContentAccessValueWrapper createWrapper(
429        CmsObject cms,
430        I_CmsXmlContentValue value,
431        I_CmsXmlDocument content,
432        String valueName,
433        Locale locale) {
434
435        if ((value != null) && (cms != null)) {
436            return new CmsJspContentAccessValueWrapper(cms, value);
437        }
438        if ((content != null) && (valueName != null) && (locale != null) && (cms != null)) {
439            CmsJspContentAccessValueWrapper wrapper = new CmsJspContentAccessValueWrapper();
440            wrapper.m_nullValueInfo = new NullValueInfo(content, valueName, locale);
441            wrapper.m_cms = cms;
442            return wrapper;
443        }
444        // if no value is available,
445        return NULL_VALUE_WRAPPER;
446    }
447
448    /**
449     * Returns if direct edit is enabled.<p>
450     *
451     * @param cms the current cms context
452     *
453     * @return <code>true</code> if direct edit is enabled
454     */
455    static boolean isDirectEditEnabled(CmsObject cms) {
456
457        return !cms.getRequestContext().getCurrentProject().isOnlineProject()
458            && (cms.getRequestContext().getAttribute(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT) == null);
459    }
460
461    /**
462     * Returns the wrapped content value.<p>
463     *
464     * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code><p>.
465     *
466     * @return the wrapped content value
467     */
468    public I_CmsXmlContentValue getContentValue() {
469
470        return m_contentValue;
471    }
472
473    /**
474     * Returns <code>true</code> in case this value actually exists in the XML content it was requested from.<p>
475     *
476     * Usage example on a JSP with the JSTL:<pre>
477     * &lt;cms:contentload ... &gt;
478     *     &lt;cms:contentaccess var="content" /&gt;
479     *     &lt;c:if test="${content.value['Link'].exists}" &gt;
480     *         The content has a "Link" value!
481     *     &lt;/c:if&gt;
482     * &lt;/cms:contentload&gt;</pre>
483     *
484     * @return <code>true</code> in case this value actually exists in the XML content it was requested from
485     */
486    @Override
487    public boolean getExists() {
488
489        return m_contentValue != null;
490    }
491
492    /**
493     * Returns a lazy initialized Map that provides Booleans that
494     * indicate if a nested sub value (xpath) for the current value is available in the XML content.<p>
495     *
496     * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p>
497     *
498     * In case the current value is not a nested XML content value, or the XML content value does not exist,
499     * the {@link CmsConstantMap#CONSTANT_BOOLEAN_FALSE_MAP} is returned.<p>
500     *
501     * Usage example on a JSP with the JSTL:<pre>
502     * &lt;cms:contentload ... &gt;
503     *     &lt;cms:contentaccess var="content" /&gt;
504     *     &lt;c:if test="${content.value['Link'].hasValue['Description']}" &gt;
505     *         The content has a "Description" value as sub element to the "Link" value!
506     *     &lt;/c:if&gt;
507     * &lt;/cms:contentload&gt;</pre>
508     *
509     * Please note that you can also test if a sub-value exists like this:<pre>
510     * &lt;c:if test="${content.value['Link'].value['Description'].exists}" &gt; ... &lt;/c:if&gt;</pre>
511     *
512     * @return a lazy initialized Map that provides Booleans that
513     *      indicate if a sub value (xpath) for the current value is available in the XML content
514     */
515    public Map<String, Boolean> getHasValue() {
516
517        if (m_hasValue == null) {
518            m_hasValue = CmsCollectionsGenericWrapper.createLazyMap(new CmsHasValueTransformer());
519        }
520        return m_hasValue;
521    }
522
523    /**
524     * Returns the annotation that enables image drag and drop for this content value.<p>
525     *
526     * Use to insert the annotation attributes into a HTML tag.<p>
527     *
528     * Only makes sense in case this node actually contains the path to an image.<p>
529     *
530     * Example using EL: &lt;span ${value.Image.imageDndAttr}&gt; ... &lt;/span&gt; will result in
531     * &lt;span data-imagednd="..."&gt; ... &lt;/span&gt;<p>
532     *
533     * @return the annotation that enables image drag and drop for this content value
534     */
535    public String getImageDndAttr() {
536
537        String result = "";
538        CmsObject cms = obtainCmsObject();
539
540        if ((cms != null)
541            && (m_contentValue != null)
542            && isDirectEditEnabled(cms)
543            && (m_contentValue.getDocument().getFile() != null)) {
544            result = CmsJspContentAccessBean.createImageDndAttr(
545                m_contentValue.getDocument().getFile().getStructureId(),
546                m_contentValue.getPath(),
547                String.valueOf(m_contentValue.getLocale()));
548        }
549
550        return result;
551    }
552
553    /**
554     * Returns the node index of the XML content value in the source XML document,
555     * starting with 0.<p>
556     *
557     * In case the XML content value does not exist, <code>-1</code> is returned.<p>
558     *
559     * Usage example on a JSP with the JSTL:<pre>
560     * &lt;cms:contentload ... &gt;
561     *     &lt;cms:contentaccess var="content" /&gt;
562     *     The locale of the Link node: ${content.value['Link'].locale}
563     * &lt;/cms:contentload&gt;</pre>
564     *
565     * @return the locale of the current XML content value
566     */
567    public int getIndex() {
568
569        if (m_contentValue == null) {
570            return -1;
571        }
572        return m_contentValue.getIndex();
573    }
574
575    /**
576     * Returns <code>true</code> in case the value is empty, that is either <code>null</code> or an empty String.<p>
577     *
578     * In case the XML content value does not exist, <code>true</code> is returned.<p>
579     *
580     * Usage example on a JSP with the JSTL:<pre>
581     * &lt;cms:contentload ... &gt;
582     *     &lt;cms:contentaccess var="content" /&gt;
583     *     &lt;c:if test="${content.value['Link'].isEmpty}" &gt;
584     *         The content of the "Link" value is empty.
585     *     &lt;/c:if&gt;
586     * &lt;/cms:contentload&gt;</pre>
587     *
588     * @return <code>true</code> in case the value is empty
589     */
590    @Override
591    public boolean getIsEmpty() {
592
593        if (m_contentValue == null) {
594            // this is the case for non existing values
595            return true;
596        }
597        if (m_contentValue.isSimpleType()) {
598            // return values for simple type
599            return CmsStringUtil.isEmpty(m_contentValue.getStringValue(m_cms));
600        } else {
601            // nested types are not empty if they have any children in the XML
602            return m_contentValue.getElement().elements().size() > 0;
603        }
604    }
605
606    /**
607     * Returns <code>true</code> in case the value is empty or whitespace only,
608     * that is either <code>null</code> or String that contains only whitespace chars.<p>
609     *
610     * In case the XML content value does not exist, <code>true</code> is returned.<p>
611     *
612     * Usage example on a JSP with the JSTL:<pre>
613     * &lt;cms:contentload ... &gt;
614     *     &lt;cms:contentaccess var="content" /&gt;
615     *     &lt;c:if test="${content.value['Link'].isEmptyOrWhitespaceOnly}" &gt;
616     *         The content of the "Link" value is empty or contains only whitespace chars.
617     *     &lt;/c:if&gt;
618     * &lt;/cms:contentload&gt;</pre>
619     *
620     * @return <code>true</code> in case the value is empty or whitespace only
621     */
622    @Override
623    public boolean getIsEmptyOrWhitespaceOnly() {
624
625        if (m_contentValue == null) {
626            // this is the case for non existing values
627            return true;
628        }
629        if (m_contentValue.isSimpleType()) {
630            // return values for simple type
631            return CmsStringUtil.isEmptyOrWhitespaceOnly(m_contentValue.getStringValue(m_cms));
632        } else {
633            // nested types are not empty if they have any children in the XML
634            return m_contentValue.getElement().elements().isEmpty();
635        }
636    }
637
638    /**
639     * Gets the default JSON representation of a value.
640     *
641     * @return the default JSON representation of the value
642     */
643    public CmsJspJsonWrapper getJson() throws CmsException {
644
645        if (!getExists()) {
646            return new CmsJspJsonWrapper(null);
647        }
648        CmsXmlContent content = (CmsXmlContent)m_contentValue.getDocument();
649        CmsXmlContentTree tree = (CmsXmlContentTree)content.getTempDataCache().get(TEMP_XML2JSON_TREE);
650        CmsXmlContentTree.Node node = null;
651        if (tree == null) {
652            tree = new CmsXmlContentTree(content, getLocale());
653            content.getTempDataCache().put(TEMP_XML2JSON_TREE, tree);
654        }
655        node = tree.getNodeForValue(m_contentValue);
656        if (node == null) {
657            return new CmsJspJsonWrapper(null);
658        }
659        CmsJsonRendererXmlContent renderer = new CmsJsonRendererXmlContent(getCmsObject());
660        try {
661            Object jsonObj = renderer.renderNode(node);
662            return new CmsJspJsonWrapper(jsonObj);
663        } catch (JSONException e) {
664            throw new RuntimeException(e);
665        }
666    }
667
668    /**
669     * Returns the Locale of the current XML content value.<p>
670     *
671     * In case the XML content value does not exist, the OpenCms system default Locale is returned.<p>
672     *
673     * Usage example on a JSP with the JSTL:<pre>
674     * &lt;cms:contentload ... &gt;
675     *     &lt;cms:contentaccess var="content" /&gt;
676     *     The locale of the Link node: ${content.value['Link'].locale}
677     * &lt;/cms:contentload&gt;</pre>
678     *
679     * @return the locale of the current XML content value
680     */
681    public Locale getLocale() {
682
683        if (m_contentValue == null) {
684            return CmsLocaleManager.getDefaultLocale();
685        }
686        return m_contentValue.getLocale();
687    }
688
689    /**
690     * Returns the xml node name of the wrapped content value.<p>
691     *
692     * @return the xml node name
693     *
694     * @see org.opencms.xml.types.I_CmsXmlSchemaType#getName()
695     */
696    public String getName() {
697
698        if (m_contentValue == null) {
699            return null;
700        }
701        return m_contentValue.getName();
702    }
703
704    /**
705     * Returns a list that provides the names of all nested sub values
706     * directly below the current value from the XML content, including the index.<p>
707     *
708     * Usage example on a JSP with the JSTL:<pre>
709     * &lt;cms:contentload ... &gt;
710     *     &lt;cms:contentaccess var="content" /&gt;
711     *     &lt;c:forEach items="${content.value['Items'].names}" var="elem"&gt;
712     *         &lt;c:out value="${elem}" /&gt;
713     *     &lt;/c:forEach&gt;
714     * &lt;/cms:contentload&gt;</pre>
715     *
716     * @return a list with all available elements paths (Strings) available directly below this element
717     */
718    public List<String> getNames() {
719
720        if ((m_names == null)) {
721            m_names = new ArrayList<String>();
722            if (!m_contentValue.isSimpleType()) {
723                for (I_CmsXmlContentValue value : m_contentValue.getDocument().getSubValues(getPath(), getLocale())) {
724                    m_names.add(CmsXmlUtils.createXpathElement(value.getName(), value.getXmlIndex() + 1));
725                }
726            }
727        }
728        return m_names;
729    }
730
731    /**
732     * @see org.opencms.jsp.util.A_CmsJspValueWrapper#getObjectValue()
733     */
734    @Override
735    public Object getObjectValue() {
736
737        return m_contentValue;
738    }
739
740    /**
741     * Returns the path to the current XML content value.<p>
742     *
743     * In case the XML content value does not exist, an empty String <code>""</code> is returned.<p>
744     *
745     * Usage example on a JSP with the JSTL:<pre>
746     * &lt;cms:contentload ... &gt;
747     *     &lt;cms:contentaccess var="content" /&gt;
748     *     The path to the Link node in the XML: ${content.value['Link'].path}
749     * &lt;/cms:contentload&gt;</pre>
750     *
751     * @return the path to the current XML content value
752     */
753    public String getPath() {
754
755        if (m_contentValue == null) {
756            return "";
757        }
758        return m_contentValue.getPath();
759    }
760
761    /**
762     * Returns a lazy initialized Map that provides the RDFA for nested sub values.<p>
763     *
764     * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p>
765     *
766     * @return a lazy initialized Map that provides the RDFA for nested sub values
767     */
768    public Map<String, String> getRdfa() {
769
770        if (m_rdfa == null) {
771            m_rdfa = CmsCollectionsGenericWrapper.createLazyMap(new CmsRdfaTransformer());
772        }
773        return m_rdfa;
774    }
775
776    /**
777     * Returns the RDF annotation to this content value.<p>
778     *
779     * Use to insert the annotation attributes into a HTML tag.<p>
780     * Example using EL: &lt;h1 ${value.Title.rdfaAttr}&gt;${value.Title}&lt;/h1&gt; will result in
781     * &lt;h1 data-oc-id="..." data-oc-field="..."&gt;My title&lt;/h1&gt;<p>
782     *
783     * @return the RDFA
784     */
785    public String getRdfaAttr() {
786
787        String result = "";
788        CmsObject cms = obtainCmsObject();
789        if (cms != null) {
790            if (isDirectEditEnabled(cms)) {
791                if (m_contentValue != null) {
792                    // within the offline project return the OpenCms specific entity id's and property names
793                    result = CmsContentService.getRdfaAttributes(m_contentValue);
794                } else if ((m_nullValueInfo != null)) {
795                    if (m_nullValueInfo.getParentValue() != null) {
796                        result = CmsContentService.getRdfaAttributes(
797                            m_nullValueInfo.getParentValue(),
798                            m_nullValueInfo.getValueName());
799                    } else if (m_nullValueInfo.getContent() != null) {
800                        result = CmsContentService.getRdfaAttributes(
801                            m_nullValueInfo.getContent(),
802                            m_nullValueInfo.getLocale(),
803                            m_nullValueInfo.getValueName());
804                    }
805                }
806            } else {
807                // TODO: return mapped property names etc. when online
808            }
809        }
810        return result;
811    }
812
813    /**
814     * Short form of {@link #getResolveMacros()}.<p>
815     *
816     * @return a value wrapper with macro resolving turned on
817     *
818     * @see #getResolveMacros()
819     */
820    public CmsJspContentAccessValueWrapper getResolve() {
821
822        return getResolveMacros();
823    }
824
825    /**
826     * Turn on macro resolving for the wrapped value.<p>
827     *
828     * Macro resolving is turned off by default.
829     * When turned on, a macro resolver is initialized with
830     * the current OpenCms user context and the URI of the current resource.
831     * This means known macros contained in the wrapped value will be resolved when the output String is generated.
832     * For example, a <code>%(property.Title)</code> in the value would be replaced with the
833     * value of the title property. Macros that can not be resolved will be kept.<p>
834     *
835     * Usage example on a JSP with the JSTL:<pre>
836     * &lt;cms:contentload ... &gt;
837     *     &lt;cms:contentaccess var="content" /&gt;
838     *     The text with macros resolved: ${content.value['Text'].resolveMacros}
839     * &lt;/cms:contentload&gt;</pre>
840     *
841     * @return a value wrapper with macro resolving turned on
842     *
843     * @see CmsMacroResolver
844     */
845    public CmsJspContentAccessValueWrapper getResolveMacros() {
846
847        if (m_macroResolver == null) {
848            CmsMacroResolver macroResolver = CmsMacroResolver.newInstance();
849            macroResolver.setCmsObject(m_cms);
850            macroResolver.setKeepEmptyMacros(true);
851            return new CmsJspContentAccessValueWrapper(this, macroResolver);
852        }
853        // macro resolving is already turned on
854        return this;
855    }
856
857    /**
858     * Returns a lazy initialized Map that provides the Lists of sub values directly below
859     * the current value from the XML content.<p>
860     *
861     * The provided Map key is assumed to be a String that represents the relative xpath to the value.
862     * Use this method in case you want to iterate over a List of sub values from the XML content.<p>
863     *
864     * In case the current value is not a nested XML content value, or the XML content value does not exist,
865     * the {@link CmsConstantMap#CONSTANT_EMPTY_LIST_MAP} is returned.<p>
866     *
867     * Usage example on a JSP with the JSTL:<pre>
868     * &lt;cms:contentload ... &gt;
869     *     &lt;cms:contentaccess var="content" /&gt;
870     *     &lt;c:forEach var="desc" items="${content.value['Link'].subValueList['Description']}"&gt;
871     *         ${desc}
872     *     &lt;/c:forEach&gt;
873     * &lt;/cms:contentload&gt;</pre>
874     *
875     * @return a lazy initialized Map that provides a Lists of direct sub values of the current value from the XML content
876     */
877    public Map<String, List<CmsJspContentAccessValueWrapper>> getSubValueList() {
878
879        if (m_subValueList == null) {
880            m_subValueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsSubValueListTransformer());
881        }
882        return m_subValueList;
883    }
884
885    /**
886     * Converts a date series configuration to a date series bean.
887     * @return the date series bean.
888     */
889    public CmsJspDateSeriesBean getToDateSeries() {
890
891        if (m_dateSeries == null) {
892            m_dateSeries = new CmsJspDateSeriesBean(this, m_cms.getRequestContext().getLocale());
893        }
894        return m_dateSeries;
895    }
896
897    /**
898     * Transforms the current value into a parameter map.<p>
899     *
900     * The current value must be a nested content with sub-values that
901     * have exactly 2 values each, for example like this:<p>
902     *
903     * <pre>
904     * &lt;Parameters&gt;
905     *   &lt;Key&gt;&lt;foo&gt;&lt;/Key&gt;
906     *   &lt;Value&gt;&lt;bar&gt;&lt;/Value&gt;
907     * &lt;/Parameters&gt;
908     * &lt;Parameters&gt;
909     *   &lt;Key&gt;&lt;foo2&gt;&lt;/Key&gt;
910     *   &lt;Value&gt;&lt;bar2&gt;&lt;/Value&gt;
911     * &lt;/Parameters&gt;
912     * </pre>
913     *
914     * Please note that the result Map is a simple String map,
915     * NOT a map with {@link CmsJspContentAccessValueWrapper} classes.<p>
916     *
917     * @return the current value transformed into a parameter map
918     */
919    public Map<String, String> getToParameters() {
920
921        if (m_parameters == null) {
922            I_CmsXmlContentValue xmlvalue = getContentValue();
923            if (xmlvalue != null) {
924                if (!getContentValue().isSimpleType()) {
925                    // this is a nested content, get the list of values
926                    List<I_CmsXmlContentValue> parameters = xmlvalue.getDocument().getValues(
927                        xmlvalue.getPath(),
928                        xmlvalue.getLocale());
929                    m_parameters = new HashMap<String, String>(parameters.size());
930                    for (I_CmsXmlContentValue params : parameters) {
931                        // iterate all elements in this value list
932                        List<Element> children = params.getElement().elements();
933                        if (children.size() == 2) {
934                            String key = children.get(0).getText();
935                            String value = children.get(1).getText();
936                            m_parameters.put(key, value);
937                        }
938                    }
939                }
940            }
941            if (m_parameters == null) {
942                m_parameters = Collections.EMPTY_MAP;
943            }
944        }
945        return m_parameters;
946    }
947
948    /**
949     * Returns the schema type name of the wrapped content value.<p>
950     *
951     * @return the type name
952     *
953     * @see org.opencms.xml.types.I_CmsXmlSchemaType#getTypeName()
954     */
955    public String getTypeName() {
956
957        if (m_contentValue != null) {
958            return m_contentValue.getTypeName();
959        }
960        return null;
961    }
962
963    /**
964     * Returns a lazy initialized Map that provides the nested sub values
965     * for the current value from the XML content.<p>
966     *
967     * The provided Map key is assumed to be a String that represents the relative xpath to the value.<p>
968     *
969     * In case the current value is not a nested XML content value, or the XML content value does not exist,
970     * the {@link CmsJspContentAccessBean#CONSTANT_NULL_VALUE_WRAPPER_MAP} is returned.<p>
971     *
972     * Usage example on a JSP with the JSTL:<pre>
973     * &lt;cms:contentload ... &gt;
974     *     &lt;cms:contentaccess var="content" /&gt;
975     *     The Link Description: ${content.value['Link'].value['Description']}
976     * &lt;/cms:contentload&gt;</pre>
977     *
978     * Please note that this example will only work if the 'Link' element is mandatory in the schema definition
979     * of the XML content.<p>
980     *
981     * @return a lazy initialized Map that provides a sub value for the current value from the XML content
982     */
983    public Map<String, CmsJspContentAccessValueWrapper> getValue() {
984
985        if (m_value == null) {
986            m_value = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueTransformer());
987        }
988        return m_value;
989    }
990
991    /**
992     * Returns a lazy initialized Map that provides the Lists of nested sub values
993     * for the current value from the XML content.<p>
994     *
995     * The provided Map key is assumed to be a String that represents the relative xpath to the value.
996     * Use this method in case you want to iterate over a List of values form the XML content.<p>
997     *
998     * In case the current value is not a nested XML content value, or the XML content value does not exist,
999     * the {@link CmsConstantMap#CONSTANT_EMPTY_LIST_MAP} is returned.<p>
1000     *
1001     * Usage example on a JSP with the JSTL:<pre>
1002     * &lt;cms:contentload ... &gt;
1003     *     &lt;cms:contentaccess var="content" /&gt;
1004     *     &lt;c:forEach var="desc" items="${content.value['Link'].valueList['Description']}"&gt;
1005     *         ${desc}
1006     *     &lt;/c:forEach&gt;
1007     * &lt;/cms:contentload&gt;</pre>
1008     *
1009     * @return a lazy initialized Map that provides a Lists of sub values for the current value from the XML content
1010     */
1011    public Map<String, List<CmsJspContentAccessValueWrapper>> getValueList() {
1012
1013        if (m_valueList == null) {
1014            m_valueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueListTransformer());
1015        }
1016        return m_valueList;
1017    }
1018
1019    /**
1020     * Returns a lazy initialized Map that provides direct access to the XML element
1021     * for the current value from the XML content.<p>
1022     *
1023     * @return a lazy initialized Map that provides direct access to the XML element for the current value from the XML content
1024     */
1025    public Map<String, String> getXmlText() {
1026
1027        if (m_xml == null) {
1028            m_xml = CmsCollectionsGenericWrapper.createLazyMap(new CmsXmlValueTransformer());
1029        }
1030        return m_xml;
1031    }
1032
1033    /**
1034     * The hash code is created from the file structure id of the underlying XML content,
1035     * the selected locale and the path to the node in the XML content.
1036     *
1037     * @see java.lang.Object#hashCode()
1038     */
1039    @Override
1040    public int hashCode() {
1041
1042        if (m_contentValue == null) {
1043            return 0;
1044        }
1045        if (m_hashCode == 0) {
1046            StringBuffer result = new StringBuffer(64);
1047            result.append(m_contentValue.getDocument().getFile().getStructureId().toString());
1048            result.append('/');
1049            result.append(m_contentValue.getLocale());
1050            result.append('/');
1051            result.append(m_contentValue.getPath());
1052            m_hashCode = result.toString().hashCode();
1053        }
1054        return m_hashCode;
1055    }
1056
1057    /**
1058     * Returns the wrapped OpenCms user context.<p>
1059     *
1060     * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code>.
1061     *
1062     * @deprecated use {@link #getCmsObject()} instead
1063     *
1064     * @return the wrapped OpenCms user context
1065     */
1066    @Deprecated
1067    public CmsObject obtainCmsObject() {
1068
1069        return m_cms;
1070    }
1071
1072    /**
1073     * Returns the wrapped content value.<p>
1074     *
1075     * Note that this will return <code>null</code> when {@link #getExists()} returns <code>false</code><p>.
1076     *
1077     * Method name does not start with "get" to prevent using it in the expression language.<p>
1078     *
1079     * @return the wrapped content value
1080     *
1081     * @deprecated use {@link #getContentValue()} instead
1082     */
1083    @Deprecated
1084    public I_CmsXmlContentValue obtainContentValue() {
1085
1086        return m_contentValue;
1087    }
1088
1089    /**
1090     * @see java.lang.Object#toString()
1091     * @see #getToString()
1092     */
1093    @Override
1094    public String toString() {
1095
1096        if (m_contentValue == null) {
1097            // this is the case for non existing values
1098            return "";
1099        }
1100        if (m_contentValue.isSimpleType()) {
1101            // return values for simple type
1102            String value = m_contentValue.getStringValue(m_cms);
1103            if (m_macroResolver == null) {
1104                // no macro resolving
1105                return value;
1106            } else {
1107                // resolve macros first
1108                return m_macroResolver.resolveMacros(value);
1109            }
1110        } else {
1111            // nested types should not be called this way by the user
1112            return "";
1113        }
1114    }
1115
1116    /**
1117     * Returns the path to the XML content based on the current element path.<p>
1118     *
1119     * This is used to create xpath information for sub-elements in the transformers.<p>
1120     *
1121     * @param input the additional path that is appended to the current path
1122     *
1123     * @return the path to the XML content based on the current element path
1124     */
1125    protected String createPath(Object input) {
1126
1127        return CmsXmlUtils.concatXpath(m_contentValue.getPath(), String.valueOf(input));
1128    }
1129}