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