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