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.CmsFile;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProject;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.file.types.CmsResourceTypeXmlPage;
037import org.opencms.i18n.CmsEncoder;
038import org.opencms.i18n.CmsLocaleManager;
039import org.opencms.jsp.CmsJspResourceWrapper;
040import org.opencms.lock.CmsLock;
041import org.opencms.main.CmsException;
042import org.opencms.main.CmsRuntimeException;
043import org.opencms.main.OpenCms;
044import org.opencms.security.CmsPermissionSet;
045import org.opencms.util.CmsCollectionsGenericWrapper;
046import org.opencms.util.CmsConstantMap;
047import org.opencms.util.CmsUUID;
048import org.opencms.xml.I_CmsXmlDocument;
049import org.opencms.xml.content.CmsXmlContentFactory;
050import org.opencms.xml.page.CmsXmlPageFactory;
051import org.opencms.xml.types.I_CmsXmlContentValue;
052
053import java.util.ArrayList;
054import java.util.Collections;
055import java.util.Comparator;
056import java.util.Iterator;
057import java.util.List;
058import java.util.Locale;
059import java.util.Map;
060
061import org.apache.commons.collections.Transformer;
062
063/**
064 * Allows access to the individual elements of an XML content, usually used inside a loop of a
065 * <code>&lt;cms:contentload&gt;</code> tag.<p>
066 *
067 * The implementation is optimized for performance and uses lazy initializing of the
068 * requested values as much as possible.<p>
069 *
070 * @since 7.0.2
071 *
072 * @see org.opencms.jsp.CmsJspTagContentAccess
073 */
074public class CmsJspContentAccessBean {
075
076    /**
077     * Provides Booleans that indicate if a specified locale is available in the XML content,
078     * the input is assumed to be a String that represents a Locale.<p>
079     */
080    public class CmsHasLocaleTransformer implements Transformer {
081
082        /**
083         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
084         */
085        public Object transform(Object input) {
086
087            return Boolean.valueOf(getRawContent().hasLocale(CmsJspElFunctions.convertLocale(input)));
088        }
089    }
090
091    /**
092     * Provides Booleans that indicate if a specified path exists in the XML content,
093     * the input is assumed to be a String that represents an xpath in the XML content.<p>
094     */
095    public class CmsHasLocaleValueTransformer implements Transformer {
096
097        /**
098         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
099         */
100        public Object transform(Object input) {
101
102            Locale locale = CmsJspElFunctions.convertLocale(input);
103            Map<String, Boolean> result;
104            if (getRawContent().hasLocale(locale)) {
105                result = CmsCollectionsGenericWrapper.createLazyMap(new CmsHasValueTransformer(locale));
106            } else {
107                result = CmsConstantMap.CONSTANT_BOOLEAN_FALSE_MAP;
108            }
109            return result;
110        }
111    }
112
113    /**
114     * Provides a Map with Booleans that indicate if a specified path exists in the XML content in the selected Locale,
115     * the input is assumed to be a String that represents an xpath in the XML content.<p>
116     */
117    public class CmsHasValueTransformer implements Transformer {
118
119        /** The selected locale. */
120        private Locale m_selectedLocale;
121
122        /**
123         * Constructor with a locale.<p>
124         *
125         * @param locale the locale to use
126         */
127        public CmsHasValueTransformer(Locale locale) {
128
129            m_selectedLocale = locale;
130        }
131
132        /**
133         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
134         */
135        public Object transform(Object input) {
136
137            return Boolean.valueOf(getRawContent().hasValue(String.valueOf(input), m_selectedLocale));
138        }
139    }
140
141    /**
142     * Transformer used for the 'imageDnd' EL attribute which is used to annotate images which can be replaced by drag and drop.<p>
143     */
144    public class CmsImageDndTransformer implements Transformer {
145
146        /**
147         * Creates a new instance.<p>
148         */
149        public CmsImageDndTransformer() {
150
151            // do nothing
152
153        }
154
155        /**
156         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
157         */
158        public Object transform(Object input) {
159
160            String result;
161            if (CmsJspContentAccessValueWrapper.isDirectEditEnabled(getCmsObject())) {
162                result = createImageDndAttr(
163                    getRawContent().getFile().getStructureId(),
164                    String.valueOf(input),
165                    String.valueOf(getLocale()));
166            } else {
167                result = "";
168            }
169            return result;
170        }
171    }
172
173    /**
174     * Provides a Map which lets the user access the list of element names from the selected locale in an XML content,
175     * the input is assumed to be a String that represents a Locale.<p>
176     */
177    public class CmsLocaleNamesTransformer implements Transformer {
178
179        /**
180         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
181         */
182        public Object transform(Object input) {
183
184            Locale locale = CmsLocaleManager.getLocale(String.valueOf(input));
185
186            return getRawContent().getNames(locale);
187        }
188    }
189
190    /**
191     * Provides a Map which lets the user access the RDFA tags for all values in the selected locale in an XML content,
192     * the input is assumed to be a String that represents a Locale.<p>
193     */
194    public class CmsLocaleRdfaTransformer implements Transformer {
195
196        /**
197         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
198         */
199        public Object transform(Object input) {
200
201            Locale locale = CmsLocaleManager.getLocale(String.valueOf(input));
202            Map<String, String> result;
203            if (getRawContent().hasLocale(locale)) {
204                result = CmsCollectionsGenericWrapper.createLazyMap(new CmsRdfaTransformer(locale));
205            } else {
206                // return a map that always returns an empty string
207                result = CmsConstantMap.CONSTANT_EMPTY_STRING_MAP;
208            }
209            return result;
210        }
211    }
212
213    /**
214     * Provides a Map which lets the user access sub value Lists from the selected locale in an XML content,
215     * the input is assumed to be a String that represents a Locale.<p>
216     */
217    public class CmsLocaleSubValueListTransformer implements Transformer {
218
219        /**
220         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
221         */
222        public Object transform(Object input) {
223
224            Locale locale = CmsJspElFunctions.convertLocale(input);
225            Map<String, List<CmsJspContentAccessValueWrapper>> result;
226            if (getRawContent().hasLocale(locale)) {
227                result = CmsCollectionsGenericWrapper.createLazyMap(new CmsSubValueListTransformer(locale));
228            } else {
229                result = CmsConstantMap.CONSTANT_EMPTY_LIST_MAP;
230            }
231            return result;
232        }
233    }
234
235    /**
236     * Provides a Map which lets the user access value Lists from the selected locale in an XML content,
237     * the input is assumed to be a String that represents a Locale.<p>
238     */
239    public class CmsLocaleValueListTransformer implements Transformer {
240
241        /**
242         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
243         */
244        public Object transform(Object input) {
245
246            Locale locale = CmsJspElFunctions.convertLocale(input);
247            Map<String, List<CmsJspContentAccessValueWrapper>> result;
248            if (getRawContent().hasLocale(locale)) {
249                result = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueListTransformer(locale));
250            } else {
251                result = CmsConstantMap.CONSTANT_EMPTY_LIST_MAP;
252            }
253            return result;
254        }
255    }
256
257    /**
258     * Provides a Map which lets the user access a value from the selected locale in an XML content,
259     * the input is assumed to be a String that represents a Locale.<p>
260     */
261    public class CmsLocaleValueTransformer implements Transformer {
262
263        /**
264         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
265         */
266        public Object transform(Object input) {
267
268            Locale locale = CmsLocaleManager.getLocale(String.valueOf(input));
269            Map<String, CmsJspContentAccessValueWrapper> result;
270            if (getRawContent().hasLocale(locale)) {
271                result = CmsCollectionsGenericWrapper.createLazyMap(new CmsValueTransformer(locale));
272            } else {
273                result = CONSTANT_NULL_VALUE_WRAPPER_MAP;
274            }
275            return result;
276        }
277    }
278
279    /**
280     * Provides a Map which lets the user access the RDFA tag for a value in an XML content,
281     * the input is assumed to be a String that represents an xpath in the XML content.<p>
282     */
283    public class CmsRdfaTransformer implements Transformer {
284
285        /** The selected locale. */
286        private Locale m_selectedLocale;
287
288        /**
289         * Constructor with a locale.<p>
290         *
291         * @param locale the locale to use
292         */
293        public CmsRdfaTransformer(Locale locale) {
294
295            m_selectedLocale = locale;
296        }
297
298        /**
299         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
300         */
301        public Object transform(Object input) {
302
303            if (CmsJspContentAccessValueWrapper.isDirectEditEnabled(getCmsObject())) {
304                return CmsContentService.getRdfaAttributes(getRawContent(), m_selectedLocale, String.valueOf(input));
305            } else {
306                return "";
307            }
308        }
309    }
310
311    /**
312     * Provides a Map which lets the user access sub value Lists in an XML content,
313     * the input is assumed to be a String that represents an xpath in the XML content.<p>
314     */
315    public class CmsSubValueListTransformer implements Transformer {
316
317        /** The selected locale. */
318        private Locale m_selectedLocale;
319
320        /**
321         * Constructor with a locale.<p>
322         *
323         * @param locale the locale to use
324         */
325        public CmsSubValueListTransformer(Locale locale) {
326
327            m_selectedLocale = locale;
328        }
329
330        /**
331         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
332         */
333        public Object transform(Object input) {
334
335            List<I_CmsXmlContentValue> values = getRawContent().getSubValues(String.valueOf(input), m_selectedLocale);
336            List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>();
337            Iterator<I_CmsXmlContentValue> i = values.iterator();
338            while (i.hasNext()) {
339                // XML content API offers List of values only as Objects, must iterate them and create Strings
340                I_CmsXmlContentValue value = i.next();
341                result.add(CmsJspContentAccessValueWrapper.createWrapper(getCmsObject(), value, null, null));
342            }
343            return result;
344        }
345    }
346
347    /**
348     * Provides a Map which lets the user access value Lists in an XML content,
349     * the input is assumed to be a String that represents an xpath in the XML content.<p>
350     */
351    public class CmsValueListTransformer implements Transformer {
352
353        /** The selected locale. */
354        private Locale m_selectedLocale;
355
356        /**
357         * Constructor with a locale.<p>
358         *
359         * @param locale the locale to use
360         */
361        public CmsValueListTransformer(Locale locale) {
362
363            m_selectedLocale = locale;
364        }
365
366        /**
367         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
368         */
369        public Object transform(Object input) {
370
371            List<I_CmsXmlContentValue> values = getRawContent().getValues(String.valueOf(input), m_selectedLocale);
372            List<CmsJspContentAccessValueWrapper> result = new ArrayList<CmsJspContentAccessValueWrapper>();
373            Iterator<I_CmsXmlContentValue> i = values.iterator();
374            while (i.hasNext()) {
375                // XML content API offers List of values only as Objects, must iterate them and create Strings
376                I_CmsXmlContentValue value = i.next();
377                result.add(CmsJspContentAccessValueWrapper.createWrapper(getCmsObject(), value, null, null));
378            }
379            return result;
380        }
381    }
382
383    /**
384     * Provides a Map which lets the user access a value in an XML content,
385     * the input is assumed to be a String that represents an xpath in the XML content.<p>
386     */
387    public class CmsValueTransformer implements Transformer {
388
389        /** The selected locale. */
390        private Locale m_selectedLocale;
391
392        /**
393         * Constructor with a locale.<p>
394         *
395         * @param locale the locale to use
396         */
397        public CmsValueTransformer(Locale locale) {
398
399            m_selectedLocale = locale;
400        }
401
402        /**
403         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
404         */
405        public Object transform(Object input) {
406
407            I_CmsXmlContentValue value = getRawContent().getValue(String.valueOf(input), m_selectedLocale);
408            return CmsJspContentAccessValueWrapper.createWrapper(
409                getCmsObject(),
410                value,
411                getRawContent(),
412                (String)input,
413                m_selectedLocale);
414        }
415    }
416
417    /** Constant Map that always returns the {@link CmsJspContentAccessValueWrapper#NULL_VALUE_WRAPPER}.*/
418    protected static final Map<String, CmsJspContentAccessValueWrapper> CONSTANT_NULL_VALUE_WRAPPER_MAP = new CmsConstantMap<String, CmsJspContentAccessValueWrapper>(
419        CmsJspContentAccessValueWrapper.NULL_VALUE_WRAPPER);
420
421    /** The categories assigned to the resource. */
422    private CmsJspCategoryAccessBean m_categories;
423
424    /** The OpenCms context of the current user. */
425    private CmsObject m_cms;
426
427    /** The XML content to access. */
428    private I_CmsXmlDocument m_content;
429
430    /** Lazy map for the "has locale" check. */
431    private Map<String, Boolean> m_hasLocale;
432
433    /** Lazy map for the "has locale value" check. */
434    private Map<String, Map<String, Boolean>> m_hasLocaleValue;
435
436    /** Lazy map for imageDnd annotations. */
437    private Map<String, String> m_imageDnd;
438
439    /** The locale used for accessing entries from the XML content, this may be a fallback default locale. */
440    private Locale m_locale;
441
442    /** Lazy map with the locale names. */
443    private Map<String, List<String>> m_localeNames;
444
445    /** Lazy map of RDFA maps by locale. */
446    private Map<String, Map<String, String>> m_localeRdfa;
447
448    /** Lazy map with the locale sub value lists. */
449    private Map<String, Map<String, List<CmsJspContentAccessValueWrapper>>> m_localeSubValueList;
450
451    /** Lazy map with the locale value. */
452    private Map<String, Map<String, CmsJspContentAccessValueWrapper>> m_localeValue;
453
454    /** Lazy map with the locale value lists. */
455    private Map<String, Map<String, List<CmsJspContentAccessValueWrapper>>> m_localeValueList;
456
457    /** The original locale requested for accessing entries from the XML content. */
458    private Locale m_requestedLocale;
459
460    /** Resource the XML content is created from. */
461    private CmsResource m_resource;
462
463    /** JSP EL access wrapped version of the resource the XML content is created from. */
464    private CmsJspResourceWrapper m_resourceWrapper;
465
466    /**
467     * No argument constructor, required for a JavaBean.<p>
468     *
469     * You must call {@link #init(CmsObject, Locale, I_CmsXmlDocument, CmsResource)} and provide the
470     * required values when you use this constructor.<p>
471     *
472     * @see #init(CmsObject, Locale, I_CmsXmlDocument, CmsResource)
473     */
474    public CmsJspContentAccessBean() {
475
476        // must call init() manually later
477    }
478
479    /**
480     * Creates a content access bean based on a Resource, using the current request context locale.<p>
481     *
482     * @param cms the OpenCms context of the current user
483     * @param resource the resource to create the content from
484     */
485    public CmsJspContentAccessBean(CmsObject cms, CmsResource resource) {
486
487        this(cms, cms.getRequestContext().getLocale(), resource);
488    }
489
490    /**
491     * Creates a content access bean based on a Resource.<p>
492     *
493     * @param cms the OpenCms context of the current user
494     * @param locale the Locale to use when accessing the content
495     * @param resource the resource to create the content from
496     */
497    public CmsJspContentAccessBean(CmsObject cms, Locale locale, CmsResource resource) {
498
499        init(cms, locale, null, resource);
500    }
501
502    /**
503     * Creates a content access bean based on an XML content object.<p>
504     *
505     * @param cms the OpenCms context of the current user
506     * @param locale the Locale to use when accessing the content
507     * @param content the content to access
508     */
509    public CmsJspContentAccessBean(CmsObject cms, Locale locale, I_CmsXmlDocument content) {
510
511        init(cms, locale, content, content.getFile());
512    }
513
514    /**
515     * Generates the HTML attribute "data-imagednd" that enables the ADE image drag and drop feature.<p>
516     *
517     * @param structureId the structure ID of the XML document to insert the image
518     * @param locale the locale to generate the image in
519     * @param imagePath the XML path to the image source node.
520     *
521     * @return the HTML attribute "data-imagednd" that enables the ADE image drag and drop feature
522     */
523    protected static String createImageDndAttr(CmsUUID structureId, String imagePath, String locale) {
524
525        String attrValue = structureId + "|" + imagePath + "|" + locale;
526        String escapedAttrValue = CmsEncoder.escapeXml(attrValue);
527        return ("data-imagednd=\"" + escapedAttrValue + "\"");
528    }
529
530    /**
531     * Gets the list of locales for which there are attachments.<p>
532     *
533     * @return the list of locales for which there are attachments
534     */
535    public List<String> getAttachmentLocales() {
536
537        return CmsJspContentAttachmentsBean.getAttachmentLocales(m_cms, m_resource);
538    }
539
540    /**
541     * Gets an attachment bean, trying to automatically find the right locale for the current page.<p>
542     *
543     * @return the attachments bean for the current page's locale
544     * @throws CmsException if something goes wrong
545     */
546    public CmsJspContentAttachmentsBean getAttachments() throws CmsException {
547
548        return CmsJspContentAttachmentsBean.getAttachmentsForCurrentPage(m_cms, m_resource);
549    }
550
551    /**
552     * Gets a lazy map which maps locales to attachment beans for that locale.<p>
553     *
554     * @return the lazy map
555     */
556    public Map<?, ?> getAttachmentsForLocale() {
557
558        return CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
559
560            @SuppressWarnings("synthetic-access")
561            public Object transform(Object arg0) {
562
563                String localeStr = (String)arg0;
564                return CmsJspContentAttachmentsBean.getAttachmentsForLocale(m_cms, m_resource, localeStr);
565            }
566        });
567    }
568
569    /**
570     * Returns the OpenCms user context this bean was initialized with.<p>
571     *
572     * @return the OpenCms user context this bean was initialized with
573     */
574    public CmsObject getCmsObject() {
575
576        return m_cms;
577    }
578
579    /**
580     * Returns the raw VFS file object the content accessed by this bean was created from.<p>
581     *
582     * This can be used to access information from the raw file on a JSP.<p>
583     *
584     * Usage example on a JSP with the JSTL:<pre>
585     * &lt;cms:contentload ... &gt;
586     *     &lt;cms:contentaccess var="content" /&gt;
587     *     Root path of the resource: ${content.file.rootPath}
588     * &lt;/cms:contentload&gt;</pre>
589     *
590     * @return the raw VFS file object the content accessed by this bean was created from
591     */
592    public CmsFile getFile() {
593
594        return getRawContent().getFile();
595    }
596
597    /**
598     * Returns the substituted link to the content file.<p>
599     * Use for detail page links.<p>
600     *
601     * @return the substituted link
602     */
603    public String getFileLink() {
604
605        if (m_resource != null) {
606            return A_CmsJspValueWrapper.substituteLink(m_cms, m_cms.getSitePath(m_resource));
607        } else {
608            return "";
609        }
610    }
611
612    /**
613     * Returns the site path of the current resource, that is the result of
614     * {@link CmsObject#getSitePath(CmsResource)} with the resource
615     * obtained by {@link #getFile()}.<p>
616     *
617     * Usage example on a JSP with the JSTL:<pre>
618     * &lt;cms:contentload ... &gt;
619     *     &lt;cms:contentaccess var="content" /&gt;
620     *     Site path of the resource: "${content.filename}";
621     * &lt;/cms:contentload&gt;</pre>
622     *
623     * @return the site path of the current resource
624     *
625     * @see CmsObject#getSitePath(CmsResource)
626     */
627    public String getFilename() {
628
629        return m_cms.getSitePath(getRawContent().getFile());
630    }
631
632    /**
633     * Returns a lazy initialized Map that provides Booleans that indicate if a specified Locale is available
634     * in the XML content.<p>
635     *
636     * The provided Map key is assumed to be a String that represents a Locale.<p>
637     *
638     * Usage example on a JSP with the JSTL:<pre>
639     * &lt;cms:contentload ... &gt;
640     *     &lt;cms:contentaccess var="content" /&gt;
641     *     &lt;c:if test="${content.hasLocale['de']}" &gt;
642     *         The content has a "de" Locale!
643     *     &lt;/c:if&gt;
644     * &lt;/cms:contentload&gt;</pre>
645     *
646     * @return a lazy initialized Map that provides Booleans that indicate if a specified Locale is available
647     *      in the XML content
648     */
649    public Map<String, Boolean> getHasLocale() {
650
651        if (m_hasLocale == null) {
652            m_hasLocale = CmsCollectionsGenericWrapper.createLazyMap(new CmsHasLocaleTransformer());
653        }
654        return m_hasLocale;
655    }
656
657    /**
658     * Returns a lazy initialized Map that provides a Map that provides Booleans that
659     * indicate if a value (xpath) is available in the XML content in the selected locale.<p>
660     *
661     * The first provided Map key is assumed to be a String that represents the Locale,
662     * the second provided Map key is assumed to be a String that represents the xpath to the value.<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     *     &lt;c:if test="${content.hasLocaleValue['de']['Title']}" &gt;
668     *         The content has a "Title" value in the "de" Locale!
669     *     &lt;/c:if&gt;
670     * &lt;/cms:contentload&gt;</pre>
671     *
672     * Please note that you can also test if a locale value exists like this:<pre>
673     * &lt;c:if test="${content.value['de']['Title'].exists}" &gt; ... &lt;/c:if&gt;</pre>
674     *
675     * @return a lazy initialized Map that provides a Map that provides Booleans that
676     *      indicate if a value (xpath) is available in the XML content in the selected locale
677     *
678     * @see #getHasValue()
679     */
680    public Map<String, Map<String, Boolean>> getHasLocaleValue() {
681
682        if (m_hasLocaleValue == null) {
683            m_hasLocaleValue = CmsCollectionsGenericWrapper.createLazyMap(new CmsHasLocaleValueTransformer());
684        }
685        return m_hasLocaleValue;
686    }
687
688    /**
689     * Returns a lazy initialized Map that provides Booleans that
690     * indicate if a value (xpath) is available in the XML content in the current locale.<p>
691     *
692     * The provided Map key is assumed to be a String that represents the xpath to the value.<p>
693     *
694     * Usage example on a JSP with the JSTL:<pre>
695     * &lt;cms:contentload ... &gt;
696     *     &lt;cms:contentaccess var="content" /&gt;
697     *     &lt;c:if test="${content.hasValue['Title']}" &gt;
698     *         The content has a "Title" value in the current locale!
699     *     &lt;/c:if&gt;
700     * &lt;/cms:contentload&gt;</pre>
701     *
702     * Please note that you can also test if a value exists like this:<pre>
703     * &lt;c:if test="${content.value['Title'].exists}" &gt; ... &lt;/c:if&gt;</pre>
704     *
705     * @return a lazy initialized Map that provides Booleans that
706     *      indicate if a value (xpath) is available in the XML content in the current locale
707     *
708     * @see #getHasLocaleValue()
709     */
710    public Map<String, Boolean> getHasValue() {
711
712        return getHasLocaleValue().get(getLocale());
713    }
714
715    /**
716     * Returns the structure ID of the current resource, that is the ID of
717     * the resource obtained by {@link #getFile()}.<p>
718     *
719     * Usage example on a JSP with the JSTL:<pre>
720     * &lt;cms:contentload ... &gt;
721     *     &lt;cms:contentaccess var="content" /&gt;
722     *     Site path of the resource: "${content.id}";
723     * &lt;/cms:contentload&gt;</pre>
724     *
725     * @return the structure ID of the current resource
726     *
727     * @see CmsResource#getStructureId()
728     */
729    public CmsUUID getId() {
730
731        return getRawContent().getFile().getStructureId();
732    }
733
734    /**
735     * Gets the lazy imageDnd map.<p>
736     *
737     * @return the lazy imageDnd map
738     */
739    public Map<String, String> getImageDnd() {
740
741        if (m_imageDnd == null) {
742            m_imageDnd = CmsCollectionsGenericWrapper.createLazyMap(new CmsImageDndTransformer());
743        }
744        return m_imageDnd;
745    }
746
747    /**
748     * Returns <code>true</code> in case the current user is allowed to edit the XML content.<p>
749     *
750     * If the check is performed from the online project, the user context is internally switched to an offline
751     * project. So this may return <code>true</code> even if the user is currently in the online project.
752     *
753     * "Allowed to edit" here requires "read" and "write" permission for the VFS resource the XML content was created from.
754     * It also requires that the VFS resource is not locked by another user.
755     * Moreover, the user must be able to access at least one "offline" project.<p>
756     *
757     * Intended for quick checks to for example show / hide edit buttons for user generated content.<p>
758     *
759     * @return <code>true</code> in case the current user is allowed to edit the XML content
760     */
761    public boolean getIsEditable() {
762
763        boolean result = false;
764        try {
765            CmsObject cms;
766            if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
767                // we are in the online project, which means we must first switch to an offline project
768                // otherwise write permission checks will always return false
769                cms = OpenCms.initCmsObject(m_cms);
770                List<CmsProject> projects = OpenCms.getOrgUnitManager().getAllAccessibleProjects(
771                    cms,
772                    cms.getRequestContext().getOuFqn(),
773                    false);
774                if ((projects != null) && (projects.size() > 0)) {
775                    // there is at least one project available
776                    for (CmsProject p : projects) {
777                        // need to iterate because the online project will be part of the result list
778                        if (!p.isOnlineProject()) {
779                            cms.getRequestContext().setCurrentProject(p);
780                            break;
781                        }
782                    }
783                }
784            } else {
785                // not in the online project, so just use the current project
786                cms = m_cms;
787            }
788
789            result = cms.hasPermissions(
790                m_resource,
791                CmsPermissionSet.ACCESS_WRITE,
792                false,
793                CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
794            if (result) {
795                // still need to check the lock status
796                CmsLock lock = cms.getLock(m_resource);
797                if (!lock.isLockableBy(cms.getRequestContext().getCurrentUser())) {
798                    // resource is locked from a different user
799                    result = false;
800                }
801            }
802        } catch (CmsException e) {
803            // should not happen, in case it does just assume not editable
804        }
805        return result;
806    }
807
808    /**
809     * Returns the Locale this bean is using for content access, this may be a default fall back Locale.<p>
810     *
811     * @return the Locale this bean is using for content access, this may be a default fall back Locale
812     */
813    public Locale getLocale() {
814
815        // check the content if the locale has not been set yet
816        if (m_locale == null) {
817            getRawContent();
818        }
819        return m_locale;
820    }
821
822    /**
823     * Returns a lazy initialized Map that provides a List with all available elements paths (Strings)
824     * used in this document in the selected locale.<p>
825     *
826     * The provided Map key is assumed to be a String that represents the Locale.<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 items="${content.localeNames['de']}" var="elem"&gt;
832     *         &lt;c:out value="${elem}" /&gt;
833     *     &lt;/c:forEach&gt;
834     * &lt;/cms:contentload&gt;</pre>
835     *
836     * @return a lazy initialized Map that provides a Map that provides
837     *      values from the XML content in the selected locale
838     *
839     * @see #getNames()
840     */
841    public Map<String, List<String>> getLocaleNames() {
842
843        if (m_localeNames == null) {
844            m_localeNames = CmsCollectionsGenericWrapper.createLazyMap(new CmsLocaleNamesTransformer());
845        }
846        return m_localeNames;
847    }
848
849    /**
850     * Returns the map of RDFA maps by locale.<p>
851     *
852     * @return the map of RDFA maps by locale
853     */
854    public Map<String, Map<String, String>> getLocaleRdfa() {
855
856        if (m_localeRdfa == null) {
857            m_localeRdfa = CmsCollectionsGenericWrapper.createLazyMap(new CmsLocaleRdfaTransformer());
858        }
859        return m_localeRdfa;
860    }
861
862    /**
863     * Returns a lazy initialized Map that provides a Map that provides Lists of direct sub values
864     * from the XML content in the selected locale.<p>
865     *
866     * The first provided Map key is assumed to be a String that represents the Locale,
867     * the second provided Map key is assumed to be a String that represents the xpath to the value.<p>
868     *
869     * Usage example on a JSP with the JSTL:<pre>
870     * &lt;cms:contentload ... &gt;
871     *     &lt;cms:contentaccess var="content" /&gt;
872     *     &lt;c:forEach var="item" items="${content.localeSubValueList['de']['Items']}"&gt;
873     *         ${item}
874     *     &lt;/c:forEach&gt;
875     * &lt;/cms:contentload&gt;</pre>
876     *
877     * @return a lazy initialized Map that provides a Map that provides Lists of direct sub values
878     *      from the XML content in the selected locale
879     *
880     * @see #getLocaleValue()
881     */
882    public Map<String, Map<String, List<CmsJspContentAccessValueWrapper>>> getLocaleSubValueList() {
883
884        if (m_localeSubValueList == null) {
885            m_localeSubValueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsLocaleSubValueListTransformer());
886        }
887        return m_localeSubValueList;
888    }
889
890    /**
891     * Returns a lazy initialized Map that provides a Map that provides
892     * values from the XML content in the selected locale.<p>
893     *
894     * The first provided Map key is assumed to be a String that represents the Locale,
895     * the second provided Map key is assumed to be a String that represents the xpath to the value.<p>
896     *
897     * Usage example on a JSP with the JSTL:<pre>
898     * &lt;cms:contentload ... &gt;
899     *     &lt;cms:contentaccess var="content" /&gt;
900     *     The Title in Locale "de": ${content.localeValue['de']['Title']}
901     * &lt;/cms:contentload&gt;</pre>
902     *
903     * @return a lazy initialized Map that provides a Map that provides
904     *      values from the XML content in the selected locale
905     *
906     * @see #getValue()
907     */
908    public Map<String, Map<String, CmsJspContentAccessValueWrapper>> getLocaleValue() {
909
910        if (m_localeValue == null) {
911            m_localeValue = CmsCollectionsGenericWrapper.createLazyMap(new CmsLocaleValueTransformer());
912        }
913        return m_localeValue;
914    }
915
916    /**
917     * Returns a lazy initialized Map that provides a Map that provides Lists of values
918     * from the XML content in the selected locale.<p>
919     *
920     * The first provided Map key is assumed to be a String that represents the Locale,
921     * the second provided Map key is assumed to be a String that represents the xpath to the value.<p>
922     *
923     * Usage example on a JSP with the JSTL:<pre>
924     * &lt;cms:contentload ... &gt;
925     *     &lt;cms:contentaccess var="content" /&gt;
926     *     &lt;c:forEach var="teaser" items="${content.localeValueList['de']['Teaser']}"&gt;
927     *         ${teaser}
928     *     &lt;/c:forEach&gt;
929     * &lt;/cms:contentload&gt;</pre>
930     *
931     * @return a lazy initialized Map that provides a Map that provides Lists of values
932     *      from the XML content in the selected locale
933     *
934     * @see #getLocaleValue()
935     */
936    public Map<String, Map<String, List<CmsJspContentAccessValueWrapper>>> getLocaleValueList() {
937
938        if (m_localeValueList == null) {
939            m_localeValueList = CmsCollectionsGenericWrapper.createLazyMap(new CmsLocaleValueListTransformer());
940        }
941        return m_localeValueList;
942    }
943
944    /**
945     * Returns a list with all available elements paths (Strings) used in this document
946     * in the current locale.<p>
947     *
948     * Usage example on a JSP with the JSTL:<pre>
949     * &lt;cms:contentload ... &gt;
950     *     &lt;cms:contentaccess var="content" /&gt;
951     *     &lt;c:forEach items="${content.names}" var="elem"&gt;
952     *         &lt;c:out value="${elem}" /&gt;
953     *     &lt;/c:forEach&gt;
954     * &lt;/cms:contentload&gt;</pre>
955     *
956     * @return a list with all available elements paths (Strings) used in this document in the current locale
957     *
958     * @see #getLocaleNames()
959     */
960    public List<String> getNames() {
961
962        return getLocaleNames().get(getLocale());
963    }
964
965    /**
966     * Prints the XPaths of the available content values into an HTML ul list.<p>
967     *
968     * @return the HTML list of XPaths
969     */
970    public String getPrintStructure() {
971
972        if (getRawContent().hasLocale(m_requestedLocale)) {
973            List<I_CmsXmlContentValue> values = new ArrayList<I_CmsXmlContentValue>(
974                getRawContent().getValues(m_requestedLocale));
975            Collections.sort(values, new Comparator<I_CmsXmlContentValue>() {
976
977                public int compare(I_CmsXmlContentValue arg0, I_CmsXmlContentValue arg1) {
978
979                    return arg0.getPath().compareTo(arg1.getPath());
980                }
981            });
982
983            StringBuffer buffer = new StringBuffer("<ul>\n");
984            for (I_CmsXmlContentValue value : values) {
985                buffer.append("<li>").append(value.getPath()).append("</li>\n");
986            }
987            buffer.append("</ul>");
988            return buffer.toString();
989        } else {
990            return "";
991        }
992    }
993
994    /**
995     * Returns the raw XML content object that is accessed by this bean.<p>
996     *
997     * @return the raw XML content object that is accessed by this bean
998     */
999    public I_CmsXmlDocument getRawContent() {
1000
1001        if (m_content == null) {
1002            // content has not been provided, must unmarshal XML first
1003            CmsFile file;
1004            try {
1005                file = m_cms.readFile(m_resource);
1006                if (CmsResourceTypeXmlPage.isXmlPage(file)) {
1007                    // this is an XML page
1008                    m_content = CmsXmlPageFactory.unmarshal(m_cms, file);
1009                } else {
1010                    // this is an XML content
1011                    m_content = CmsXmlContentFactory.unmarshal(m_cms, file);
1012                }
1013            } catch (CmsException e) {
1014                // this usually should not happen, as the resource already has been read by the current user
1015                // and we just upgrade it to a File
1016                throw new CmsRuntimeException(
1017                    Messages.get().container(Messages.ERR_XML_CONTENT_UNMARSHAL_1, m_resource.getRootPath()),
1018                    e);
1019            }
1020        }
1021
1022        // make sure a valid locale is used
1023        if (m_locale == null) {
1024            m_locale = OpenCms.getLocaleManager().getBestMatchingLocale(
1025                m_requestedLocale,
1026                OpenCms.getLocaleManager().getDefaultLocales(m_cms, m_cms.getRequestContext().getUri()),
1027                m_content.getLocales());
1028        }
1029
1030        return m_content;
1031    }
1032
1033    /**
1034     * Returns RDFA by value name map.<p>
1035     *
1036     * @return RDFA by value name map
1037     */
1038    public Map<String, String> getRdfa() {
1039
1040        return getLocaleRdfa().get(getLocale());
1041    }
1042
1043    /**
1044     * Reads and returns the categories assigned to the content's VFS resource.
1045     * @return the categories assigned to the content's VFS resource.
1046     */
1047    public CmsJspCategoryAccessBean getReadCategories() {
1048
1049        if (null == m_categories) {
1050            m_categories = readCategories();
1051        }
1052        return m_categories;
1053    }
1054
1055    /**
1056     * Returns a lazy initialized Map that provides Lists of direct sub values
1057     * of the given value from the XML content in the current locale.<p>
1058     *
1059     * The provided Map key is assumed to be a String that represents the xpath to the value.
1060     * Use this method in case you want to iterate over a List of sub values from the XML content.<p>
1061     *
1062     * Usage example on a JSP with the JSTL:<pre>
1063     * &lt;cms:contentload ... &gt;
1064     *     &lt;cms:contentaccess var="content" /&gt;
1065     *     &lt;c:forEach var="teaser" items="${content.subValueList['Items']}"&gt;
1066     *         ${item}
1067     *     &lt;/c:forEach&gt;
1068     * &lt;/cms:contentload&gt;</pre>
1069     *
1070     * @return a lazy initialized Map that provides Lists of values from the XML content in the current locale
1071     *
1072     * @see #getLocaleValueList()
1073     */
1074    public Map<String, List<CmsJspContentAccessValueWrapper>> getSubValueList() {
1075
1076        return getLocaleSubValueList().get(getLocale());
1077    }
1078
1079    /**
1080     * Returns the resource type name.<p>
1081     *
1082     * @return the resource type name
1083     */
1084    public String getTypeName() {
1085
1086        if (m_resource != null) {
1087            return OpenCms.getResourceManager().getResourceType(m_resource).getTypeName();
1088        } else {
1089            return "";
1090        }
1091    }
1092
1093    /**
1094     * Returns a lazy initialized Map that provides values from the XML content in the current locale.<p>
1095     *
1096     * The provided Map key is assumed to be a String that represents the xpath to the value.<p>
1097     *
1098     * Usage example on a JSP with the JSTL:<pre>
1099     * &lt;cms:contentload ... &gt;
1100     *     &lt;cms:contentaccess var="content" /&gt;
1101     *     The Title: ${content.value['Title']}
1102     * &lt;/cms:contentload&gt;</pre>
1103     *
1104     * @return a lazy initialized Map that provides values from the XML content in the current locale
1105     *
1106     * @see #getLocaleValue()
1107     */
1108    public Map<String, CmsJspContentAccessValueWrapper> getValue() {
1109
1110        return getLocaleValue().get(getLocale());
1111    }
1112
1113    /**
1114     * Returns a lazy initialized Map that provides Lists of values from the XML content in the current locale.<p>
1115     *
1116     * The provided Map key is assumed to be a String that represents the xpath to the value.
1117     * Use this method in case you want to iterate over a List of values form the XML content.<p>
1118     *
1119     * Usage example on a JSP with the JSTL:<pre>
1120     * &lt;cms:contentload ... &gt;
1121     *     &lt;cms:contentaccess var="content" /&gt;
1122     *     &lt;c:forEach var="teaser" items="${content.valueList['Teaser']}"&gt;
1123     *         ${teaser}
1124     *     &lt;/c:forEach&gt;
1125     * &lt;/cms:contentload&gt;</pre>
1126     *
1127     * @return a lazy initialized Map that provides Lists of values from the XML content in the current locale
1128     *
1129     * @see #getLocaleValueList()
1130     */
1131    public Map<String, List<CmsJspContentAccessValueWrapper>> getValueList() {
1132
1133        return getLocaleValueList().get(getLocale());
1134    }
1135
1136    /**
1137     * Returns an instance of a VFS access bean,
1138     * initialized with the OpenCms user context this bean was created with.<p>
1139     *
1140     * @return an instance of a VFS access bean,
1141     *      initialized with the OpenCms user context this bean was created with
1142     */
1143    public CmsJspVfsAccessBean getVfs() {
1144
1145        return CmsJspVfsAccessBean.create(m_cms);
1146    }
1147
1148    /**
1149     * Returns the content file as a CmsJspObjectValueWrapper.<p>
1150     *
1151     * @return the content file as a CmsJspObjectValueWrapper
1152     */
1153    public CmsJspResourceWrapper getWrap() {
1154
1155        if (m_resourceWrapper == null) {
1156            m_resourceWrapper = CmsJspResourceWrapper.wrap(m_cms, m_resource);
1157        }
1158        return m_resourceWrapper;
1159    }
1160
1161    /**
1162     * Initialize this instance.<p>
1163     *
1164     * @param cms the OpenCms context of the current user
1165     * @param locale the Locale to use when accessing the content
1166     * @param content the XML content to access
1167     * @param resource the resource to create the content from
1168     */
1169    public void init(CmsObject cms, Locale locale, I_CmsXmlDocument content, CmsResource resource) {
1170
1171        m_cms = cms;
1172        m_requestedLocale = locale;
1173        m_content = content;
1174        m_resource = resource;
1175    }
1176
1177    /**
1178     * Reads the categories assigned to the content's VFS resource.
1179     * @return the categories assigned to the content's VFS resource.
1180     */
1181    private CmsJspCategoryAccessBean readCategories() {
1182
1183        return new CmsJspCategoryAccessBean(getCmsObject(), m_resource);
1184    }
1185}