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