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