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