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